SpringCloud学习笔记(1)- Spring Cloud Alibaba

落日映苍穹つ 2022-09-10 13:20 321阅读 0赞

文章目录

  • SpringCloud学习笔记(1)- Spring Cloud Alibaba
  • 服务治理
    • Nacos 服务注册
    • Nacos 服务发现与调用
    • Ribbon 负载均衡
    • Sentinel 服务限流降级
      • 流控规则
      • 流控效果
      • 降级规则
      • 热点规则
      • 授权规则
      • 自定义规则异常返回
    • 整合 RocketMQ
      • 安装 RocketMQ
      • 安装 RocketMQ 控制台
      • Java 实现消息发送
      • Java 实现消息消费
    • Spring Boot 整合 RocketMQ
    • 服务网关
      • Gateway 限流
    • 分布式事务
      • 模拟分布式事务异常
      • Seata 解决(事务回滚)

SpringCloud学习笔记(1)- Spring Cloud Alibaba

创建父工程

Spring Cloud Alibaba 的环境在父工程中创建,微服务的各个组件作为子工程,继承父工程的环境。

Spring Boot —》Spring Cloud —》Spring Cloud Alibaba

pom.xml 中添加。

  1. <dependencyManagement>
  2. <dependencies>
  3. <!-- Spring Cloud Hoxton -->
  4. <dependency>
  5. <groupId>org.springframework.cloud</groupId>
  6. <artifactId>spring-cloud-dependencies</artifactId>
  7. <version>Hoxton.SR3</version>
  8. <type>pom</type>
  9. <scope>import</scope>
  10. </dependency>
  11. <!-- Spring Cloud Alibaba -->
  12. <dependency>
  13. <groupId>com.alibaba.cloud</groupId>
  14. <artifactId>spring-cloud-alibaba-dependencies</artifactId>
  15. <version>2.2.1.RELEASE</version>
  16. <type>pom</type>
  17. <scope>import</scope>
  18. </dependency>
  19. </dependencies>
  20. </dependencyManagement>

服务治理

服务注册 + 服务发现

Nacos 服务注册

解压,启动服务。

Nacos 搭建成功,接下来注册服务。

在父工程路径下创建子工程,让子工程继承父工程的环境依赖,pom.xml 中添加 nacos 发现组件。

  1. <dependency>
  2. <groupId>com.alibaba.cloud</groupId>
  3. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  4. </dependency>

application.yml 中配置

  1. spring:
  2. cloud:
  3. nacos:
  4. discovery:
  5. # 指定nacos server地址
  6. server-addr: localhost:8848
  7. application:
  8. name: my-nacos

Nacos 服务发现与调用

pom.xml 添加 discovery,完成服务发现。

  1. <dependency>
  2. <groupId>com.alibaba.cloud</groupId>
  3. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  4. </dependency>

通过 discoveryClient 发现注册到 nacos 中的 provider 服务。

  1. @RestController
  2. public class ConsumerController {
  3. @Autowired
  4. private DiscoveryClient discoveryClient;
  5. @GetMapping("/instances")
  6. public List<ServiceInstance> instances(){
  7. List<ServiceInstance> provider = discoveryClient.getInstances("provider");
  8. return provider;
  9. }
  10. }
  11. @Configuration
  12. public class ConsumerConfig {
  13. @Bean
  14. public RestTemplate restTemplate(){
  15. return new RestTemplate();
  16. }
  17. }
  18. @RestController
  19. public class ConsumerController {
  20. @Autowired
  21. private DiscoveryClient discoveryClient;
  22. @Autowired
  23. private RestTemplate restTemplate;
  24. @GetMapping("/index")
  25. public String index(){
  26. List<ServiceInstance> provider = discoveryClient.getInstances("provider");
  27. int index = ThreadLocalRandom.current().nextInt(provider.size());
  28. String url = provider.get(index).getUri()+"/index";
  29. return "consumer随机远程调用provier:"+this.restTemplate.getForObject(url, String.class);
  30. }
  31. }

在这里插入图片描述

Ribbon 负载均衡

  1. @Configuration
  2. public class ConsumerConfig {
  3. @Bean
  4. @LoadBalanced
  5. public RestTemplate restTemplate(){
  6. return new RestTemplate();
  7. }
  8. }
  9. @RestController
  10. public class ConsumerController {
  11. @Autowired
  12. private RestTemplate restTemplate;
  13. @GetMapping("/index")
  14. public String index(){
  15. return "consumer远程调用provier:"+this.restTemplate.getForObject("http://provider/index", String.class);
  16. }
  17. }

随机 (随机的算法)

  1. server:
  2. port: 8180
  3. provider:
  4. ribbon:
  5. NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

Nacos 权重 (基于权重的算法)

  1. @Slf4j
  2. public class NacosWeightedRule extends AbstractLoadBalancerRule {
  3. @Autowired
  4. private NacosDiscoveryProperties nacosDiscoveryProperties;
  5. @Override
  6. public void initWithNiwsConfig(IClientConfig iClientConfig) {
  7. //读取配置文件
  8. }
  9. @Override
  10. public Server choose(Object o) {
  11. ILoadBalancer loadBalancer = this.getLoadBalancer();
  12. BaseLoadBalancer baseLoadBalancer = (BaseLoadBalancer) loadBalancer;
  13. //获取要请求的微服务名称
  14. String name = baseLoadBalancer.getName();
  15. //获取服务发现的相关API
  16. NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
  17. try {
  18. Instance instance = namingService.selectOneHealthyInstance(name);
  19. log.info("选择的实例是port={},instance={}",instance.getPort(),instance);
  20. return new NacosServer(instance);
  21. } catch (NacosException e) {
  22. e.printStackTrace();
  23. return null;
  24. }
  25. }
  26. }
  27. server:
  28. port: 8180
  29. provider:
  30. ribbon:
  31. NFLoadBalancerRuleClassName: com.southwind.configuration.NacosWeightedRule

Sentinel 服务限流降级

雪崩效应
在这里插入图片描述
解决方案

1、设置线程超时

2、设置限流

3、熔断器 Sentinel、Hystrix (类比保险丝)

  • 降级
  • 限流
  • 熔断

在这里插入图片描述
1、provider模块pom.xml 引入依赖

  1. <dependency>
  2. <groupId>com.alibaba.cloud</groupId>
  3. <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-actuator</artifactId>
  8. </dependency>

2、application 配置

  1. management:
  2. endpoints:
  3. web:
  4. exposure:
  5. include: '*'
  6. spring:
  7. cloud:
  8. sentinel:
  9. transport:
  10. dashboard: localhost:8080

3、下载 Sentinel 控制台,解压,启动。

流控规则

在这里插入图片描述

直接限流

关联限流

链路限流

1、pom.xml 添加依赖

  1. <dependency>
  2. <groupId>com.alibaba.csp</groupId>
  3. <artifactId>sentinel-core</artifactId>
  4. <version>1.7.1</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>com.alibaba.csp</groupId>
  8. <artifactId>sentinel-web-servlet</artifactId>
  9. <version>1.7.1</version>
  10. </dependency>

2、application.yml

  1. spring:
  2. cloud:
  3. sentinel:
  4. filter:
  5. enabled: false

3、写配置类(让能关联到Service)

  1. package com.southwind.configuration;
  2. import com.alibaba.csp.sentinel.adapter.servlet.CommonFilter;
  3. import org.springframework.boot.web.servlet.FilterRegistrationBean;
  4. import org.springframework.context.annotation.Bean;
  5. import org.springframework.context.annotation.Configuration;
  6. @Configuration
  7. public class FilterConfiguration {
  8. @Bean
  9. public FilterRegistrationBean registrationBean(){
  10. FilterRegistrationBean registrationBean = new FilterRegistrationBean();
  11. registrationBean.setFilter(new CommonFilter());
  12. registrationBean.addUrlPatterns("/*");
  13. registrationBean.addInitParameter(CommonFilter.WEB_CONTEXT_UNIFY,"false"); //让所有链路开放出来才能限制
  14. registrationBean.setName("sentinelFilter");
  15. return registrationBean;
  16. }
  17. }

4、Service

  1. @Service
  2. public class HelloService {
  3. @SentinelResource("test")
  4. public void test(){
  5. System.out.println("test");
  6. }
  7. }

5、Controller

  1. @GetMapping("/test1")
  2. public String test1(){
  3. this.helloService.test();
  4. return "test1";
  5. }
  6. @GetMapping("/test2")
  7. public String test2(){
  8. this.helloService.test();
  9. return "test2";
  10. }

流控效果

快速失败

直接抛出异常

Warm UP

给系统一个预热的时间,预热时间段内单机阈值较低,预热时间过后单机阈值增加,预热时间内当前的单机阈值是设置的阈值的三分之一,预热时间过后单机阈值恢复设置的值。

排队等待

当请求调用失败之后,不会立即抛出异常,等待下一次调用,时间范围是超时时间,在时间范围内如果能请求成功则不抛出异常,如果请求则抛出异常。

降级规则

RT

单个请求的响应时间超过阈值,则进入准降级状态,接下来 1 S 内连续 5 个请求响应时间均超过阈值,就进行降级,持续时间为时间窗口的值。

异常比例

每秒异常数量占通过量的比例大于阈值,就进行降级处理,持续时间为时间窗口的值。

异常数

1 分钟内的异常数超过阈值就进行降级处理,时间窗口的值要大于 60S,否则刚结束熔断又进入下一次熔断了。

热点规则

热点规则是流控规则的更细粒度操作,可以具体到对某个热点参数的限流,设置限流之后,如果带着限流参数的请求量超过阈值,则进行限流,时间为统计窗口时长。

必须要添加 @SentinelResource,即对资源进行流控。

  1. @GetMapping("/hot")
  2. @SentinelResource("hot")
  3. public String hot(
  4. @RequestParam(value = "num1",required = false) Integer num1,
  5. @RequestParam(value = "num2",required = false) Integer num2){
  6. return num1+"-"+num2;
  7. }

授权规则

给指定的资源设置流控应用(追加参数),可以对流控应用进行访问权限的设置,具体就是添加白名单和黑名单。

如何给请求指定流控应用,通过实现 RequestOriginParser 接口来完成,代码如下所示。

  1. package com.southwind.configuration;
  2. import com.alibaba.csp.sentinel.adapter.servlet.callback.RequestOriginParser;
  3. import org.springframework.util.StringUtils;
  4. import javax.servlet.http.HttpServletRequest;
  5. public class RequestOriginParserDefinition implements RequestOriginParser {
  6. @Override
  7. public String parseOrigin(HttpServletRequest httpServletRequest) {
  8. String name = httpServletRequest.getParameter("name");
  9. if(StringUtils.isEmpty(name)){
  10. throw new RuntimeException("name is null");
  11. }
  12. return name;
  13. }
  14. }

要让 RequestOriginParserDefinition 生效,需要在配置类中进行配置。

  1. package com.southwind.configuration;
  2. import com.alibaba.csp.sentinel.adapter.servlet.callback.WebCallbackManager;
  3. import org.springframework.context.annotation.Configuration;
  4. import javax.annotation.PostConstruct;
  5. @Configuration
  6. public class SentinelConfiguration {
  7. @PostConstruct
  8. public void init(){
  9. WebCallbackManager.setRequestOriginParser(new RequestOriginParserDefinition());
  10. }
  11. }

自定义规则异常返回

创建异常处理类

  1. package com.southwind.handler;
  2. import com.alibaba.csp.sentinel.adapter.servlet.callback.UrlBlockHandler;
  3. import com.alibaba.csp.sentinel.slots.block.BlockException;
  4. import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
  5. import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
  6. import javax.servlet.http.HttpServletRequest;
  7. import javax.servlet.http.HttpServletResponse;
  8. import java.io.IOException;
  9. public class ExceptionHandler implements UrlBlockHandler {
  10. @Override
  11. public void blocked(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws IOException {
  12. httpServletResponse.setContentType("text/html;charset=utf-8");
  13. String msg = null;
  14. if(e instanceof FlowException){
  15. msg = "限流";
  16. }else if(e instanceof DegradeException){
  17. msg = "降级";
  18. }
  19. httpServletResponse.getWriter().write(msg);
  20. }
  21. }

进行配置。

  1. @Configuration
  2. public class SentinelConfiguration {
  3. @PostConstruct
  4. public void init(){
  5. WebCallbackManager.setUrlBlockHandler(new ExceptionHandler());
  6. }
  7. }

整合 RocketMQ

安装 RocketMQ

1、传入 Linux 服务器

2、解压缩

  1. unzip rocketmq-all-4.7.1-bin-release.zip

3、启动 NameServer

  1. nohup ./bin/mqnamesrv &

4、检查是否启动成功

  1. netstat -an | grep 9876

在这里插入图片描述
5、启动 Broker

启动之前需要编辑配置文件,修改 JVM 内存设置,默认给的内存 4 GB,超过我们的 JVM 了。

  1. cd bin
  2. vim runserver.sh

在这里插入图片描述

  1. vim runbroker.sh

在这里插入图片描述
启动 Broker

  1. nohup ./mqbroker -n localhost:9876 &

可以查看日志

  1. tail -f ~/logs/rocketmqlogs/broker.log

在这里插入图片描述
启动成功

6、测试 RocketMQ

消息发送

  1. cd bin
  2. export NAMESRV_ADDR=localhost:9876
  3. ./tools.sh org.apache.rocketmq.example.quickstart.Producer

消息接收

  1. cd bin
  2. export NAMESRV_ADDR=localhost:9876
  3. ./tools.sh org.apache.rocketmq.example.quickstart.Consumer

7、关闭 RocketMQ

  1. cd bin
  2. ./mqshutdown broker
  3. ./mqshutdown namesrv

安装 RocketMQ 控制台

1、解压缩,修改配置,打包

在这里插入图片描述

  1. mvn clean package -Dmaven.test.skip=true

在这里插入图片描述
2、进入 target 启动 jar

  1. java -jar rocketmq-console-ng-1.0.0.jar

在这里插入图片描述
打开浏览器访问 localhost:9877,如果报错如下:
在这里插入图片描述
这是因为我们的 RocketMQ 安装在 Linux 中,控制台在 windows,Linux 需要开放端口才能访问,开放 10909 和 9876 端口

  1. firewall-cmd --zone=public --add-port=10909/tcp --permanent
  2. firewall-cmd --zone=public --add-port=9876/tcp --permanent
  3. systemctl restart firewalld.service
  4. firewall-cmd --reload

重新启动控制台项目

在这里插入图片描述

Java 实现消息发送

1、pom.xml 中引入依赖

  1. <dependency>
  2. <groupId>org.apache.rocketmq</groupId>
  3. <artifactId>rocketmq-spring-boot-starter</artifactId>
  4. <version>2.1.0</version>
  5. </dependency>

2、生产消息

  1. package com.southwind;
  2. import org.apache.rocketmq.client.producer.DefaultMQProducer;
  3. import org.apache.rocketmq.client.producer.SendResult;
  4. import org.apache.rocketmq.common.message.Message;
  5. public class Test {
  6. public static void main(String[] args) throws Exception {
  7. //创建消息生产者
  8. DefaultMQProducer producer = new DefaultMQProducer("myproducer-group");
  9. //设置NameServer
  10. producer.setNamesrvAddr("192.168.248.129:9876");
  11. //启动生产者
  12. producer.start();
  13. //构建消息对象
  14. Message message = new Message("myTopic","myTag",("Test MQ").getBytes());
  15. //发送消息
  16. SendResult result = producer.send(message, 1000);
  17. System.out.println(result);
  18. //关闭生产者
  19. producer.shutdown();
  20. }
  21. }

3、直接运行,如果报错 sendDefaultImpl call timeout,可以开放 10911 端口

  1. firewall-cmd --zone=public --add-port=10911/tcp --permanent
  2. systemctl restart firewalld.service
  3. firewall-cmd --reload

打开 RocketMQ 控制台,可查看消息。

Java 实现消息消费

  1. package com.southwind.service;
  2. import lombok.extern.slf4j.Slf4j;
  3. import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
  4. import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
  5. import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
  6. import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
  7. import org.apache.rocketmq.client.exception.MQClientException;
  8. import org.apache.rocketmq.common.message.MessageExt;
  9. import java.util.List;
  10. @Slf4j
  11. public class ConsumerTest {
  12. public static void main(String[] args) throws MQClientException {
  13. //创建消息消费者
  14. DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("myconsumer-group");
  15. //设置NameServer
  16. consumer.setNamesrvAddr("192.168.248.129:9876");
  17. //指定订阅的主题和标签
  18. consumer.subscribe("myTopic","*");
  19. //回调函数
  20. consumer.registerMessageListener(new MessageListenerConcurrently() {
  21. @Override
  22. public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
  23. log.info("Message=>{}",list);
  24. return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
  25. }
  26. });
  27. //启动消费者
  28. consumer.start();
  29. }
  30. }

Spring Boot 整合 RocketMQ

provider

1、pom.xml

  1. <dependency>
  2. <groupId>org.apache.rocketmq</groupId>
  3. <artifactId>rocketmq-spring-boot-starter</artifactId>
  4. <version>2.1.0</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.apache.rocketmq</groupId>
  8. <artifactId>rocketmq-client</artifactId>
  9. <version>4.7.0</version>
  10. </dependency>

2、application.yml

  1. rocketmq:
  2. name-server: 192.168.248.129:9876
  3. producer:
  4. group: myprovider

3、Order

  1. package com.southwind.entity;
  2. import lombok.AllArgsConstructor;
  3. import lombok.Data;
  4. import lombok.NoArgsConstructor;
  5. import java.util.Date;
  6. @Data
  7. @AllArgsConstructor
  8. @NoArgsConstructor
  9. public class Order {
  10. private Integer id;
  11. private String buyerName;
  12. private String buyerTel;
  13. private String address;
  14. private Date createDate;
  15. }

4、Controller

  1. @Autowired
  2. private RocketMQTemplate rocketMQTemplate;
  3. @GetMapping("/create")
  4. public Order create(){
  5. Order order = new Order(
  6. 1,
  7. "张三",
  8. "123123",
  9. "软件园",
  10. new Date()
  11. );
  12. this.rocketMQTemplate.convertAndSend("myTopic",order);
  13. return order;
  14. }

consumer

1、pom.xml

  1. <dependency>
  2. <groupId>org.apache.rocketmq</groupId>
  3. <artifactId>rocketmq-spring-boot-starter</artifactId>
  4. <version>2.1.0</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.apache.rocketmq</groupId>
  8. <artifactId>rocketmq-client</artifactId>
  9. <version>4.7.0</version>
  10. </dependency>

2、application.yml

  1. rocketmq:
  2. name-server: 192.168.248.129:9876

3、Service

  1. @Slf4j
  2. @Service
  3. @RocketMQMessageListener(consumerGroup = "myConsumer",topic = "myTopic")
  4. public class SmsService implements RocketMQListener<Order> {
  5. @Override
  6. public void onMessage(Order order) {
  7. log.info("新订单{},发短信",order);
  8. }
  9. }

服务网关

Spring Cloud Gateway 是基于 Netty,跟 Servlet 不兼容,所以你的工程中不能出现 Servlet 的组件 。

1、pom.xml

注意,一定不能出现 spring web 的依赖,因为 Gateway 与 Servlet 不兼容。

  1. <dependency>
  2. <groupId>org.springframework.cloud</groupId>
  3. <artifactId>spring-cloud-starter-gateway</artifactId>
  4. </dependency>

2、application.yml

  1. server:
  2. port: 8010
  3. spring:
  4. application:
  5. name: gateway
  6. cloud:
  7. gateway:
  8. discovery:
  9. locator:
  10. enabled: true
  11. routes:
  12. - id: provider_route
  13. uri: http://localhost:8081
  14. predicates:
  15. - Path=/provider/**
  16. filters:
  17. - StripPrefix=1

上面这种做法其实没有用到 nacos ,现在我们让 gateway 直接去 nacos 中发现服务,配置更加简单了。

1、pom.xml 引入 nacos

  1. <dependency>
  2. <groupId>org.springframework.cloud</groupId>
  3. <artifactId>spring-cloud-starter-gateway</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>com.alibaba.cloud</groupId>
  7. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  8. </dependency>

2、application.yml

  1. server:
  2. port: 8010
  3. spring:
  4. application:
  5. name: gateway
  6. cloud:
  7. gateway:
  8. discovery:
  9. locator:
  10. enabled: true

Gateway 限流

基于路由限流

1、pom.xml

  1. <dependency>
  2. <groupId>org.springframework.cloud</groupId>
  3. <artifactId>spring-cloud-starter-gateway</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>com.alibaba.csp</groupId>
  7. <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
  8. </dependency>

2、配置类

  1. package com.southwind.configuration;
  2. import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
  3. import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
  4. import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
  5. import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
  6. import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
  7. import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
  8. import org.springframework.beans.factory.ObjectProvider;
  9. import org.springframework.cloud.gateway.filter.GlobalFilter;
  10. import org.springframework.context.annotation.Bean;
  11. import org.springframework.context.annotation.Configuration;
  12. import org.springframework.core.Ordered;
  13. import org.springframework.core.annotation.Order;
  14. import org.springframework.http.HttpStatus;
  15. import org.springframework.http.MediaType;
  16. import org.springframework.http.codec.ServerCodecConfigurer;
  17. import org.springframework.web.reactive.function.BodyInserters;
  18. import org.springframework.web.reactive.function.server.ServerResponse;
  19. import org.springframework.web.reactive.result.view.ViewResolver;
  20. import org.springframework.web.server.ServerWebExchange;
  21. import reactor.core.publisher.Mono;
  22. import javax.annotation.PostConstruct;
  23. import java.util.*;
  24. @Configuration
  25. public class GatewayConfiguration {
  26. private final List<ViewResolver> viewResolvers;
  27. private final ServerCodecConfigurer serverCodecConfigurer;
  28. public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,
  29. ServerCodecConfigurer serverCodecConfigurer) {
  30. this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
  31. this.serverCodecConfigurer = serverCodecConfigurer;
  32. }
  33. //配置限流的异常处理
  34. @Bean
  35. @Order(Ordered.HIGHEST_PRECEDENCE)
  36. public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
  37. return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
  38. }
  39. //配置初始化的限流参数
  40. @PostConstruct
  41. public void initGatewayRules(){
  42. Set<GatewayFlowRule> rules = new HashSet<>();
  43. rules.add(
  44. new GatewayFlowRule("provider_route")
  45. .setCount(1)
  46. .setIntervalSec(1)
  47. );
  48. GatewayRuleManager.loadRules(rules);
  49. }
  50. //初始化限流过滤器
  51. @Bean
  52. @Order(Ordered.HIGHEST_PRECEDENCE)
  53. public GlobalFilter sentinelGatewayFilter() {
  54. return new SentinelGatewayFilter();
  55. }
  56. //自定义限流异常页面
  57. @PostConstruct
  58. public void initBlockHandlers(){
  59. BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
  60. @Override
  61. public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
  62. Map map = new HashMap();
  63. map.put("code",0);
  64. map.put("msg","被限流了");
  65. return ServerResponse.status(HttpStatus.OK)
  66. .contentType(MediaType.APPLICATION_JSON)
  67. .body(BodyInserters.fromObject(map));
  68. }
  69. };
  70. GatewayCallbackManager.setBlockHandler(blockRequestHandler);
  71. }
  72. }

3、application.yml

  1. server:
  2. port: 8010
  3. spring:
  4. application:
  5. name: gateway
  6. cloud:
  7. gateway:
  8. discovery:
  9. locator:
  10. enabled: true
  11. routes:
  12. - id: provider_route
  13. uri: http://localhost:8081
  14. predicates:
  15. - Path=/provider/**
  16. filters:
  17. - StripPrefix=1

基于 API 分组限流

1、修改配置类,添加基于 API 分组限流的方法,修改初始化的限流参数

  1. package com.southwind.configuration;
  2. import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
  3. import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition;
  4. import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem;
  5. import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem;
  6. import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager;
  7. import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
  8. import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
  9. import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
  10. import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
  11. import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
  12. import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
  13. import org.springframework.beans.factory.ObjectProvider;
  14. import org.springframework.cloud.gateway.filter.GlobalFilter;
  15. import org.springframework.context.annotation.Bean;
  16. import org.springframework.context.annotation.Configuration;
  17. import org.springframework.core.Ordered;
  18. import org.springframework.core.annotation.Order;
  19. import org.springframework.http.HttpStatus;
  20. import org.springframework.http.MediaType;
  21. import org.springframework.http.codec.ServerCodecConfigurer;
  22. import org.springframework.web.reactive.function.BodyInserters;
  23. import org.springframework.web.reactive.function.server.ServerResponse;
  24. import org.springframework.web.reactive.result.view.ViewResolver;
  25. import org.springframework.web.server.ServerWebExchange;
  26. import reactor.core.publisher.Mono;
  27. import javax.annotation.PostConstruct;
  28. import java.util.*;
  29. @Configuration
  30. public class GatewayConfiguration {
  31. private final List<ViewResolver> viewResolvers;
  32. private final ServerCodecConfigurer serverCodecConfigurer;
  33. public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,
  34. ServerCodecConfigurer serverCodecConfigurer) {
  35. this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
  36. this.serverCodecConfigurer = serverCodecConfigurer;
  37. }
  38. //配置限流的异常处理
  39. @Bean
  40. @Order(Ordered.HIGHEST_PRECEDENCE)
  41. public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
  42. return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
  43. }
  44. //配置初始化的限流参数
  45. @PostConstruct
  46. public void initGatewayRules(){
  47. Set<GatewayFlowRule> rules = new HashSet<>();
  48. rules.add(new GatewayFlowRule("provider_api1").setCount(1).setIntervalSec(1));
  49. rules.add(new GatewayFlowRule("provider_api2").setCount(1).setIntervalSec(1));
  50. GatewayRuleManager.loadRules(rules);
  51. }
  52. //初始化限流过滤器
  53. @Bean
  54. @Order(Ordered.HIGHEST_PRECEDENCE)
  55. public GlobalFilter sentinelGatewayFilter() {
  56. return new SentinelGatewayFilter();
  57. }
  58. //自定义限流异常页面
  59. @PostConstruct
  60. public void initBlockHandlers(){
  61. BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
  62. @Override
  63. public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
  64. Map map = new HashMap();
  65. map.put("code",0);
  66. map.put("msg","被限流了");
  67. return ServerResponse.status(HttpStatus.OK)
  68. .contentType(MediaType.APPLICATION_JSON)
  69. .body(BodyInserters.fromObject(map));
  70. }
  71. };
  72. GatewayCallbackManager.setBlockHandler(blockRequestHandler);
  73. }
  74. //自定义API分组
  75. @PostConstruct
  76. private void initCustomizedApis(){
  77. Set<ApiDefinition> definitions = new HashSet<>();
  78. ApiDefinition api1 = new ApiDefinition("provider_api1")
  79. .setPredicateItems(new HashSet<ApiPredicateItem>(){ {
  80. add(new ApiPathPredicateItem().setPattern("/provider/api1/**")
  81. .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
  82. }});
  83. ApiDefinition api2 = new ApiDefinition("provider_api2")
  84. .setPredicateItems(new HashSet<ApiPredicateItem>(){ {
  85. add(new ApiPathPredicateItem().setPattern("/provider/api2/demo1"));
  86. }});
  87. definitions.add(api1);
  88. definitions.add(api2);
  89. GatewayApiDefinitionManager.loadApiDefinitions(definitions);
  90. }
  91. }

2、Controller 添加方法

  1. @GetMapping("/api1/demo1")
  2. public String demo1(){
  3. return "demo";
  4. }
  5. @GetMapping("/api1/demo2")
  6. public String demo2(){
  7. return "demo";
  8. }
  9. @GetMapping("/api2/demo1")
  10. public String demo3(){
  11. return "demo";
  12. }
  13. @GetMapping("/api2/demo2")
  14. public String demo4(){
  15. return "demo";
  16. }

也可以基于 Nacos 服务发现组件进行限流

  1. server:
  2. port: 8010
  3. spring:
  4. application:
  5. name: gateway
  6. cloud:
  7. gateway:
  8. discovery:
  9. locator:
  10. enabled: true

API 分组代码修改,改为 discovery 中的服务名。

  1. ApiDefinition api2 = new ApiDefinition("provider_api2")
  2. .setPredicateItems(new HashSet<ApiPredicateItem>(){ {
  3. add(new ApiPathPredicateItem().setPattern("/p1/api2/demo1"));
  4. }});

分布式事务

模拟分布式事务异常

1、创建两个工程 order、pay,pom.xml

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-jdbc</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-web</artifactId>
  8. </dependency>
  9. <dependency>
  10. <groupId>mysql</groupId>
  11. <artifactId>mysql-connector-java</artifactId>
  12. <scope>runtime</scope>
  13. </dependency>
  14. <dependency>
  15. <groupId>org.projectlombok</groupId>
  16. <artifactId>lombok</artifactId>
  17. <optional>true</optional>
  18. </dependency>

2、建两个数据库 order、pay,两个微服务分别访问。

3、分别写两个服务的 application.yml

  1. server:
  2. port: 8010
  3. spring:
  4. application:
  5. name: order
  6. datasource:
  7. driver-class-name: com.mysql.cj.jdbc.Driver
  8. username: root
  9. password: 123456
  10. url: jdbc:mysql://localhost:3306/order
  11. server:
  12. port: 8020
  13. spring:
  14. application:
  15. name: pay
  16. datasource:
  17. driver-class-name: com.mysql.cj.jdbc.Driver
  18. username: root
  19. password: 123456
  20. url: jdbc:mysql://localhost:3306/pay

4、分别写两个 Service

  1. package com.southwind.service;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.jdbc.core.JdbcTemplate;
  4. import org.springframework.stereotype.Service;
  5. @Service
  6. public class OrderService {
  7. @Autowired
  8. private JdbcTemplate jdbcTemplate;
  9. public void save(){
  10. this.jdbcTemplate.update("insert into orders(username) values ('张三')");
  11. }
  12. }
  13. package com.southwind.service;
  14. import org.springframework.beans.factory.annotation.Autowired;
  15. import org.springframework.jdbc.core.JdbcTemplate;
  16. import org.springframework.stereotype.Service;
  17. @Service
  18. public class PayService {
  19. @Autowired
  20. private JdbcTemplate jdbcTemplate;
  21. public void save(){
  22. this.jdbcTemplate.update("insert into pay(username) values ('张三')");
  23. }
  24. }

5、控制器 Order 通过 RestTemplate 调用 Pay 的服务

  1. package com.southwind.controller;
  2. import com.southwind.service.OrderService;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.web.bind.annotation.GetMapping;
  5. import org.springframework.web.bind.annotation.RestController;
  6. import org.springframework.web.client.RestTemplate;
  7. @RestController
  8. public class OrderController {
  9. @Autowired
  10. private OrderService orderService;
  11. @Autowired
  12. private RestTemplate restTemplate;
  13. @GetMapping("/save")
  14. public String save(){
  15. //订单
  16. this.orderService.save();
  17. int i = 10/0;
  18. //支付
  19. this.restTemplate.getForObject("http://localhost:8020/save",String.class);
  20. return "success";
  21. }
  22. }
  23. package com.southwind.controller;
  24. import com.southwind.service.PayService;
  25. import org.springframework.beans.factory.annotation.Autowired;
  26. import org.springframework.web.bind.annotation.GetMapping;
  27. import org.springframework.web.bind.annotation.RestController;
  28. @RestController
  29. public class PayController {
  30. @Autowired
  31. private PayService payService;
  32. @GetMapping("/save")
  33. public String save(){
  34. this.payService.save();
  35. return "success";
  36. }
  37. }

6、启动类

  1. package com.southwind;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. import org.springframework.context.annotation.Bean;
  5. import org.springframework.web.client.RestTemplate;
  6. @SpringBootApplication
  7. public class OrderApplication {
  8. public static void main(String[] args) {
  9. SpringApplication.run(OrderApplication.class, args);
  10. }
  11. @Bean
  12. public RestTemplate restTemplate(){
  13. return new RestTemplate();
  14. }
  15. }
  16. package com.southwind;
  17. import org.springframework.boot.SpringApplication;
  18. import org.springframework.boot.autoconfigure.SpringBootApplication;
  19. @SpringBootApplication
  20. public class PayApplication {
  21. public static void main(String[] args) {
  22. SpringApplication.run(PayApplication.class, args);
  23. }
  24. }

分布式异常模拟结束,Order 存储完成之后,出现异常,会导致 Pay 无法存储,但是 Order 数据库不会进行回滚。

Seata 解决(事务回滚)

1、下载

2、解压,修改两个文件
在这里插入图片描述
regisry.conf

  1. registry {
  2. type = "nacos"
  3. nacos {
  4. serverAddr = "localhost"
  5. namespace = "public"
  6. cluster = "default"
  7. }
  8. }
  9. config {
  10. type = "nacos"
  11. nacos {
  12. serverAddr = "localhost"
  13. namespace = "public"
  14. cluster = "default"
  15. }
  16. }

nacos-config.txt
在这里插入图片描述
3、启动 Nacos,运行 nacos-config.sh 将 Seata 配置导入 Nacos

进入 conf,右键 Git Bash Here

  1. cd conf
  2. sh nacos-config.sh 127.0.0.1

执行成功,刷新 Nacos,配置加入(如下图)
在这里插入图片描述
nacos-config.txt 配置已生效
在这里插入图片描述
4、启动 Seata Server, JDK 8 以上环境无法启动

  1. cd bin
  2. seata-server.bat -p 8090 -m file

在这里插入图片描述
启动成功,Nacos 注册成功。
在这里插入图片描述
Seata 服务环境搭建完毕,接下来去应用中添加。

1、初始化数据库,在两个数据库中添加事务日志记录表,SQL Seata 已经提供。

在这里插入图片描述
2、直接在两个数据库运行脚本。

  1. CREATE TABLE `undo_log` (
  2. `id` bigint(20) NOT NULL AUTO_INCREMENT,
  3. `branch_id` bigint(20) NOT NULL,
  4. `xid` varchar(100) NOT NULL,
  5. `context` varchar(128) NOT NULL,
  6. `rollback_info` longblob NOT NULL,
  7. `log_status` int(11) NOT NULL,
  8. `log_created` datetime NOT NULL,
  9. `log_modified` datetime NOT NULL,
  10. `ext` varchar(100) DEFAULT NULL,
  11. PRIMARY KEY (`id`),
  12. UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
  13. ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

3、两个工程的 pom.xml 添加 Seata 组件和 Nacos Config 组件。

  1. <dependency>
  2. <groupId>com.alibaba.cloud</groupId>
  3. <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
  4. <version>2.1.1.RELEASE</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>com.alibaba.cloud</groupId>
  8. <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
  9. </dependency>

4、给 JDBCTemplate 添加代理数据源

  1. package com.southwind;
  2. import io.seata.rm.datasource.DataSourceProxy;
  3. import org.springframework.boot.SpringApplication;
  4. import org.springframework.boot.autoconfigure.SpringBootApplication;
  5. import org.springframework.context.annotation.Bean;
  6. import org.springframework.jdbc.core.JdbcTemplate;
  7. import org.springframework.web.client.RestTemplate;
  8. import javax.sql.DataSource;
  9. @SpringBootApplication
  10. public class OrderApplication {
  11. public static void main(String[] args) {
  12. SpringApplication.run(OrderApplication.class, args);
  13. }
  14. @Bean
  15. public RestTemplate restTemplate(){
  16. return new RestTemplate();
  17. }
  18. @Bean
  19. public JdbcTemplate jdbcTemplate(DataSource dataSource){
  20. return new JdbcTemplate(new DataSourceProxy(dataSource));
  21. }
  22. }
  23. package com.southwind;
  24. import io.seata.rm.datasource.DataSourceProxy;
  25. import org.springframework.boot.SpringApplication;
  26. import org.springframework.boot.autoconfigure.SpringBootApplication;
  27. import org.springframework.context.annotation.Bean;
  28. import org.springframework.jdbc.core.JdbcTemplate;
  29. import javax.sql.DataSource;
  30. @SpringBootApplication
  31. public class PayApplication {
  32. public static void main(String[] args) {
  33. SpringApplication.run(PayApplication.class, args);
  34. }
  35. @Bean
  36. public JdbcTemplate jdbcTemplate(DataSource dataSource){
  37. return new JdbcTemplate(new DataSourceProxy(dataSource));
  38. }
  39. }

5、将 registry.conf 复制到两个工程的 resources 下。

6、给两个工程添加 bootstrap.yml 读取 Nacos 配置。

  1. spring:
  2. application:
  3. name: order
  4. cloud:
  5. nacos:
  6. config:
  7. server-addr: localhost:8848
  8. namespace: public
  9. group: SEATA_GROUP
  10. alibaba:
  11. seata:
  12. tx-service-group: ${ spring.application.name}
  13. spring:
  14. application:
  15. name: pay
  16. cloud:
  17. nacos:
  18. config:
  19. server-addr: localhost:8848
  20. namespace: public
  21. group: SEATA_GROUP
  22. alibaba:
  23. seata:
  24. tx-service-group: ${ spring.application.name}

tx-service-group 需要和 Nacos 配置中的名称一致。
在这里插入图片描述
7、在 Order 调用 Pay 处添加注解 @GlobalTransactional

  1. package com.southwind.controller;
  2. import com.southwind.service.OrderService;
  3. import io.seata.spring.annotation.GlobalTransactional;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.web.bind.annotation.GetMapping;
  6. import org.springframework.web.bind.annotation.RestController;
  7. import org.springframework.web.client.RestTemplate;
  8. @RestController
  9. public class OrderController {
  10. @Autowired
  11. private OrderService orderService;
  12. @Autowired
  13. private RestTemplate restTemplate;
  14. @GetMapping("/save")
  15. @GlobalTransactional
  16. public String save(){
  17. //订单
  18. this.orderService.save();
  19. int i = 10/0;
  20. //支付
  21. this.restTemplate.getForObject("http://localhost:8020/save",String.class);
  22. return "success";
  23. }
  24. }

【视频参考】https://www.bilibili.com/video/BV1Mt4y1i7JW
【源码参考】https://github.com/monkeyhlj/spring-study

发表评论

表情:
评论列表 (有 0 条评论,321人围观)

还没有评论,来说两句吧...

相关阅读