Spring Cloud Alibaba微服务组件学习笔记

发布于:2024-08-16 ⋅ 阅读:(183) ⋅ 点赞:(0)


一、版本说明

  1. 版本关系

    github查看:https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E

    在这里插入图片描述

    在这里插入图片描述

    链接包含了项目所需依赖,只需在 <dependencyManagement> 中添加 Spring Cloud Alibaba 依赖即可:

    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-alibaba-dependencies</artifactId>
        <version>2.2.7.RELEASE</version>
        <type>pom</type>
        <scope>import</scope>
    </dependency>
    

    父项目添加依赖后,子项目自动继承依赖。

  2. 项目创建

    • 阿里云脚手架:https://start.aliyun.com/
    • Spring脚手架:https://start.spring.io/

二、Nacos注册中心

  1. 什么是Nacos

    集:注册中心配置中心服务管理 的平台。

    关键特性:

    • 服务发现和服务健康监测
    • 动态配置服务
    • 动态 DNS 服务
    • 服务及其元数据 (服务名称、地址、命名空间等) 管理
  2. Nacos注册中心

    引入心跳机制,定时接收心跳信息,若没有按时接收到心跳 (默认5秒发送一次) ,则注册中心便会将status改为down;若15秒内还没有心跳,则不会再拉取该服务,同时服务健康状态变为false;再后达到一定时间 (默认30秒) 没有心跳,则直接剔除该服务。
    在这里插入图片描述

  3. 核心功能

    Nacos Discovery

    • 服务注册:Nacos Client发送REST请求向Nacos Server注册服务;
    • 服务心跳:注册后,会维护一个定时心跳,默认5秒一次;
    • 服务同步:Nacos Server集群之间会互相同步服务实例;
    • 服务发现:Nacos Client在调用服务时,会缓存Server中的服务清单到本地,并启动一个定时任务定时拉取最新服务清单来更新本地缓存;
    • 服务监控检查:Nacos Server会开启一个定时任务检查注册服务的健康状态,15s未接收到心跳,健康状态将视为false,服务发现不会拉取;30s没有心跳,则直接剔除该服务实例。
  4. Nacos Server部署(windows版本)

    Nacos服务端下载 :https://github.com/alibaba/nacos/releases/;自行选择合适的版本下载,源码等。

    Nacos帮助文档 :https://nacos.io/zh-cn/docs/what-is-nacos.html/

    • 配置启动方式为单机模式 (默认为集群方式):
      • 编辑 nacos/bin/startup.cmd 文件,将 set MODE="cluster" 修改为 set MODE="standalone" 模式;
      • 配置Nacos服务,nacos/conf/application.properties 修改端口号、超时时间、最大连接数、数据源 等信息,默认存储位置是内存中,集群启动一般设置为MYSQL中;
      • 登录账户密码默认都是nacos;
    • 注意:Nacos2.0.3版本对 JDK 的要求,如果启动失败,先更新 JDK 版本!此处使用的是1.8.0_311
  5. Nacos Client服务

    1. 引入Nacos服务发现的依赖,版本由父工程提供:

      <dependency>
          <groupId>com.alibaba.cloud</groupId>
          <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
      </dependency>
      
    2. yml配置:

      # 应用服务 WEB 访问端口
      server:
        port: 8080
      # 应用名称
      spring:
        application:
          name: order-service   # 注册 Nacos Server将采用应用名称
        cloud:
          nacos:
            server-addr: 127.0.0.1:8848 # 注册地址
            discovery:
              username: nacos   # Nacos Server登录账户、密码、命名空间(自定义 dev / product)
              password: nacos
              namespace: public
              ephemeral: false	# 默认true,切换临时/永久,永久实例宕机后注册中心不会删除服务
      
    3. 如果使用 RestTemplate 模板类调用接口,需要配置如下:

      @Configuration
      public class RestTemplateConfig {
          @Bean
          @LoadBalanced		//此处一定要配置负载均衡注解,Nacos不知道调用哪个服务
          public RestTemplate getRestTemplate(RestTemplateBuilder builder) {
              RestTemplate restTemplate = builder.build();
              return restTemplate;
          }
      }
      
    4. 启动类注解:@EnableDiscoveryClient,可加可不加,高版本的SpringCloud做了处理;

    5. 代码参考

      • order-service代码,服务调用者:
      @RestController
      @RequestMapping("/order")
      public class OrderController {
      
          @Autowired
          private RestTemplate restTemplate;
      
          @RequestMapping("/getOrder")
          public String getOrder(){
              String info =
                      restTemplate.getForObject("http://stock-service/stock/getStock", String.class);
              return "HELLO WORLD!" + info;
          }
      }
      
      • stock-service代码,服务提供者,此处复制启动类端口8082测试负载均衡,默认轮询:
      @RestController
      @RequestMapping("/stock")
      public class StockController {
      
          @Value("${server.port}")
          private String port;
      
          @RequestMapping("/getStock")
          public String getStock(){
              return "下单了!" + port;
          }
      }
      
  6. Nacos Server配置项详解:

    • 命名空间:namespace,可创建多个命名空间,区分不同的项目或项目组等;

      在这里插入图片描述

    • 服务管理

      在这里插入图片描述

      (1) 服务列表:

      • 保护阈值:取值范围0~1,0表示不开启;当 健康实例 / 总实例数 < 阈值 时,服务会将不健康的服务拿去使用,防止雪崩;

      • 临时实例:ephemeral,默认true,切换临时/永久,永久实例宕机后注册中心不会删除服务,结合保护阈值使用;

      • 权重:weight,结合负载均衡策略使用,越大则调用的次数越多;

      • 分组:group,默认值“DEFAULT_GROUP”,比命名空间更细致的划分;

      • 元数据:metadata,键值对类型的值,用于扩展功能;

        在这里插入图片描述

      (2) 订阅者列表:

      ​ 用于查询哪个服务被调用,调用者名称等信息;

    • 权限管理:配置登录Nacos的账户、密码、访问权限等信息;

    • 配置管理:统一配置服务的配置项,类似Spring Cloud Config的功能;

    • 集群管理:Nacos集群的配置;

  7. Nacos集群搭建:

    环境:JDK1.8+,Maven3.3+,Nginx,MYSQL5.7+;

    参考文档:https://nacos.io/zh-cn/docs/cluster-mode-quick-start.html

    1. 复制三个节点,分别安装Nacos,搭建伪集群测试;

    2. 修改配置文件 application.propprties,配置数据源,MYSQL5.7+;

    3. 修改配置文件cluster.conf.example,改为cluster.conf,内部配置集群的 IP 地址:

      # ip:port
      192.168.2.100:8849
      192.168.2.101:8850
      192.168.2.102:8851 
      
    4. 数据库运行Nacos的sql脚本,创建数据库;

    5. 修改启动脚本 startup.sh:

    • 启动方式为集群:set MODE="cluster"
    • 内存不足时 (此处虚拟机内存较小) ,修改小一些: JAVA_OPT="${JAVA_OPT} -server -Xms512m -Xmx512m -Xmn256m -XX:MetaspaceSize=64m -XX:MaxMetaspaceSize=128m"
    1. 官方推荐,nginx作为反向代理:

      • 配置 nginx.conf 配置文件:

        upstream nacoscluster { 
            server 192.168.2.100:8849;
            server 192.168.2.101:8850;
            server 192.168.2.102:8851;
        }	
        server { 
            listen 8848;
            server_name localhost;
            location /nacos/ {
            	# 反向代理的路径,如访问http://192.168.2.100:8848将均衡代理到对应的Nacos上
            	proxy_pass http://nacoscluster/naocs/;
            }
        }
        

三、Ribbon负载均衡

Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡工具,它通过Load Balance获取到服务端的所有机器实例,基于某种规则去调用这些服务,同时也可以实现我们自己的负载均衡算法。

  1. 主流的负载方案:

    • 集中式负载均衡 (服务端),硬件 / 软件的方式,如Nginx;
    • 客户端负载均衡,根据自身请求的状况做负载均衡;
  2. 常见的负载均衡算法,实现接口 IRule

    在这里插入图片描述

    • 随机

    • 轮询

    • 加权轮询

    • 地址Hash算法

    • 最小连接数

      ……

  3. Nacos使用Ribobn

    1. 依赖问题;nacos-discovery 中添加了Ribbon的依赖,所以无需在添加!
    2. 添加注解:@LoadBalanced 加在RestTemplate配置类中;
    3. RestTemplate调用的 url 地址,将 IP 改为对应的服务名称;
  4. 自定义负载算法:

    1. 自定义配置类,可自己实现AbstractLoadBalancerRule抽象类,重写choose方法,使用getLoadBalancer方法获取所有的服务,按需返回指定的服务,即可实现自己需要的负载策略:

      @Bean
      public IRule getIRule() {
          return new NacosRule();
      }
      

      注意此处的配置类不能放在启动类可扫描到的包下,否则会被所有服务共享策略!!!

    2. 启动类配置注解,针对某一服务进行负载均衡策略:

      @SpringBootApplication
      @RibbonClients(value = {
          @RibbonClient(name = "order-service", configuration = RestTemplateConfig.class),
          @RibbonClient(name = "stock-service", configuration = RestTemplateConfig.class)
      })
      public class OrderNacosApplication {
      
          public static void main(String[] args) {
              SpringApplication.run(OrderNacosApplication.class, args);
          }
      
      }
      
  5. 自定义配置文件修改负载策略:

    采用NacosRule,它实现了Nacos的权重,可以在Nacos中配置,也可以在配置文件中配置weight,设置为0表示不使用此服务。

    # 基于配置文件形式的 针对单个服务的 Ribbon 负载均衡策略
    stock-service:  # 被调用者的服务名称
      ribbon:
        NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule
    
  6. Ribbon饥饿加载:

    默认关闭,开启后,服务将在启动时初始化负载策略,解决服务初次访问速度响应慢的问题;

    ribbon:
      eager-load:
        enabled: true	# 开启饥饿加载
        # 指定需要饥饿加载的服务名称,多个服务使用逗号分隔
        clients: stock-service, order-service
    
  7. Load Balancer替换Ribbon

    支持RestTemplate、WebClient两种方式;

    1. 添加SpringCloud父依赖:

      <!--父工程添加如下依赖-->
      <dependencyManagement>
          <dependency>
              <groupId>org.springframework.cloud</groupId>
              <artifactId>spring-cloud-dependencies</artifactId>
              <version>${spring-cloud.version}</version>
              <type>pom</type>
              <scope>import</scope>
          </dependency>
      </dependencyManagement>
      
    2. nacos-discovery依赖,排除掉Ribbon,子项目添加Load Balancer依赖:

      <dependency>
          <groupId>com.alibaba.cloud</groupId>
       <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
          <exclusions>
              <exclusion>
                  <!--排除Ribbon依赖-->
                  <groupId>org.springframework.cloud</groupId>
                  <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
              </exclusion>
          </exclusions>
      </dependency>
      <!--添加loadbalancer依赖-->
      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-loadbalancer</artifactId>
      </dependency>
      
    3. yml文件禁用Ribbon功能:

      spring:
        cloud:
          loadbalancer:
            ribbon:
              enabled: false
      
    4. 启动类配置,类似 @RibbonClients 的用法:

      @LoadBalancerClients(value = {
          @LoadBalancerClient(name = "order-service", configuration = RestTemplateConfig.class),
          @LoadBalancerClient(name = "stock-service", configuration = RestTemplateConfig.class)
      })
      
    5. 自定义负载策略,实现 ReactorLoadBalancer 接口,重写choose方法,实现自己的负载策略。


四、微服务调用组件Feign

Feign 是Netflix开发的声明式、模板化的HTTP客户端。可以更发方便简洁的调用HTTP API。

SpringCloud Openfeign 对Feign 进行了增强,使其支持SpringMVC注解,而且还整合了Ribbon和Nacos。

  1. 使用OpenFeign

    1. 添加父依赖:spring-cloud-dependencies

    2. 添加openfeign依赖:

      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-openfeign</artifactId>
      </dependency>
      
    3. 服务调用者编写Feign接口,这里需要保证服务提供者服务存在Nacos中:

      /**
       * name:需要调用的服务名称
       * path:也可以去掉,去使用@RequestMapping("/stock")
       */
      @FeignClient(name = "stock-service", path = "/stock")
      public interface StockFeignService {
          @RequestMapping("/getStock")
          String getStock();
      }
      
    4. 启动类开启Feign功能:@EnableFeignClients,添加在启动类或配置类上;

  2. Feign的自定义配置

    1. 日志配置

      • 自定义配置类,配置日志级别:

        @Configuration
        public class FeignConfig {
            @Bean
            public Logger.Level feignLoggerLevel() {
                return Logger.Level.FULL;	// 选择一种配置
             // return Logger.Level.BASIC;
             // return Logger.Level.HEADERS;
             // return Logger.Level.NONE;
            }
        }
        
      • yml配置文件修改Spring默认日志级别,改为只打印Feign的日志:

        # Spring的日志级别是info,而feign的日志级别是debug,不改这里就不会输出feign日志
        logging:
          level:
          	# 只输出指定的Feign接口日志信息
            com.axin.feign.StockFeignService: debug
        
      • 局部日志配置

        • 第一种,将配置类的 @Configuration 去除,而在 @FeignClient 注解上配置属性:

          @FeignClient(name = "stock-service", configuration = FeignConfig.class)
          public interface StockFeignService {
              @RequestMapping("/getStock")
              String getStock();
          }
          
        • 第二种,修改yml配置文件:

          feign:
            client:
              config:
                stock-service:  # 日志的服务名称
                  loggerLevel: BASIC
          
    2. 契约配置

      原生Feign是不支持SpringMVC注解的,所以修改契约配置可以去使用原生的Feign注解,如老项目升级。

      修改配置后,Feign接口将不能使用MVC注解,而是使用原生注解。

      • 配置类修改契约配置:

        @Bean
        public Contract feignContract() {
            return new Contract.Default();
        }
        
      • 配置文件yml修改:

        feign:
          client:
            config:
              stock-service:  # 服务名称
                contract: feign.Contract.Default  # 契约配置,还原原生注解
        
      • 原生注解对应MVC注解:

        @RequestMapping("/getStock") ==> @RequestLine("GET /getStock") 手动配置请求方法;

        @PathVariable ==> @Param

    3. 超时时间配置

      • 全局配置类配置:

        @Bean
        public Request.Options options() {
            return new Request.Options(5000, 10000);
        }
        
      • 配置文件yml配置:

        feign:
          client:
            config:
              stock-service:  # 服务名称
                connectTimeout: 5000    # 连接超时的时间,默认2s
                readTimeout: 10000      # 响应超时的时间,默认5s
        
    4. 自定义拦截器实现认证

      • 配置类配置,自定义拦截器,实现RequestInterceptor接口,重写apply方法,加入Spring容器内;

      • 配置文件yml配置,这里拦截器就无需加入Spring容器了:

        feign:
          client:
            config:
              stock-service:  # 服务名称
                requestInterceptors[0]: # 配置拦截器
                  com.it.config.FeignAuthRequestInterceptor   # 自定义拦截器全路径
        

五、Nacos配置中心

  1. 新增Nacos Config配置文件

    1. Nacos服务端新增配置:

      注:微服务将自动读取Nacos中与微服务名称一样的配置文件名!!!

      在这里插入图片描述

    2. 启用Nacos的权限控制,nacos/conf/application.properties 中修改配置文件:

      权限控制开启后,微服务中必须配置Nacos的用户名和密码,否则报错!!!

      ### If turn on auth system:
      nacos.core.auth.enabled=true	# 默认false
      
  2. 搭建Nacos Config的服务读取配置文件

    1. 添加依赖:

      <dependency>
          <groupId>com.alibaba.cloud</groupId>
          <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
      </dependency>
      
    2. 配置bootstrap.yml文件,此处没有开启权限控制,无需配置Nacos的用户名和密码:

      spring:
        application:
          name: config-service
        cloud:
          nacos:
            server-addr: 127.0.0.1:8848
      
    3. 测试读取配置信息:

      @SpringBootApplication
      public class ConfigNacosApplication {
      
          public static void main(String[] args) {
              ConfigurableApplicationContext applicationContext = 
                  	SpringApplication.run(ConfigNacosApplication.class, args);
              ConfigurableEnvironment environment = 
                  	applicationContext.getEnvironment();
              System.out.println("my.name:" + 
                                 environment.getProperty("my.name"));
              System.out.println("my.password:" + 
                                 environment.getProperty("my.password"));
          }
      
      }
      

      在这里插入图片描述

    4. 测试实时修改Naocs配置中心的配置,微服务将自动拉取新的信息;

    5. 修改微服务配置的文件格式,默认的格式为properties

      spring:
        cloud:
          nacos:
            config:
              file-extension: yaml
      
    6. 配置profiles

      • Nacos配置中心的配置

        dataid的命名:服务名 - 环境 - 文件后缀,如config-nacos-dev.yml;

        注意:默认的配置文件(与服务名相同的dataid的配置文件),则无需添加文件后缀。

      • 本地微服务的配置

        application.yml中,使用 spring.profiles.active 选择 dev / product 环境。

      • 配置文件的优先级

        profiles > 默认 > 自定义dataid配置,优先级大的将覆盖小的,同时配置将互补,配置一项即可。

    7. 配置命名空间namespace,微服务配置后,将读取指定命名空间中的配置信息。

    8. 配置组group,微服务配置后,将读取指定组内的配置信息。

    9. 自定义dataid配置文件的读取:

      共两种方式,二者功能一致,只是优先级不同。

      extension-configs > shared-configs,同时,数组的下标越大优先级越大。

      • shared-configs数组:

        注意:SpringBoot 将优先使用最后读取的配置文件,此处将选择下标1的配置。

        spring:
          cloud:
            nacos:
              server-addr: 127.0.0.1:8848
              config:
                file-extension: yaml
                shared-configs:   # 接收一个数组
                  - data-id: my.config1.yml	# 下标为0
                    refresh: true
                  - data-id: my.config2.yml	# 下标为1,SpringBoot优先使用后读取的配置
                    refresh: true
        
      • extension-configs数组:

        spring:
          cloud:
            nacos:
              server-addr: 127.0.0.1:8848
              config:
                file-extension: yaml
                extension-configs:   # 接收一个数组
                  - data-id: my.config3.yml   # 下标为0
                      refresh: true
                  - data-id: my.config4.yml   # 下标为1
                    refresh: true
        
    10. java类中动态获取配置信息:

      注意:正常情况下,修改配置中心的配置后,类中是无法动态取到的,需要添加注解 @RefreshScope

      @RefreshScope
      @RestController
      @RequestMapping("/config")
      public class ConfigController {
          
          @Value("${my.name}")
          private String name;
          @Value("${my.password}")
          private String password;
          
          @RequestMapping("/show")
          public String show(){
              return this.toString();
          }
      
          @Override
          public String toString() {
              return "ConfigController{" +
                      "name='" + name + '\'' +
                      ", password='" + password + '\'' +
                      '}';
          }
      }
      

六、Sentinel分布式高可用组件

  1. 说明文档:https://github.com/alibaba/Sentinel

Sentinel 是面向分布式服务架构的流量控制组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来保障微服务的稳定性。

Sentinel的特征:

  • 丰富的应用场景:
  • 完备的实时监控:
  • 广泛的开源生态:
  • 完善的SPI扩展点:

Sentinelhystrix对比:

在这里插入图片描述

  1. 快速开始:

    1. 核心依赖:

      <dependency>
          <groupId>com.alibaba.csp</groupId>
          <artifactId>sentinel-core</artifactId>
          <version>1.8.4</version>
      </dependency>
      
    2. 其他依赖:

      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      
    3. 自定义一个Controller:

      import com.alibaba.csp.sentinel.Entry;
      import com.alibaba.csp.sentinel.SphU;
      import com.alibaba.csp.sentinel.slots.block.BlockException;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RestController;
      
      @RestController
      public class HelloWorldController {
      
          @RequestMapping("/hello")
          public String helloWorld() {
              Entry entry = null;
              try {
                  entry = SphU.entry("hello");
                  return "hello world";
              }catch (BlockException be) {
                  return "限流了";
              } catch (Exception e) {
                  e.printStackTrace();
                  return "error";
              } finally {
                  if (entry != null) {
                      entry.exit();
                  }
              }
          }
      
      }
      
    4. 自定义一个Spring的初始化方法,内部添加限流规则:

      import com.alibaba.csp.sentinel.slots.block.RuleConstant;
      import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
      import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
      import org.springframework.context.annotation.Configuration;
      
      import javax.annotation.PostConstruct;
      import java.util.ArrayList;
      
      @Configuration
      public class SpringInitConfig {
      
          @PostConstruct  // 初始化方法, 类加载的时候执行, 在这里添加限流规则, 也可在启动类中添加
          private static void initFlowRule() {
              ArrayList<FlowRule> flowRules = new ArrayList<>();
              FlowRule rule = new FlowRule(); // 创建限流规则
              rule.setResource("hello");  //资源名称,可以是类名、方法名、路径名等
              rule.setGrade(RuleConstant.FLOW_GRADE_QPS); //限流等级
              rule.setCount(1);   // 限流阈值, 即每秒钟最多访问1次, 参数类型为double
              flowRules.add(rule);
              // 注册限流规则
              FlowRuleManager.loadRules(flowRules);   // 加载限流规则
          }
      }
      
    5. 端口号配置:

      server:
      	port: 8080
      
    6. 启动项目,浏览器访问:http://localhost:8080/hello

    7. 缺点:代码复杂,嵌入代码过多

  2. @SentinelResource的使用

    1. 文档:https://github.com/alibaba/Sentinel/wiki/%E6%B3%A8%E8%A7%A3%E6%94%AF%E6%8C%81

    2. 添加依赖

      <dependency>
          <groupId>com.alibaba.csp</groupId>
          <artifactId>sentinel-annotation-aspectj</artifactId>
          <version>1.8.4</version>
      
    ```
    1. 配置SentinelResourceAspect切面类,加入到Spring容器中:

      /**
       * 配置SentinelResourceAspect, 会自动拦截@SentinelResource注解的方法
       */
      @Bean
      public SentinelResourceAspect sentinelResourceAspect() {
          return new SentinelResourceAspect();
      }
      
    2. 处理器方法上添加注解==@SentinelResource==:

      @SentinelResource(
              value = "user",
              blockHandler = "userBlockHandler",
              fallback = "userFallback",
              exceptionsToIgnore = {NullPointerException.class}
      )
      @RequestMapping("/user")
      public User getUser(@RequestParam(required = false, value = "num") String num) {
          if (num != null) {
              throw new RuntimeException("降级了");
          } 
          return new User("张三", "23");
      }
      
      public User userBlockHandler(String num, BlockException e) {
          e.printStackTrace();
          return new User("限流了", "0");
      }
      
      public User userFallback(String num, Throwable e) {
          e.printStackTrace();
          return new User("降级了", "0");
      }
      
    3. 在Spring的初始化方法中,内部添加限流规则:

      @PostConstruct  // 初始化方法, 在类加载的时候执行
      private static void initFlowRule() {
          ArrayList<FlowRule> flowRules = new ArrayList<>();
          FlowRule rule2 = new FlowRule(); // 创建限流规则(流量的控制), 可以添加多个
          rule2.setResource("user");  //资源名称,可以是类名、方法名、路径名等,具体参考官方文档
          rule2.setGrade(RuleConstant.FLOW_GRADE_QPS);
          rule2.setCount(1);   // 限流阈值, 即每秒钟最多访问1次, 参数类型为double
          flowRules.add(rule2);
          // 注册限流规则
          FlowRuleManager.loadRules(flowRules);   // 加载限流规则
      }
      
    4. @SentinelResource的常用属性

      • value:资源名称,可以是类名、方法名、路径名等

      • blockHandler:限流时执行的方法,返回值类型与原返回值类型一致,参数可以新增BlockException

      • blockHandlerClass:限流时执行方法存在的类,使用blockHandler指定对应的方法,需要使用static修饰

      • fallback:降级处理器,用于接收异常,当资源被降级时,会调用该处理器,返回值类型与原返回值类型一致,参数可以新增Throwable

      • fallbackClass:降级时执行方法存在的类,使用fallback指定对应的方法,需要使用static修饰

      • exceptionsToIgnore:排除的异常,如果抛出的异常在exceptionsToIgnore中,则不会被限流

      • 注意blockHandlerfallback 同时使用时,blockHandler的优先级更高

      • 注意2:自定义blockHandler 方法时,一定要记得添加对应的异常参数,否则无法生效;

  3. 流量控制规则 FlowRule,服务提供端使用

    @PostConstruct
    private static void initFlowRule() {
        ArrayList<FlowRule> rules = new ArrayList<>();
        FlowRule rule = new FlowRule(); // 创建限流规则
        rule.setResource("hello");  //资源名称
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS); //限流等级,分为:QPS(每秒请求数)、线程数
        rule.setCount(1);   // 限流阈值, 即每秒钟最多访问次数, 参数类型为double
        rules.add(rule);
        // 注册限流规则
        FlowRuleManager.loadRules(rules);   // 加载限流规则
    }
    
  4. 熔断降级规则 DegradeRule,服务消费端使用

    @PostConstruct
    private static void initDegradeRule() {
        ArrayList<DegradeRule> rules = new ArrayList<>();
        DegradeRule rule = new DegradeRule();   // 降级规则
        rule.setResource("degrade");  //资源名称
        /*
         * 60秒内,请求次数超过2次,出现2个异常,则降级,调用降级的方法,降级时间为10秒
         * 降级之后会处于"半开"状态:当10秒之后,若再次调用发生一次异常,则将立即触发降级
         */
        rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);  //降级等级:慢调用比例、异常比例、异常数
        rule.setCount(2);   // 异常数超过2次,则降级
        rule.setMinRequestAmount(2); // 触发熔断的最小请求数,参数类型为int
        rule.setStatIntervalMs(60 * 1000); // 统计时长,单位为毫秒,默认1000ms
        rule.setTimeWindow(10); // 触发熔断时长,单位为秒
        rules.add(rule);
        // 注册降级规则
        DegradeRuleManager.loadRules(rules);    // 加载降级规则
    }
    
  5. Sentinel控制台

    1. 版本:依据 Spring Cloud Alibaba 的版本选择,此处为2.3.7,所以下载1.8.1版本的控制台;

    2. 下载jar包:https://github.com/alibaba/Sentinel/releases

    3. cmd启动:java -jar sentinel-dashboard-1.8.1.jar

    4. 访问:默认端口8080,http://localhost:8080/#/login

    5. 登陆:账户密码都为 sentinel

    6. 启动配置修改:

      • 端口:-Dserver.port=8858

      • 账户:-Dsentinel.dashboard.auth.username=sentinel

      • 密码:-Dsentinel.dashboard.auth.password=123456

      • 日志:

        • -Dlogging.file=.\logs\sentinel-dashboard.log
        • -Dcsp.sentinel.log.dir=.\logs\sentinel-dashboard
      • 自定义批处理文件

        • 新建文本文档,输入如下命令,修改文件名称为:start.bat

          title sentinel-dashboard
          
          java -Dlogging.file=.\logs\sentinel-dashboard.log -Dcsp.sentinel.log.dir=.\logs\sentinel-dashboard -Dserver.port=8858 -Dsentinel.dashboard.auth.username=demo -Dsentinel.dashboard.auth.password=demo -jar sentinel-dashboard-1.8.1.jar
          
          pause
          
        • 访问地址:http://localhost:8858/#/login

    7. Sentinel控制台与微服务建立通信

      1. 引入依赖:

        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-transport-simple-http</artifactId>
            <version>1.8.4</version>
        </dependency>
        
      2. 参数配置:

        微服务启动时,配置Sentinel控制台的IP地址及端口号,首次访问接口会注册到控制台内:

        -Dcsp.sentinel.dashboard.server=consoleIp:port

        在这里插入图片描述

        在这里插入图片描述

  6. Spring Cloud Alibaba整合Sentinel:

    1. 添加依赖:

      <dependency>
          <groupId>com.alibaba.cloud</groupId>
          <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
      </dependency>
      
    2. 配置yml参数:

      # 应用名称
      spring:
        application:
          name: sentinel-alibaba
      # sentinel控制台地址
        cloud:
          sentinel:
            transport:
              dashboard: 127.0.0.1:8858
            log:	
           dir: .\logs\sentinel-dashboard	# 设置日志存储位置
      
    3. 用户首次访问路径时,当前服务会自动注册进控制台中;

    4. 自定义限流规则,此时无需再使用@SentinelResource注解一一配置限流方法:

      1. 创建Result类,返回统一的返回值类型;

        public class Result<T> {
            private Integer code;
            private String message;
            private T data;
        
            public Result(Integer code, String message) {
                this.code = code;
                this.message = message;
            }
        
            public static <T> Result<T> error(Integer code, String message) {
                return new Result<T>(code, message);
            }
            // ……省略部分代码
        }
        
      2. 自定义类,实现 BlockExceptionHandler 接口,重写内部方法;

        @Slf4j
        @Component
        public class MyBlockException implements BlockExceptionHandler {
        
            @Override
            public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
                log.error("当前的异常资源路径为:" + e.getRule());
                Result<String> result = null;
                if (e instanceof FlowException) {
                    result = Result.error(500, "限流了,请稍后再试");
                } else if (e instanceof DegradeException) {
                    result = Result.error(500, "降级了,请稍后再试");
                } else if (e instanceof ParamFlowException) {
                    result = Result.error(500, "热点参数限流了,请稍后再试");
                } else if (e instanceof SystemBlockException) {
                    result = Result.error(500, "触发系统保护规则了,请稍后再试");
                } else if (e instanceof AuthorityException) {
                    result = Result.error(500, "权限不足,授权规则不通过");
                }
                // 将结果转换为json格式
                response.setStatus(500);
                response.setCharacterEncoding("UTF-8");
                response.setContentType(MediaType.APPLICATION_JSON_VALUE);
                new ObjectMapper().writeValue(response.getWriter(), result);
            }
        }
        
      3. 测试的Controller:

        @RequestMapping("/myBlockExceptionTest")
        public User myBlockExceptionTest() {
            /*
             * 访问每三次,抛出一个异常,否则返回User对象,
             * 测试自定义的限流类
             */
            count++;
            if (count % 3 == 0) {
                throw new RuntimeException("自定义限流异常");
            }
            return new User("自定义的限流类", 23);
        }
        
      4. 在Seninel控制台配置限流规则;

        在这里插入图片描述

      5. 在Seninel控制台配置降级规则;

        在这里插入图片描述

      6. 注意:微服务每次重新启动时需要再次配置规则,后期可自行做Seninel控制台的持久化配置;

    5. 流控模式介绍:

      在这里插入图片描述

      • 直接:直接限制设置的资源路径,可以是路径名,也可以是方法;

      • 关联:当访问设置的资源路径时,对关联的路径进行限制,而不是设置的路径;

        • 新增方法:

          @RequestMapping("/add")		// 使用Jmeter访问此地址
          public User add() {
              return new User("add", 23);
          }
          
          @RequestMapping("/get")		// 使用浏览器访问此地址
          public User get() {
              return new User("get", 23);
          }
          
        • 新增流控规则:

          在这里插入图片描述

        • 使用Jmeter设置一个 线程组 ,增加 取样器HTTP请求 ,增加 监听器 结果树,如图:

          在这里插入图片描述

          在这里插入图片描述

        • 进入浏览器,访问 http://localhost:8123/get ,这时将会显示服务限流;

      • 链路

        对某一方法做限流时,使用链路,可以针对某一个入口方法(具体路径)做限流,在达到设置阈值时,进限流指定的入口方法,而不影响其他;

        • 创建Service及impl,新增流控的方法:

          @Service
          public class SentinelServiceImpl implements SentinelService {
          
              @Override
              @SentinelResource(value = "getUser", blockHandler = "handleException")
              public String getUser() {
                  return "查询成功";
              }
          
              public String handleException(BlockException e) {
                  return "查询失败";
              }
          }
          
        • Controller方法:

          @RequestMapping("/test1")
          public String test1() {
              return sentinelService.getUser() + "test1";
          }
          
          @RequestMapping("/test2")
          public String test2() {
              return sentinelService.getUser() + "test2";
          }
          
        • yml配置文件添加一项配置:

          spring:
            cloud:
              sentinel:
                web-context-unify: false  # 默认为true,将调用链路context收敛,无法使用链路流控,必须修改为false才能使用,
          
        • Sentinel控制台,任选一个getUser方法的簇点链路,配置流控:

          在这里插入图片描述

          在这里插入图片描述

    6. 流控效果介绍:

      在这里插入图片描述

      1. 快速失败:直接完成流控;
      2. Warm Up(激增流量):预热 / 冷启动,使流量在一定时间内,缓慢增加到阈值上限;
        • 冷加载因子:codeFactor,默认是3,即QPS从threshold/3开始,逐渐升至设定的QPS值。
      3. 排队等待(脉冲流量):不会立即流控,而是设置超时时间,让流控的请求在一定时间内去延迟执行,充分利用系统空闲时间;
    7. 熔断降级策略

      1. 慢调用比例:当服务执行时间超过设置的统计时长,称为一次慢调用。超过设定的比例开始降级;
      2. 异常比例:当服务出现的异常次数超过设定的比例开始降级;
      3. 异常数:当服务出现的异常次数超过设定的次数开始降级;
    8. 热点参数流控:针对REST风格,对访问频率高的参数值进行流控降级;

      在这里插入图片描述

      1. 参数索引:参数在方法中所在位置,从零开始计算;
      2. 参数类型:java参数,四类八种;
      3. 参数值:流控的参数具体值,可添加多个;
    9. 系统保护规则

      在这里插入图片描述

      1. LOAD自适应:仅对Linux等系统生效;
      2. 平均RT:当单台机器的入口流量的平均 RT 达到阈值,则自动触发系统保护规则;
      3. 并发线程数:当单台机器的并发线程数达到阈值,则自动触发系统保护规则;
      4. 入口QPS:当单台机器的入口流量的 QPS 达到阈值,则自动触发系统保护规则;
      5. CPU使用率:当CPU使用率超过阈值,则自动触发系统保护规则;
    10. openFeign整合Sentinel

      1. 依赖,nacos,feign,sentinel:

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        
      2. yml配置文件,开启feign对Sentinel的支持:

        feign:
          sentinel:
            enabled: true
        
      3. 自定义一个异常类,实现feign接口的所在的接口类,并加入Spring容器中;

      4. 在feign的接口类上,声明使用自己的异常类:

        @FeignClient(name = "stock-service", 
                     path = "/stock", 
                     fallback = FeignException.class)
        
    11. Sentinel规则的持久化(此处使用第三种模式:拉模式):

      在这里插入图片描述

      1. 添加三方的依赖:

        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
        </dependency>
        
      2. 在nacos中新增配置如下:

        [
            {
                "resource": "/getUser",	
                "controlBehavior": 0,
                "count": 2,
                "grade": 1,
                "limitApp": "default",
                "strategy": 0,
            }
        ]
        
        • resource:流控的资源路径;
        • count:限流阈值;
        • grade:限流阈值类型,QPS为1,并发线程数为0;
        • limitApp:调用来源,默认default,不区分调用来源;
        • strategy:流控模式,直接、链路、关联;
        • controlBehavior:流控效果, 快速失败、Warm Up、排队等待;
        • clusterMode:是否集群限流
      3. 在application.yml配置文件中新增配置:

        详细配置参考 DataSourcePropertiesConfiguration 配置类

        spring:
          cloud:
            sentinel:
              datasource:   # 源码:DataSourcePropertiesConfiguration配置类
                flow-rule:    # 自定义的名称
                  nacos:    # 可选值:nacos、zk、redis、apollo等等
                    server-addr: localhost:8848   # nacos的ip地址
                    username: nacos   # 用户名
                    password: nacos   # 密码
                    dataId: config-nacos-sentinel-flow-rule   # 配置中心自定义的配置的名称
                    rule-type: flow   # 流控规则,具体查看RuleType枚举类
        

七、分布式事务Seata

  1. Seata官网:https://seata.apache.org/zh-cn/;

  2. 名词解释:

    • TMTransaction Manager,事务管理者。定义全局事务的范围,开始全局事务、提交或回滚;
    • RMResource Manager,资源管理者。管理分支事务处理的资源,与 TC 交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚;
    • TCTransaction Coordinator,事务协调者。维护全局和分支事务的状态,驱动全局事务提交或回滚;
  3. 四种事务模式:

    • XA模式:事务提交至事务协调者TC,由TC统一决定是否提交事务;

    • AT模式:事务直接提交,并记录 undo_log 日志(SQL执行前后各一条),异常后根据日志回滚;

    • TCC模式:自行决定事务的提交回滚策略,指定执行的方法;

    • Saga模式:长事务解决方案,一种分布式异步事务。分两种实现:

      • 状态机引擎:通过事件驱动的方法异步执行提高系统吞吐,可以实现服务编排需求;

      • 基于注解和拦截器:开发简单、学习成本低;

    模式 一致性 隔离性 侵入性 性能 场景
    XA 强一致 完全隔离 对一致性、隔离性有高要求的业务
    AT 弱一致 依赖全局锁隔离 基于关系型数据库的大多数分布式事务场景
    TCC 弱一致 资源预留隔离 prepare
    commit
    cancel
    需编写三个接口
    对性能要求较高的事务。
    有非关系型数据库要参与的事务
    Saga 最终一致 需编写状态机、
    补偿业务
    业务流程长、业务流程多,
    参与者包含其它公司遗留系统服务,
    无法提供 TCC 模式要求的三个接口
  4. 设计问题处理:

    • 脏写:事务回滚前,需校验数据库与记录日志是否相同,如果不同,说明出现了脏写,需人工处理;
    • 允许空回滚:Try拥堵或其它原因未执行,Cancel执行了;
    • 防悬挂控制:Cancel比Try先执行了,即允许空回滚,但要拒绝空回滚后的Try操作;
    • 幂等控制:Try、Commit、Cancel要保证幂等性,即一次请求和多次请求对系统资源的影响是一致的;
  5. 使用Seata(摘自官网):

    • 基本使用:在需要开启分布式事务的方法上添加注解 @GlobalTransactional 即可;

      @GlobalTransactional
      public void purchase(String userId, String commodityCode, int orderCount) {
          ......
      }
      
    • Seata防止脏写:

      • 方法一:

        @GlobalTransactional
        @Transactional
        public boolean updateA(DTO dto) {
            serviceA.update(dto.getA());
        }
        
      • 方法二:

        @GlobalLock
        @Transactional
        public boolean updateA(DTO dto) {
            serviceA.selectForUpdate(dto.getA());
            serviceA.update(dto.getA());
        }
        

| XA | 强一致 | 完全隔离 | 无 | 差 | 对一致性、隔离性有高要求的业务 |
| AT | 弱一致 | 依赖全局锁隔离 | 无 | 好 | 基于关系型数据库的大多数分布式事务场景 |
| TCC | 弱一致 | 资源预留隔离 | prepare
commit
cancel
需编写三个接口 | 优 | 对性能要求较高的事务。
有非关系型数据库要参与的事务 |
| Saga | 最终一致 | 无 | 需编写状态机、
补偿业务 | 优 | 业务流程长、业务流程多,
参与者包含其它公司遗留系统服务,
无法提供 TCC 模式要求的三个接口 |

  1. 设计问题处理:

    • 脏写:事务回滚前,需校验数据库与记录日志是否相同,如果不同,说明出现了脏写,需人工处理;
    • 允许空回滚:Try拥堵或其它原因未执行,Cancel执行了;
    • 防悬挂控制:Cancel比Try先执行了,即允许空回滚,但要拒绝空回滚后的Try操作;
    • 幂等控制:Try、Commit、Cancel要保证幂等性,即一次请求和多次请求对系统资源的影响是一致的;
  2. 使用Seata(摘自官网):

    • 基本使用:在需要开启分布式事务的方法上添加注解 @GlobalTransactional 即可;

      @GlobalTransactional
      public void purchase(String userId, String commodityCode, int orderCount) {
          ......
      }
      
    • Seata防止脏写:

      • 方法一:

        @GlobalTransactional
        @Transactional
        public boolean updateA(DTO dto) {
            serviceA.update(dto.getA());
        }
        
      • 方法二:

        @GlobalLock
        @Transactional
        public boolean updateA(DTO dto) {
            serviceA.selectForUpdate(dto.getA());
            serviceA.update(dto.getA());
        }