SpringCloud

SpringCloud

概述

简介

  • SpringCloud:分布式微服务架构的一站式解决方案,是多种微服务架构落地技术的集合体(微服务全家桶)
  • 版本命名规则:采用伦敦地铁站名称来命名,根据字母表顺序对应版本时间顺序,当发布内容累积到临界点或重大BUG被解决后会发布“service releases”版本,简称SRX版(X为数字)
  • SpringCloud和Springboot之间的依赖关系:https://spring.io/projects/spring-cloud#overview

编码构建

  • 约定 > 配置 > 编码

  • 步骤

    1. 新建project工作空间

      1. 微服务cloud整体聚合父工程Project

        1. maven工程maven-archetype-site
        2. 字符编码
        3. 注解生效激活
        4. 删除src文件夹
      2. 父工程POM

        1. 设置packaging为pom

          1
          2
          3
          4
          5
          6
          <!-- maven坐标 -->
          <groupId>cc.mousse.springcloud</groupId>
          <artifactId>SpringCloud</artifactId>
          <version>1.0-SNAPSHOT</version>
          <!-- 表示为pom父工程 -->
          <packaging>pom</packaging>
        2. 设置统一管理jar包版本

          1
          2
          3
          4
          5
          6
          <!-- 统一管理jar包版本 -->
          <properties>
          <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
          <maven.compiler.source>17</maven.compiler.source>
          <maven.compiler.target>17</maven.compiler.target>
          </properties>
        3. 设置dependencyManagement

          • dependencyManagement:用于父类管理,通常存在于一个组织或最顶层的父POM中,能让所有子项目引用一个依赖而不用显示列出版本号,只声明依赖,不实现引入,子项目需要显示声明依赖,若子项目指定版本号则会使用子项目制定的jar版本
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          27
          28
          29
          30
          31
          32
          33
          34
          35
          36
          37
          38
          39
          40
          41
          42
          43
          44
          45
          46
          47
          48
          49
          50
          51
          52
          53
          54
          55
          56
          57
          58
          59
          60
          61
          62
          63
          64
          65
          66
          67
          68
          69
          70
          71
          72
          73
          74
          75
          76
          77
          78
          79
          80
          81
          82
          83
          <!-- 作用: 子模块继承后锁定版本并且子module不用写groupId和version -->
          <dependencyManagement>
          <dependencies>
          <!-- 引入SpringBoot基本依赖 -->
          <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-dependencies</artifactId>
          <version>${springboot.version}</version>
          <type>pom</type>
          <scope>import</scope>
          </dependency>

          <!-- 引入SpringCloud基本依赖 -->
          <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-dependencies</artifactId>
          <version>${springcloud.version}</version>
          <type>pom</type>
          <scope>import</scope>
          </dependency>

          <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
          <version>${springboot.version}</version>
          </dependency>

          <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-actuator</artifactId>
          <version>${springboot.version}</version>
          </dependency>

          <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-test</artifactId>
          <version>${springboot.version}</version>
          </dependency>

          <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-jdbc</artifactId>
          <version>${springboot.version}</version>
          </dependency>

          <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-devtools</artifactId>
          <version>${springboot.version}</version>
          </dependency>

          <dependency>
          <groupId>org.mybatis.spring.boot</groupId>
          <artifactId>mybatis-spring-boot-starter</artifactId>
          <version>${mybatis.version}</version>
          </dependency>

          <dependency>
          <groupId>org.apache.logging.log4j</groupId>
          <artifactId>log4j-core</artifactId>
          <version>${log4j.version}</version>
          </dependency>

          <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
          <version>${lombok.version}</version>
          <optional>true</optional>
          </dependency>

          <dependency>
          <groupId>com.alibaba</groupId>
          <artifactId>druid-spring-boot-starter</artifactId>
          <version>${druid.version}</version>
          </dependency>

          <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
          <version>${mysql.version}</version>
          </dependency>
          </dependencies>
          </dependencyManagement>
    2. Rest微服务工程构建

      1. 创建新模块并配置pom文件

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        <?xml version="1.0" encoding="UTF-8"?>
        <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>

        <parent>
        <groupId>cc.mousse.springcloud</groupId>
        <artifactId>SpringCloud</artifactId>
        <version>1.0-SNAPSHOT</version>
        </parent>

        <artifactId>cloud-provider-payment8001</artifactId>
        <name>cloud-provider-payment8001</name>
        <description>cloud-provider-payment8001</description>

        <dependencies>
        <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>

        <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        </dependency>

        <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
        </dependency>

        <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        </dependency>
        </dependencies>
        </project>
      2. 配置yaml

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        server:
        port: 8001

        spring:
        application:
        name: cloud-payment-service
        datasource:
        druid:
        url: jdbc:mysql://localhost:3306/springcloud?useUnicode=true&characterEncoding=utf-8&useSSL=false
        driver-class-name: com.mysql.cj.jdbc.Driver
        username: root
        password: root

        mybatis:
        mapper-locations: classpath:mapper/*.xml
        # 所有Entity别名类所在包
        type-aliases-package: cc.mousse.springcloud.entities
      3. 业务类

热部署Devtools

  1. 在pom中添加jar包

    1
    2
    3
    4
    5
    6
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
    </dependency>
  2. 在总pom中添加插件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <build>
    <!-- 单一工程时添加 -->
    <!-- <finalName>你的工程名</finalName> -->
    <plugins>
    <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
    <fork>true</fork>
    <addResources>true</addResources>
    </configuration>
    </plugin>
    </plugins>
    </build>
  3. 开启自动编译选项

RestTemplate

  • 提供多种便捷访问远程http服务的方法,是一种简单便捷的访问restful服务的模板类,是Spring提供用于访问Rest服务的客户端模板工具集

  • config配置类ApplicationContextConfig

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Configuration
    public class ApplicationContextConfig {

    @Bean
    public RestTemplate getRestTemplate() {
    return new RestTemplate();
    }

    }
  • 业务类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    @Slf4j
    @RestController
    @RequestMapping("/consumer/payment")
    public class OrderController {

    private static final String PAYMENT_URL = "http://localhost:8001";

    @Resource
    private RestTemplate restTemplate;

    // 客户端都发GET请求
    @GetMapping("/create")
    // Order80发送给Payment8001的是JSON数据,要在Payment8001使用@RequestBody注解才能生效
    public CommonResult<Payment> create(@RequestBody Payment payment) {
    return restTemplate.postForObject(PAYMENT_URL + "/payment/create", payment, CommonResult.class);
    }

    @GetMapping("/get/{id}")
    public CommonResult<Payment> getPayment(@PathVariable("id") Long id) {
    return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
    }

    }

工程重构

  • 重构系统中重复部分

  • 新建cloud-api-commons

    1. pom

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      <?xml version="1.0" encoding="UTF-8"?>
      <project xmlns="http://maven.apache.org/POM/4.0.0"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      <parent>
      <artifactId>SpringCloud</artifactId>
      <groupId>cc.mousse.springcloud</groupId>
      <version>1.0-SNAPSHOT</version>
      </parent>
      <modelVersion>4.0.0</modelVersion>

      <artifactId>cloud-api-commons</artifactId>

      <properties>
      <maven.compiler.source>17</maven.compiler.source>
      <maven.compiler.target>17</maven.compiler.target>
      </properties>


      <!-- 公共依赖 -->
      <dependencies>
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-devtools</artifactId>
      <scope>runtime</scope>
      <optional>true</optional>
      </dependency>

      <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
      </dependency>

      <dependency>
      <groupId>cn.hutool</groupId>
      <artifactId>hutool-all</artifactId>
      <version>5.7.19</version>
      </dependency>
      </dependencies>
      </project>
    2. 移动entities

    3. 改造Order80和Payment8001

      1. 删除各自的entities

      2. 更新pom文件

        1
        2
        3
        4
        5
        <dependency>
        <groupId>cc.mousse.springcloud</groupId>
        <artifactId>cloud-api-commons</artifactId>
        <version>${project.version}</version>
        </dependency>

组件

服务注册中心

✖️Eureka

概述

  • 管理服务于服务之间依赖关系,可以实现服务调用、负载均衡、容错等,实现服务发现与注册

  • 服务注册:将服务信息注册进注册中心

  • 服务发现:从注册中心上获取服务信息

  • 实质:存key服务命取value闭用地址

  • ap

  • 步骤:

    1. 先启动eureka注主册中心
    2. 启动服务提供者payment支付服务
    3. 支付服务启动后会把自身信息(比服务地址以别名方式注册进eureka)
    4. 消费者order服务在需要调用接口时,使用服务别名去注册中心获取实际的RPC远程调用地址
    5. 消去者导调用地址后,底屋实际是利用HttpClient技术实现远程调用
    6. 消费者实癸导服务地址后会缓存在本地jvm内存中,默认每间隔30秒更新一次服务调用地址
  • 启动顺序:

    1. Eureka
    2. 客户端
  • 服务者

    1. 创建名为cloud-eureka-server7001的Maven工程

    2. pom

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      <?xml version="1.0" encoding="UTF-8"?>
      <project xmlns="http://maven.apache.org/POM/4.0.0"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      <parent>
      <artifactId>SpringCloud</artifactId>
      <groupId>cc.mousse.springcloud</groupId>
      <version>1.0-SNAPSHOT</version>
      </parent>
      <modelVersion>4.0.0</modelVersion>

      <artifactId>cloud-eureka-server7001</artifactId>

      <properties>
      <maven.compiler.source>17</maven.compiler.source>
      <maven.compiler.target>17</maven.compiler.target>
      </properties>

      <dependencies>
      <!--eureka-server-->
      <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
      <version>3.1.0</version>
      </dependency>

      <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
      <dependency>
      <groupId>com.lun.springcloud</groupId>
      <artifactId>cloud-api-commons</artifactId>
      <version>${project.version}</version>
      </dependency>

      <!-- boot web actuator -->
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      </dependency>

      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
      </dependency>

      <!-- 一般通用配置 -->
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-devtools</artifactId>
      <scope>runtime</scope>
      <optional>true</optional>
      </dependency>

      <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      </dependency>

      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
      </dependency>

      <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      </dependency>
      </dependencies>
      </project>
    3. application.yaml

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      server:
      port: 7001

      eureka:
      instance:
      # eureka服务者的实例名称
      hostname: locathost
      client:
      # false表示不向注册中心注册自己。
      register-with-eureka: false
      # false表示自己端就是注册中心,职责就是维护服务实例,并不需要去检索服务
      fetch-registry: false
      service-url:
      # 设置与Eureka server交互的地址查询服务和注册服务都需要依赖这个地址。
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
    4. 主启动

      1
      2
      3
      4
      5
      6
      7
      8
      @EnableEurekaServer
      @SpringBootApplication
      public class EurekaApplication7001 {

      public static void main(String[] args) {
      SpringApplication.run(EurekaApplication7001.class, args);
      }
      }
    5. 浏览器输入http://localhost:7001/回车,会查看到Spring Eureka服务主页

  • 消费者

    1. pom

      1
      2
      3
      4
      <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
      </dependency>
    2. yaml

      1
      2
      3
      4
      5
      6
      7
      8
      9
      # 主页内的Instances currently registered with Eureka会显示cloud-provider-payment8001的配置文件application.yml设置的应用名cloud-payment-service
      eureka:
      client:
      # 表示是否将自己注册进Eurekaserver默认为true
      register-with-eureka: true
      # 是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
      fetchRegistry: true
      service-url:
      defaultZone: http://localhost:7001/eureka
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      # cloud-consumer-order80
      server:
      port: 80

      spring:
      application:
      name: cloud-order-service

      eureka:
      client:
      #表示是否将自己注册进Eurekaserver默认为true
      register-with-eureka: true
      #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
      fetchRegistry: true
      service-url:
      defaultZone: http://localhost:7001/eureka
    3. 主启动

      1
      2
      3
      4
      5
      6
      7
      8
      9
      @EnableEurekaClient
      @SpringBootApplication
      public class PaymentApplication8001 {

      public static void main(String[] args) {
      SpringApplication.run(PaymentApplication8001.class, args);
      }

      }
  • 集群

    • 实现负载均衡+故障容错(互相注册,相互守望

    • Eureka yaml

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      server:
      port: 7001

      eureka:
      instance:
      # eureka服务者的实例名称
      hostname: locathost
      client:
      # false表示不向注册中心注册自己。
      register-with-eureka: false
      # false表示自己端就是注册中心,职责就是维护服务实例,并不需要去检索服务
      fetch-registry: false
      service-url:
      # 设置与Eureka server交互的地址查询服务和注册服务都需要依赖这个地址
      # 集群指向其它eureka
      defaultZone: http://localhost:7002/eureka/
      # 单机就是7001自己
      # defaultZone: http://eureka7001.com:7001/eureka/
    • 客户端 yaml

      1
      2
      3
      4
      5
      6
      7
      8
      9
      eureka:
      client:
      # 表示是否将自己注册进Eurekaserver默认为true
      register-with-eureka: true
      # 是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
      fetchRegistry: true
      service-url:
      # 注册集群
      defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka
    • cloud-consumer-order80订单服务访问地址

      1
      private static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";
    • 使用@LoadBalanced注解赋予RestTemplate负载均衡的能力

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      @Configuration
      public class ApplicationContextConfig {

      @Bean
      // 赋予RestTemplate负载均衡的能力
      @LoadBalanced
      public RestTemplate getRestTemplate() {
      return new RestTemplate();
      }

      }
  • actuator微服务信息完善

    • 主机名称:服务名称修改(也就是将IP地址,换成可读性高的名字)

    • 访问信息有IP信息提示,(就是将鼠标指针移至payment8001,payment8002名下,会有IP地址提示)

      1
      2
      3
      4
      5
      6
      7
      eureka:
      ...
      instance:
      # 服务名称修改
      instance-id: payment8002
      # IP信息提示
      prefer-ip-address: true
  • 服务发现Discovery

    • 对于注册进eureka里面的微服务,可以通过服务发现来获得该服务的信息

    • 修改cloud-provider-payment8001的Controller

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      @Slf4j
      @RestController
      @RequestMapping(value = "/payment")
      public class PaymentController {

      ...

      @Resource
      private DiscoveryClient discoveryClient;

      ...

      @GetMapping(value = "/discovery")
      public Object discovery() {
      // 获取微服务信息
      List<String> services = discoveryClient.getServices();
      for (String element : services) log.info("element: " + element);
      // 获取某一微服务的信息
      List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
      for (ServiceInstance instance : instances) {
      log.info("instance: {}\t{}\t{}\t{}", instance.getServiceId(), instance.getHost(), instance.getPort(), instance.getUri());
      }
      return this.discoveryClient;
      }

      }
    • 8001主启动类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      @EnableEurekaClient
      @EnableDiscoveryClient
      @SpringBootApplication
      public class PaymentApplication8001 {

      public static void main(String[] args) {
      SpringApplication.run(PaymentApplication8001.class, args);
      }

      }
  • 自我保护

    • 如果在Eureka Server的首页看到以下这段提示,则说明Eureka进入了保护模式

      • EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY’RE NOT. RENEWALS ARE LESSER THANTHRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUSTTO BE SAFE
    • 导致原因

      • 某时刻某一个微服务不可用了,Eureka不会立刻清理,依旧会对该微服务的信息进行保存
      • 属于CAP里面的AP分支
      • 当EurekaServer节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式
    • 自我保护机制

      • 默认情况下EurekaClient定时向EurekaServer端发送心跳包

      • 使用可以禁用自我保护模式

        1
        2
        3
        4
        5
        6
        7
        eureka:
        ...
        server:
        # 关闭自我保护机制,保证不可用服务被及时踢除
        enable-self-preservation: false
        # 设置间隔时间
        eviction-interval-timer-in-ms: 2000
      • 生产者客户端eureakeClient端8001

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        eureka:
        ...
        instance:
        instance-id: payment8001
        prefer-ip-address: true
        # 心跳检测与续约时间
        # 开发时没置小些,保证服务关闭后注册中心能即使剔除服务
        # Eureka客户端向服务者发送心跳的时间间隔,单位为秒(默认是30秒)
        lease-renewal-interval-in-seconds: 1
        # Eureka服务者在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
        lease-expiration-duration-in-seconds: 2

Zookeeper

  • 概述

    • 默认临时节点
    • cp
  • 服务者

    • pom

      1
      2
      3
      4
      5
      6
      <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-zookeeper-discovery -->
      <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
      <version>3.1.0</version>
      </dependency>
    • yaml

      1
      2
      3
      4
      5
      6
      spring:
      application:
      name: cloud-provider-payment
      cloud:
      zookeeper:
      connect-string: 127.0.0.1:2181
    • 主启动

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      // 该注解用于向使用consul或者zookeeper作为注册中心时注册服务
      @EnableDiscoveryClient
      @SpringBootApplication
      public class PaymentApplication8004 {

      public static void main(String[] args) {
      SpringApplication.run(PaymentApplication8004.class, args);
      }

      }
    • 业务类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      @Slf4j
      @RestController
      @RequestMapping(value = "/payment")
      public class PaymentController {

      @Value("${server.port}")
      private String serverPort;

      @RequestMapping("/zk")
      public String paymentZk() {
      return "springcloud with zookeeper: " + serverPort + "\t" + UUID.randomUUID().toString();
      }

      }
    • 详细信息

      1
      2
      [zk: localhost:2181(CONNECTED) 6] get /services/cloud-payment-service/40d507b9-17a5-4bc0-beee-154b45b31728
      {"name":"cloud-payment-service","id":"40d507b9-17a5-4bc0-beee-154b45b31728","address":"moussedembp","port":8004,"sslPort":null,"payload":{"@class":"org.springframework.cloud.zookeeper.discovery.ZookeeperInstance","id":"cloud-payment-service","name":"cloud-payment-service","metadata":{"instance_status":"UP"}},"registrationTimeUTC":1642773581223,"serviceType":"DYNAMIC","uriSpec":{"parts":[{"value":"scheme","variable":true},{"value":"://","variable":false},{"value":"address","variable":true},{"value":":","variable":false},{"value":"port","variable":true}]}}
  • 消费者

    • pom/yaml/主启动同服务者

    • 业务类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      @Configuration
      public class ApplicationContextConfig {

      @Bean
      // 赋予RestTemplate负载均衡的能力
      @LoadBalanced
      public RestTemplate getRestTemplate() {
      return new RestTemplate();
      }

      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      @Slf4j
      @RestController
      @RequestMapping("/consumer/payment")
      public class OrderController {

      private static final String INVOKE_URL = "http://cloud-provider-payment";

      @Resource
      private RestTemplate restTemplate;

      @GetMapping(value = "/zk")
      public String paymentInfo() {
      return restTemplate.getForObject(INVOKE_URL + "/payment/zk", String.class);
      }

      }

Consul

  • 概述

    • Consul是一套开源的分布式服务发现和配置管理系统,由HashiCorp 公司用Go语言开发
      • 服务发现 - 提供HTTP和DNS两种发现方式。
      • 健康监测 - 支持多种方式,HTTP、TCP、Docker、Shell脚本定制化
      • KV存储 - Key、Value的存储方式
      • 多数据中心 - Consul支持多数据中心
      • 可视化Web界面:8500
  • 命令

    • 查看版本:consul -v
    • 开发模式启动:consul agent -dev
  • 服务者

    • pom

      1
      2
      3
      4
      5
      <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-consul-discovery</artifactId>
      <version>3.1.0</version>
      </dependency>
    • yaml

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      spring:
      cloud:
      consul:
      host: localhost
      port: 8500
      discovery:
      heartbeat:
      # 不加这个会显示Critical
      enabled: true
      service-name: ${spring.application.name}
    • 主启动类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      @EnableDiscoveryClient
      @SpringBootApplication
      public class PaymentApplication8006 {

      public static void main(String[] args) {
      SpringApplication.run(PaymentApplication8006.class, args);
      }

      }
    • 业务类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      @Slf4j
      @RestController
      @RequestMapping(value = "/payment")
      public class PaymentController {

      @Value("${server.port}")
      private String serverPort;

      @RequestMapping("/consul")
      public String paymentZk() {
      return "springcloud with consul: " + serverPort + "\t" + UUID.randomUUID();
      }

      }
  • 消费者

    • pom/yaml/主启动类基本同上

    • 配置类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      @Configuration
      public class ApplicationContextConfig {

      @Bean
      // 赋予RestTemplate负载均衡的能力
      @LoadBalanced
      public RestTemplate getRestTemplate() {
      return new RestTemplate();
      }

      }
    • 业务类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      @Slf4j
      @RestController
      @RequestMapping("/consumer/payment")
      public class OrderController {

      private static final String INVOKE_URL = "http://cloud-provider-payment";

      @Resource
      private RestTemplate restTemplate;

      @GetMapping(value = "/consul")
      public String paymentInfo() {
      return restTemplate.getForObject(INVOKE_URL + "/payment/consul", String.class);
      }


      }

总结

组件名 语言CAP 服务健康检查 对外暴露接口 Spring Cloud集成
Eureka Java AP 可配支持 HTTP
Consul Go CP 支持 HTTP/DNS
Zookeeper Java CP 支持客户端 已集成
  • CAP
    • C:Consistency,强一致性
    • A:Availability,可用性
    • P:Partition tolerance,分区容错性
    • CAP理论的核心:最多只能同时较好的满足两个,根据CAP原理将NoSQL数据库分成了满足CA/CP/AP原则三大类:
      • CA:单点集群,满足一致性可用性的系统,扩展性不强
      • CP:满足一致性,分区容忍必的系统,性能不高
      • AP:满足可用性,分区容忍性的系统,一致性要求低

✨Nacos

  • Cloud Alibaba篇

服务调用

✖️Ribbon

  • Eureka3.1.0已弃用Ribbon

  • 概述

    • 基于Netflix Ribbon实现的一套客户端负载均衡的工具

    • 在配置文件中列出Load Balancer后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。使我们很容易使用Ribbon实现自定义的负载均衡算法

    • 负载均衡 + RestTemplate调用

      • Ribbon其实就是一个软负载均衡的客户端组件,它可以和其他所需请求的客户端结合使用,和Eureka结合只是其中的一个实例
    • 流程

      1. 选择EurekaServer ,它优先选择在同一个区域内负载较少的server
      2. 根据用户指定的策略,在从server取到的服务注册列表中选择一个地址
    • pom

      1
      2
      3
      4
      5
      <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
      <version>2.2.10.RELEASE</version>
      </dependency>
  • LoadBalancer

    • 负载均衡
    • Ribbon本地负载均衡客户端与Nginx服务端负载均衡区别
      • Nginx:服务器负载均衡(集中式LB),客户端所有请求都会交给nginx,然后由nginx实现转发请求。负载均衡是由服务端实现的
      • Ribbon:本地负载均衡(进程内LB),在调用微服务接口时候,会在注册中心上获取注册信息服务列表之后缓存到JVM本地,从而在本地实现RPC远程服务调用技术
  • RestTemplate

  • get/postForObject():返回对象为响应体中数据转化成的对象,基本上可以理解为Json

    1
    2
    3
    4
    @GetMapping("/get/{id}")
    public CommonResult<Payment> getPayment(@PathVariable("id") Long id) {
    return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
    }
  • get/postForEntity():返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头、响应状态码、响应体等

    1
    2
    3
    4
    5
    6
    @GetMapping("/get/{id}")
    public CommonResult<Payment> getPayment(@PathVariable("id") Long id) {
    ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
    if (entity.getStatusCode().is2xxSuccessful()) return entity.getBody();
    else return new CommonResult<>(400, "获取失败");
    }
  • 默认自带的负载规则

    • RoundRobinRule:轮询
    • RandomRule:随机
    • RetryRule:先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内会进行重试
    • WeightedResponseTimeRule:对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择
    • BestAvailableRule:先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
    • AvailabilityFilteringRule:先过滤掉故障实例,再选择并发较小的实例
    • ZoneAvoidanceRule:默认规则,复合判断server所在区域的性能和server**的可用性选择服务器
  • 负载规则替换

    • 自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下,否则自定义的配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的(不要将Ribbon配置类与主启动类同包

    • 配置类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      package cc.mousse.myrule;

      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      import com.netflix.loadbalancer.*;

      @Configuration
      public class MySelfRule {

      @Bean
      public IRule myRule() {
      // 定义为随机策略
      return new RandomRule();
      }

      }
    • 主启动类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      @EnableEurekaClient
      @SpringBootApplication
      @RibbonClient(name = "CLOUD-PAYMENT-SERVICE", configuration = MySelfRule.class)
      public class OrderApplication80 {

      public static void main(String[] args) {
      SpringApplication.run(OrderApplication80.class, args);
      }

      }
  • 默认负载轮询算法原理

    • 默认负载轮训算法:rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标,每次服务重启动后rest接口计数从1开始

OpenFeign

  • 概述

    • ✖️Feigin
      • 声明式WebService客户端,使用Feign能让编写Web Service客户端更加简单。它的使用方法是定义一个服务接口然后在上面添加注解,简化了使用Spring cloud Ribbon时自动封装服务调用客户端的开发量
      • Feign集成了Ribbon
    • OpenFeign
      • Spring Cloud在Feign的基础上支持了SpringMVC的注解
      • 接口 + 注解:微服务调用接口 + @FeignClient
  • 消费端

    • pom

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
      <version>3.1.0</version>
      </dependency>

      <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign -->
      <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-openfeign</artifactId>
      <version>3.1.0</version>
      </dependency>
    • yaml

      1
      2
      3
      4
      5
      6
      7
      8
      9
      eureka:
      client:
      #表示是否将自己注册进Eurekaserver默认为true
      register-with-eureka: true
      #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
      fetchRegistry: true
      service-url:
      # 注册集群
      defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka
    • 主启动类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      @EnableFeignClients
      @EnableEurekaClient
      @SpringBootApplication
      //@RibbonClient(name = "CLOUD-PAYMENT-SERVICE", configuration = MySelfRule.class)
      public class OrderFeignApplication80 {

      public static void main(String[] args) {
      SpringApplication.run(OrderFeignApplication80.class, args);
      }

      }
    • 业务类

      • service

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        @Service
        @FeignClient(value = "CLOUD-PAYMENT-SERVICE", path = "/payment")
        public interface PaymentService {

        @GetMapping("/get/{id}")
        CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);

        @GetMapping("/timeout")
        String timeout();

        }
      • controller

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        @Slf4j
        @RestController
        @RequestMapping("/consumer/payment")
        public class OrderController {

        @Resource
        private PaymentService paymentService;

        @GetMapping("/get/{id}")
        public CommonResult<Payment> getPayment(@PathVariable("id") Long id) {
        return paymentService.getPaymentById(id);
        }

        }
  • 超时控制

    • 服务提供方8001/8002故意写暂停程序

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      @GetMapping("/timeout")
      public String timeout() {
      // 暂停几秒钟线程
      try {
      TimeUnit.SECONDS.sleep(3);
      } catch (Exception e) {
      e.printStackTrace();
      }
      return serverPort;
      }
    • 服务消费方80添加超时方法PaymentFeignService

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      @Service
      @FeignClient(value = "CLOUD-PAYMENT-SERVICE", path = "/payment")
      public interface PaymentService {

      @GetMapping("/get/{id}")
      CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);

      @GetMapping("/timeout")
      String timeout();

      }
    • 服务消费方80添加超时方法OrderFeignController

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      @Slf4j
      @RestController
      @RequestMapping("/consumer/payment")
      public class OrderController {

      @GetMapping("/timeout")
      public String timeout() {
      // OpenFeign客户端一般默认等待1秒钟
      return paymentService.timeout();
      }
      }
    • YML文件里需要开启OpenFeign客户端超时控制(新版没有超时控制

      1
      2
      3
      4
      5
      6
      # 设置feign客户端超时时间(OpenFeign默认支持ribbon)(单位:毫秒)
      ribbon:
      # 建立连接后从服务器读取到可用资源所用的时间
      ReadTimeout: 5000
      # 建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
      ConnectTimeout: 5000
  • 日志打印功能

    • 可以通过配置来调整日志级别了解Feign中http请求的细节(对Feign接口的调用情况进行监控和输出)

      • NONE:默认的,不显示任何日志
      • BASIC:仅记录请求方法、URL、响应状态码及执行时间
      • HEADERS:除了BASIC中定义的信息之外,还有请求和响应的头信息
      • FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据
    • 配置日志bean

      1
      2
      3
      4
      5
      6
      7
      8
      9
      @Configuration
      public class FeignConfig {

      @Bean
      Logger.Level feignLoggerLevel() {
      return Logger.Level.FULL;
      }

      }
    • yaml

      1
      2
      3
      4
      logging:
      level:
      # feign日志以什么级别监控哪个接口
      cc.mousse.springcloud.service.PaymentService: debug

服务降级

  • 服务雪崩
    • 多个微服务之间调用时,微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”
    • 对于高流量的应用来说,单一的后避依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统
    • 发现一个模块下的某个实例失败后,这时候这个模块依然还会接收流量,然后这个有问题的模块还调用了其他的模块,这样就会发生级联故障,或者叫雪崩

✖️Hystrix

  • 停更

  • 概述

    • 用于处理分布式系统的延迟容错的开源库,能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性
    • 断路器:向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常
    • 作用
      • 服务降级
      • 服务熔断
      • 接近实对的监控
  • 重要概念

    • 服务降级fallback:“服务器忙,请稍后再试”,不让客户端等待并立刻返回一个友好提示
      • 发出降级的情况
        • 程序运行导常
        • 超时
        • 服务熔断触发服务降级
        • 线程池/信号量打满也会导致服务降级
    • 服务熔断break:达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示
      • 服务的降级 → 进而熔断 → 恢复调用链路
    • 服务限流flowlimit:秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行
  • 支付微服务构建

    • 新建cloud-provider-hygtrix-payment8001

      • pom

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        <!--hystrix-->
        <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <!--eureka client-->
        <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
      • yaml

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        server:
        port: 8001

        spring:
        application:
        name: cloud-provider-hystrix-payment

        eureka:
        client:
        register-with-eureka: true
        fetch-registry: true
        service-url:
        #defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
        defaultZone: http://eureka7001.com:7001/eureka
      • 主启动类

        1
        2
        3
        4
        5
        6
        7
        8
        9
        @SpringBootApplication
        @EnableEurekaClient
        public class PaymentHystrixMain8001 {

        public static void main(String[] args) {
        SpringApplication.run(PaymentHystrixMain8001.class, args);
        }

        }
      • 业务类

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        @Service
        public class PaymentService {
        /**
        */
        public String paymentInfo_OK(Integer id)
        {
        return "线程池: "+Thread.currentThread().getName()+" paymentInfo_OK,id: "+id+"\t"+"O(∩_∩)O哈哈~";
        }

        public String paymentInfo_TimeOut(Integer id)
        {
        try { TimeUnit.MILLISECONDS.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); }
        return "线程池: "+Thread.currentThread().getName()+" id: "+id+"\t"+"O(∩_∩)O哈哈~"+" 耗时(秒): 3";
        }
        }
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        @RestController
        @Slf4j
        public class OrderHystirxController {
        @Resource
        private PaymentHystrixService paymentHystrixService;


        @GetMapping("/consumer/payment/hystrix/timeout/{id}")
        @HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = {
        @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="1500")
        })
        public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
        //int age = 10/0;
        String result = paymentHystrixService.paymentInfo_TimeOut(id);
        return result;
        }

        //善后方法
        public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id){
        return "我是消费者80,对方支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,o(╥﹏╥)o";
        }

        }
    • 新建cloud-consumer-feign-hystrix-order80

      • pom

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        <!--openfeign-->
        <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!--hystrix-->
        <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <!--eureka client-->
        <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
      • yaml

        1
        2
        3
        4
        5
        6
        7
        8
        server:
        port: 80

        eureka:
        client:
        register-with-eureka: false
        service-url:
        defaultZone: http://eureka7001.com:7001/eureka/
      • 主启动类

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        @SpringBootApplication
        @EnableFeignClients
        //@EnableHystrix
        public class OrderHystrixMain80
        {
        public static void main(String[] args)
        {
        SpringApplication.run(OrderHystrixMain80.class,args);
        }
        }
      • 业务类

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        @Component
        @FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT" /*,fallback = PaymentFallbackService.class*/)
        public interface PaymentHystrixService
        {
        @GetMapping("/payment/hystrix/ok/{id}")
        public String paymentInfo_OK(@PathVariable("id") Integer id);

        @GetMapping("/payment/hystrix/timeout/{id}")
        public String paymentInfo_TimeOut(@PathVariable("id") Integer id);
        }
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        @RestController
        @Slf4j
        public class OrderHystirxController {
        @Resource
        private PaymentHystrixService paymentHystrixService;

        @GetMapping("/consumer/payment/hystrix/ok/{id}")
        public String paymentInfo_OK(@PathVariable("id") Integer id)
        {
        String result = paymentHystrixService.paymentInfo_OK(id);
        return result;
        }

        @GetMapping("/consumer/payment/hystrix/timeout/{id}")
        public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
        String result = paymentHystrixService.paymentInfo_TimeOut(id);
        return result;
        }
        }
  • 务降级支付侧fallback

    • 设置自身调用超时时间的峰值,峰值内可以正常运行,超过了需要有兜底的方法处埋,作服务降级fallback

    • 8001fallback

    • @HystrixCommand:一旦调用服务方法失败并抛出了错误信息后,会自动调用@HystrixCommand标注好的fallbackMethod调用类中的指定方法

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      @Service
      public class PaymentService{

      @HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler"/*指定善后方法名*/,commandProperties = {
      @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="3000")
      })
      public String paymentInfo_TimeOut(Integer id)
      {
      //int age = 10/0;
      try { TimeUnit.MILLISECONDS.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); }
      return "线程池: "+Thread.currentThread().getName()+" id: "+id+"\t"+"O(∩_∩)O哈哈~"+" 耗时(秒): ";
      }

      //用来善后的方法
      public String paymentInfo_TimeOutHandler(Integer id)
      {
      return "线程池: "+Thread.currentThread().getName()+" 8001系统繁忙或者运行报错,请稍后再试,id: "+id+"\t"+"o(╥﹏╥)o";
      }

      }
    • @EnableCircuitBreaker

      1
      2
      3
      4
      5
      6
      7
      8
      @SpringBootApplication
      @EnableEurekaClient
      @EnableCircuitBreaker//添加到此处
      public class PaymentHystrixMain8001{
      public static void main(String[] args) {
      SpringApplication.run(PaymentHystrixMain8001.class, args);
      }
      }
  • 服务降级订单侧fallback

    • yaml

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      server:
      port: 80

      eureka:
      client:
      register-with-eureka: false
      service-url:
      defaultZone: http://eureka7001.com:7001/eureka/

      #开启
      feign:
      hystrix:
      enabled: true
    • 主启动类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      @SpringBootApplication
      @EnableFeignClients
      @EnableHystrix//添加到此处
      public class OrderHystrixMain80{

      public static void main(String[] args){
      SpringApplication.run(OrderHystrixMain80.class,args);
      }
      }
    • 业务类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      @RestController
      @Slf4j
      public class OrderHystirxController {
      @Resource
      private PaymentHystrixService paymentHystrixService;


      @GetMapping("/consumer/payment/hystrix/timeout/{id}")
      @HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = {
      @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="1500")
      })
      public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
      //int age = 10/0;
      String result = paymentHystrixService.paymentInfo_TimeOut(id);
      return result;
      }

      //善后方法
      public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id){
      return "我是消费者80,对方支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,o(╥﹏╥)o";
      }


  • 全局服务降级DefaultProperties

    • 避免代码膨胀

    • @DefaultProperties(defaultFallback = “”)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      @RestController
      @Slf4j
      @DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
      public class OrderHystirxController {
      @Resource
      private PaymentHystrixService paymentHystrixService;

      @GetMapping("/consumer/payment/hystrix/timeout/{id}")
      @HystrixCommand//用全局的fallback方法
      public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
      //int age = 10/0;
      String result = paymentHystrixService.paymentInfo_TimeOut(id);
      return result;
      }
      public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id)
      {
      return "我是消费者80,对方支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,o(╥﹏╥)o";
      }

      // 下面是全局fallback方法
      public String payment_Global_FallbackMethod()
      {
      return "Global异常处理信息,请稍后再试,/(ㄒoㄒ)/~~";
      }
      }
  • 通配服务降级FeignFallback

    • 本次案例服务降级处理是在客户端80实现完成的,与服务端8001没有关系,只需要为Feign客户端定义的接口添加一个服务降级处理的实现类即可实现解耦

    • 根据cloud-consumer-feign-hystrix-order80已经有的PaymentHystrixService接口,重新新建一个类(AaymentFallbackService)实现该接口,统一为接口里面的方法进行异常处理

    • @Component
      // PaymentFallbackService类实现PaymentHystrixService接口
      public class PaymentFallbackService implements PaymentHystrixService
      {
          @Override
          public String paymentInfo_OK(Integer id)
          {
              return "-----PaymentFallbackService fall back-paymentInfo_OK ,o(╥﹏╥)o";
          }
      
          @Override
          public String paymentInfo_TimeOut(Integer id)
          {
              return "-----PaymentFallbackService fall back-paymentInfo_TimeOut ,o(╥﹏╥)o";
          }
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17

      - yaml

      ```java
      server:
      port: 80

      eureka:
      client:
      register-with-eureka: false
      service-url:
      defaultZone: http://eureka7001.com:7001/eureka/

      #开启
      feign:
      hystrix:
      enabled: true
    • PaymentHystrixService接口

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      @Component
      @FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT" ,//
      fallback = PaymentFallbackService.class)//指定PaymentFallbackService类
      public interface PaymentHystrixService
      {
      @GetMapping("/payment/hystrix/ok/{id}")
      public String paymentInfo_OK(@PathVariable("id") Integer id);

      @GetMapping("/payment/hystrix/timeout/{id}")
      public String paymentInfo_TimeOut(@PathVariable("id") Integer id);
      }
  • 服务熔断

    • 是应对雪崩效应的一种微服务链路保护机制。当扇出链路的某个微服务出错不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。当检测到该节点微服务调用响应正常后,恢复调用链路

    • 在Spring Cloud框架里,熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是5秒内20次调用失败,就会启动熔断机制。熔断机制的注解是@HystrixCommand

    • 熔断类型

      • 熔断打开:请求不再进行调用当前服务,内部设置时钟一般为MTTR(平均故障处理时间),当打开时长达到所设时钟则进入半熔断状态
      • 熔断关闭:熔断关闭不会对服务进行熔断
      • 熔断半开:部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断
    • 重要参数

      • 快照时间窗:断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的10秒。
      • 请求总数阀值:在快照时间窗内,必须满足请求总数阀值才有资格熔断。默认为20,意味着在10秒内,如果该hystrix命令的调用次数不足20次7,即使所有的请求都超时或其他原因失败,断路器都不会打开。
      • 错误百分比阀值:当请求总数在快照时间窗内超过了阀值,比如发生了30次调用,如果在这30次调用中,有15次发生了超时异常,也就是超过50%的错误百分比,在默认设定50%阀值情况下,这时候就会将断路器打开
    • 断路器开启或者关闭的条件

      • 达以下阀值,断路器将会开启:

        当满足一定的阀值的时候(默认10秒内超过20个请求次数)
        当失败率达到一定的时候(默认10秒内超过50%的请求失败)
        当开启的时候,所有请求都不会进行转发

        一段时间之后(默认是5秒),这个时候断路器是半开状态,会让其中一个请求进行转发。如果成功,断路器会关闭,若失败,继续开启。
        ————————————————
        版权声明:本文为CSDN博主「巨輪」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
        原文链接:https://blog.csdn.net/u011863024/article/details/114298282

    • 修改cloud-provider-hystrix-payment8001

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      @Service
      public class PaymentService{

      ...

      //=====服务熔断
      @HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = {
      @HystrixProperty(name = "circuitBreaker.enabled",value = "true"),// 是否开启断路器
      @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),// 请求次数
      @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"), // 时间窗口期
      @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60"),// 失败率达到多少后跳闸
      })
      public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
      if(id < 0) {
      throw new RuntimeException("******id 不能负数");
      }
      // Hutool国产工具类
      String serialNumber = IdUtil.simpleUUID();

      return Thread.currentThread().getName()+"\t"+"调用成功,流水号: " + serialNumber;
      }
      public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id) {
      return "id 不能负数,请稍后再试,/(ㄒoㄒ)/~~ id: " +id;
      }

      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      @RestController
      @Slf4j
      public class PaymentController
      {
      @Resource
      private PaymentService paymentService;

      ...

      //====服务熔断
      @GetMapping("/payment/circuit/{id}")
      public String paymentCircuitBreaker(@PathVariable("id") Integer id)
      {
      String result = paymentService.paymentCircuitBreaker(id);
      log.info("****result: "+result);
      return result;
      }
      }
  • 服务限流

    • alibaba的Sentinel时说明
  • 总结

    • 步骤

      1. 创建HystrixCommand (用在依赖的服务返回单个操作结果的时候)或HystrixObserableCommand(用在依赖的服务返回多个操作结果的时候)对象
      2. 命令执行
      3. 其中 HystrixCommand实现了下面前两种执行方式
        1. execute():同步执行,从依赖的服务返回一个单一的结果对象或是在发生错误的时候抛出异常
        2. queue():异步执行,直接返回一个Future对象,其中包含了服务执行结束时要返回的单一结果对象
      4. 而 HystrixObservableCommand实现了后两种执行方式:
      5. obseve():返回Observable对象,它代表了操作的多个统
      果,它是一个Hot Observable (不论“事件源”是否有“订阅者”,都会在创建后对事件进行发布,所以对于Hot Observable的每一个“订阅者”都有可能是从“事件源”的中途开始的,并可能只是看到了整个操作的局部过程)。
      
      1. toObservable():同样会返回Observable对象,也代表了操作的多个结果,但它返回的是一个Cold Observable(没有“订间者”的时候并不会发布事件,而是进行等待,直到有“订阅者”之后才发布事件,所以对于Cold Observable 的订阅者,它可以保证从一开始看到整个操作的全部过程)。
      2. 若当前命令的请求缓存功能是被启用的,并且该命令缓存命中,那么缓存的结果会立即以Observable对象的形式返回。
      3. 检查断路器是否为打开状态。如果断路器是打开的,那么Hystrix不会执行命令,而是转接到fallback处理逻辑(第8步);如果断路器是关闭的,检查是否有可用资源来执行命令(第5步)。
      4. 线程池/请求队列信号量是否占满。如果命令依赖服务的专有线程地和请求队列,或者信号量(不使用线程的时候)已经被占满,那么Hystrix也不会执行命令,而是转接到fallback处理理辑(第8步) 。
      5. Hystrix会根据我们编写的方法来决定采取什么样的方式去请求依赖服务。
      6. HystrixCommand.run():返回一个单一的结果,或者抛出异常。
      7. HystrixObservableCommand.construct():返回一个Observable对象来发射多个结果,或通过onError发送错误通知。
      8. Hystix会将“成功”、“失败”、“拒绝”、“超时” 等信息报告给断路器,而断路器会维护一组计数器来统计这些数据。断路器会使用这些统计数据来决定是否要将断路器打开,来对某个依赖服务的请求进行”熔断/短路”。
      9. 当命令执行失败的时候,Hystix会进入fallback尝试回退处理,我们通常也称波操作为“服务降级”。而能够引起服务降级处理的情况有下面几种:
      • 第4步∶当前命令处于“熔断/短路”状态,断洛器是打开的时候。
      • 第5步∶当前命令的钱程池、请求队列或者信号量被占满的时候。
      • 第6步∶HystrixObsevableCommand.construct()或HytrixCommand.run()抛出异常的时候。
      1. 当Hystrix命令执行成功之后,它会将处理结果直接返回或是以Observable的形式返回
    • 如果我们没有为命令实现降级逻辑或者在降级处理逻辑中抛出了异常,Hystrix依然会运回一个Obsevable对象,但是它不会发射任结果数惯,而是通过onError方法通知命令立即中断请求,并通过onError方法将引起命令失败的异常发送给调用者

  • Hystrix图形化Dashboard搭建

    • cloud-consumer-hystrix-dashboard9001

      • pom

        1
        2
        3
        4
        5
        6
        7
        8
        <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
        </dependency>
        <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
      • yaml

        1
        2
        server:
        port: 9001
      • 主启动类

        1
        2
        3
        4
        5
        6
        7
        8
        @SpringBootApplication
        @EnableHystrixDashboard
        public class HystrixDashboardMain9001
        {
        public static void main(String[] args) {
        SpringApplication.run(HystrixDashboardMain9001.class, args);
        }
        }
      • 所有Provider微服务提供类(8001/8002/8003)都需要监控依赖配置

        1
        2
        3
        4
        <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
      • 浏览器输入http://localhost:9001/hystrix

    • 新版本Hystrix需要在主启动类PaymentHystrixMain8001中指定监控路径

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      @SpringBootApplication
      @EnableEurekaClient
      @EnableCircuitBreaker
      public class PaymentHystrixMain8001
      {
      public static void main(String[] args) {
      SpringApplication.run(PaymentHystrixMain8001.class, args);
      }


      /**
      *此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
      *ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream",
      *只要在自己的项目里配置上下面的servlet就可以了
      *否则,Unable to connect to Command Metric Stream 404
      */
      @Bean
      public ServletRegistrationBean getServlet() {
      HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
      ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
      registrationBean.setLoadOnStartup(1);
      registrationBean.addUrlMappings("/hystrix.stream");
      registrationBean.setName("HystrixMetricsStreamServlet");
      return registrationBean;
      }
      }

Resilience4j

  • SpringCloud Alibaba篇

Sentinel

  • SpringCloud Alibaba篇

服务网关

✖️Zuul

Gateway

  • 概述

    • SpringCloud Gateway是Spring Cloud的一个全新项目,基于Spring 5.0+Spring Boot 2.0和Project Reactor等技术开发的网关,它旨在为微服务架构提供—种简单有效的统一的API路由管理方式

      SpringCloud Gateway作为Spring Cloud 生态系统中的网关,目标是替代Zuul

    • SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty

    • 基于异步非阻塞模型

  • 特点

    • 基于Spring Framework 5,Project Reactor和Spring Boot 2.0进行构建
    • 动态路由:能够匹配任何请求属性
    • 可以对路由指定Predicate (断言)和Filter(过滤器)
    • 集成Hystrix的断路器功能
    • 集成Spring Cloud 服务发现功能
    • 易于编写的Predicate (断言)和Filter (过滤器)
    • 请求限流功能
    • 支持路径重写
  • 与Zuul的区别

    • 在SpringCloud Finchley正式版之前,Spring Cloud推荐的网关是Netflix提供的Zuul
    • Zuul 1.x是一个基于阻塞I/O的API Gateway
    • Zuul 1.x基于Servlet 2.5使用阻塞架构它不支持任何长连接(如WebSocket)Zuul的设计模式和Nginx较像,每次I/О操作都是从工作线程中选择一个执行,请求线程被阻塞到工作线程完成,但是差别是Nginx用C++实现,Zuul用Java实现,而JVM本身会有第-次加载较慢的情况,使得Zuul的性能相对较差。
    • Zuul 2.x理念更先进,想基于Netty非阻塞和支持长连接,但SpringCloud目前还没有整合。Zuul .x的性能较Zuul 1.x有较大提升。在性能方面,根据官方提供的基准测试,Spring Cloud Gateway的RPS(每秒请求数)是Zuul的1.6倍。
    • Spring Cloud Gateway建立在Spring Framework 5、Project Reactor和Spring Boot2之上,使用非阻塞API。
    • Spring Cloud Gateway还支持WebSocket,并且与Spring紧密集成拥有更好的开发体验
  • 三大核心概念

    • Route(路由) - 路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如断言为true则匹配该路由
    • Predicate(断言) - 参考的是Java8的java.util.function.Predicate,开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由
    • Filter(过滤) - 指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改
    • web请求,通过一些匹配条件,定位到真正的服务节点。并在这个转发过程的前后,进行一些精细化控制。
    • predicate就是我们的匹配条件;而fliter,就可以理解为一个无所不能的拦截器。有了这两个元素,再加上目标uri,就可以实现一个具体的路由了
    • 核心逻辑:路由转发 + 执行过滤器链
  • 服务端

    • cloud-gateway-gateway9527

    • pom

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      <dependencies>
      <!--gateway-->
      <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-gateway</artifactId>
      </dependency>

      <!--eureka-client-->
      <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
      </dependency>

      <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
      <dependency>
      <groupId>cc.mousse.springcloud</groupId>
      <artifactId>cloud-api-commons</artifactId>
      <version>1.0-SNAPSHOT</version>
      </dependency>

      <!--一般基础配置类-->
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-devtools</artifactId>
      <scope>runtime</scope>
      <optional>true</optional>
      </dependency>

      <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
      </dependency>

      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
      </dependency>
      </dependencies>
    • yaml

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      server:
      port: 9527

      spring:
      application:
      name: cloud-gateway
      # 不想暴露8001端口,在8001外面套一层9527
      # 网关配置
      cloud:
      gateway:
      routes:
      # payment_route,路由的ID,没有固定规则但要求唯一,建议配合服务名
      - id: payment_route
      # 匹配后提供服务的路由地址
      uri: http://localhost:8001
      # 断言,路径相匹配的进行路由
      predicates:
      - Path=/payment/get/**
      # payment_route,路由的ID,没有固定规则但要求唯一,建议配合服务名
      - id: payment_route2
      # 匹配后提供服务的路由地址
      uri: http://localhost:8001
      # 断言,路径相匹配的进行路由
      predicates:
      - Path=/payment/port/**

      eureka:
      instance:
      hostname: cloud-gateway-service
      # 服务提供者provider注册进eureka服务列表内
      client:
      service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://localhost:7001/eureka, http://localhost:7002/eureka
    • 主启动类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      @EnableEurekaClient
      @SpringBootApplication
      public class GatewayApplication9527 {

      public static void main(String[] args) {
      SpringApplication.run(GatewayApplication9527.class, args);
      }

      }
  • 代码中注入RouteLocator的Bean方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Configuration
    public class GatewayConfig {

    @Bean
    public RouteLocator customerRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
    RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
    routes.route("support_oldtimes_club", r -> r.path("/support").uri("https://oldtimes.club/support")).build();
    return routes.build();
    }

    }
  • 动态路由

    • yaml

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      spring:
      application:
      name: cloud-gateway
      # 不想暴露8001端口,在8001外面套一层9527
      # 网关配置
      cloud:
      gateway:
      discovery:
      locator:
      # 开启从注册中心动态创建路由的功能,利用微服务名进行路由
      enabled: true
      routes:
      # payment_route,路由的ID,没有固定规则但要求唯一,建议配合服务名
      - id: payment_route
      # 匹配后提供服务的路由地址
      # lb:负载均衡
      uri: lb://CLOUD-PAYMENT-SERVICE
      # 断言,路径相匹配的进行路由
      predicates:
      - Path=/payment/get/**
      # payment_route,路由的ID,没有固定规则但要求唯一,建议配合服务名
      - id: payment_route2
      # 匹配后提供服务的路由地址
      uri: lb://CLOUD-PAYMENT-SERVICE
      # 断言,路径相匹配的进行路由
      predicates:
      - Path=/payment/port/**
  • Predicate

    • Gateway包括许多内置的Route Predicate工厂。所有这些Predicate都与HTTP请求的不同属性匹配。多个RoutePredicate工厂可以进行组合

    • Gateway创建Route 对象时,使用RoutePredicateFactory 创建 Predicate对象,Predicate 对象可以赋值给Route。Spring Cloud Gateway包含许多内置的Route Predicate Factories,所有这些谓词都匹配HTTP请求的不同属性。多种谓词工厂可以组合,并通过逻辑and

    • Predicate就是为了实现一组匹配规则,让请求过来找到对应的Route进行处理

    • 常用的Route Predicate Factory

      • The After Route Predicate Factory

        1
        2
        3
        4
        5
        6
        7
        8
        9
        spring:
        cloud:
        gateway:
        routes:
        - id: after_route
        uri: https://example.org
        predicates:
        # 这个时间后才能起效
        - After=2017-01-20T17:42:47.789-07:00[America/Denver]
        • 可以通过下述方法获得上述格式的时间戳字符串

          1
          2
          3
          4
          public static void main(String[] args) {
          ZonedDateTime zdt = ZonedDateTime.now();
          System.out.println(zdt);
          }
      • The Before Route Predicate Factory

      • The Between Route Predicate Factory

        1
        2
        3
        4
        5
        6
        7
        8
        9
        spring:
        cloud:
        gateway:
        routes:
        - id: between_route
        uri: https://example.org
        # 两个时间点之间
        predicates:
        - Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]
      • The Cookie Route Predicate Factory

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        spring:
        cloud:
        gateway:
        routes:
        - id: cookie_route
        uri: https://example.org
        predicates:
        # 需要两个参数:Cookie name,正则表达式
        - Cookie=chocolate, ch.p
        # curl http://localhost:9527/payment/get/1 --cookie "chocolate=cake"
      • The Header Route Predicate Factory

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        spring:
        cloud:
        gateway:
        routes:
        - id: header_route
        uri: https://example.org
        predicates:
        # 需要两个参数:属性名称,正则表达式
        - Header=X-Request-Id, \d+
        # 带指定请求头的参数的CURL命令:curl http://localhost:9527/payment/lb -H "X-Request-Id:123"
      • The Host Route Predicate Factory

      • The Method Route Predicate Factory

      • The Path Route Predicate Factory

      • The Query Route Predicate Factory

      • The RemoteAddr Route Predicate Factory

      • The weight Route Predicate Factory

  • Filter

    • 可用于修改进入的HTTP请求和返回的HTTP响应,路由过滤器只能指定路由进行使用。Spring Cloud Gateway内置了多种路由过滤器,他们都由GatewayFilter的工厂类来产生

    • 能干什么

      • 全局日志记录
      • 统一网关鉴权
    • 生命周期

      • pre
      • post
    • 种类(具体看官方文档)

      • GatewayFilter - 有31种
      • GlobalFilter - 有10种
    • 常用的GatewayFilter

      • AddRequestParameter GatewayFilter
    • 自定义全局GlobalFilter

      • 两个主要接口介绍

        • GlobalFilter
        • Ordered
      • 项目添加MyLogGateWayFilter类

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        @Slf4j
        @Component
        public class MyLogGateWayFilter implements GlobalFilter, Ordered {

        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("MyLogGateWayFilter.filter: " + new Date());
        String username = exchange.getRequest().getQueryParams().getFirst("username");
        if (username == null) {
        log.info("用户名为空");
        exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
        return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
        }

        @Override
        // 加载过滤器的顺序,越小越好
        public int getOrder() {
        return 0;
        }

        }

服务配置

✖️Config

  • 概述

    • Config为微服务架构中的微服务提供集中化的外部配置支持,配置服务器为各个不同微服务应用的所有环境提供了一个中心化的外部配置
    • 分为服务端和客户端两部分
      • 服务端也称为分布式配置中心,它是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密/解密信息等访问接口
      • 客户端则是通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息配置服务器默认采用git来存储配置信息,这样就有助于对环境配置进行版本管理,并且可以通过git客户端工具来方便的管理和访问配置内容
    • 作用
      • 集中管理配置文件
      • 不同环境不同配置,动态化的配置更新,分环境部署比如dev/test/prod/beta/release
      • 运行期间动态调整配置,不再需要在每个服务部署的机器上编写配置文件,服务会向配置中心统一拉取配置自己的信息
      • 当配置发生变动时,服务不需要重启即可感知到配置的变化并应用新的配置
      • 将配置信息以REST接口的形式暴露
    • 与GitHub整合配置
      • 由于SpringCloud Config默认使用Git来存储配置文件(也有其它方式,比如支持SVN和本地文件),但最推荐的还是Git,而且使用的是http/https访问的形式
  • 总控中心

    • 访问格式

      1
      2
      3
      4
      5
      6
      7

      /{application}/{profile}[/{label}]
      /{application}-{profile}.yml
      # 测试失败
      /{label}/{application}-{profile}.yml
      /{application}-{profile}.properties
      /{label}/{application}-{profile}.properties
    • gitee

      1
      2
      3
      4
      5
      6
      7
      8
      9
      # config-dev.yml
      config:
      info: "master branch,springcloud-config/config-dev.yml version=7"
      # config-pro.yml
      config:
      info: "master branch,springcloud-config/config-prod.yml version=1"
      # config-test.yml
      config:
      info: "master branch,springcloud-config/config-test.yml version=1"
    • pom

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      <?xml version="1.0" encoding="UTF-8"?>
      <project xmlns="http://maven.apache.org/POM/4.0.0"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      <parent>
      <artifactId>SpringCloud</artifactId>
      <groupId>cc.mousse.springcloud</groupId>
      <version>1.0-SNAPSHOT</version>
      </parent>
      <modelVersion>4.0.0</modelVersion>

      <artifactId>cloud-config-center-3344</artifactId>

      <properties>
      <maven.compiler.source>17</maven.compiler.source>
      <maven.compiler.target>17</maven.compiler.target>
      </properties>

      <dependencies>
      <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-config-server</artifactId>
      </dependency>

      <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
      </dependency>

      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      </dependency>

      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
      </dependency>

      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-devtools</artifactId>
      <scope>runtime</scope>
      <optional>true</optional>
      </dependency>

      <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
      </dependency>

      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
      </dependency>
      </dependencies>
      </project>
    • yaml

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      server:
      port: 3344

      spring:
      application:
      #注册进Eureka服务器的微服务名
      name: cloud-config-center
      cloud:
      config:
      server:
      git:
      #GitHub上面的git仓库名字
      uri: https://gitee.com/PhineasZ/springcloud-config.git
      #搜索目录
      search-paths:
      - springcloud-config
      default-label: master
      #读取分支
      label: master
      #服务注册到eureka地址
      eureka:
      client:
      service-url:
      defaultZone: http://localhost:7001/eureka
    • 主启动类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      @EnableConfigServer
      @SpringBootApplication
      public class ConfigCenterApplication3344 {

      public static void main(String[] args) {
      SpringApplication.run(ConfigCenterApplication3344.class, args);
      }

      }
    • 浏览器防问:http://localhost:3344/config/dev/master

  • 客户端

    • cloud-config-client-3355

    • pom

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      <dependencies>
      <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-config</artifactId>
      </dependency>

      <!-- 在SpringCloud 2020.* 版本把bootstrap禁用了,要把bootstrap从新导入进来-->
      <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-bootstrap</artifactId>
      <version>3.1.0</version>
      </dependency>

      <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
      </dependency>

      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      </dependency>

      <!-- POM引入actuator监控以实现动态刷新 -->
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
      </dependency>

      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-devtools</artifactId>
      <scope>runtime</scope>
      <optional>true</optional>
      </dependency>

      <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
      </dependency>

      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
      </dependency>
      </dependencies>
    • bootstrap.yaml

      • applicaiton.yml是用户级的资源配置项,bootstrap.yml是系统级的,优先级更加高
        • Spring Cloud会创建一个Bootstrap Context,作为Spring应用的Application Context的父上下文。初始化的时候,BootstrapContext负责从外部源加载配置属性并解析配置。这两个上下文共享一个从外部获取的Environment
        • Bootstrap属性有高优先级,默认情况下,它们不会被本地配置覆盖。Bootstrap context和Application Context有着不同的约定,所以新增了一个bootstrap.yml文件,保证Bootstrap Context和Application Context配置的分离
        • 要将Client模块下的application.yml文件改为bootstrap.yml,因为bootstrap.yml是比application.yml先加载
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      server:
      port: 3355

      spring:
      application:
      name: config-client
      cloud:
      # Config客户端配置
      config:
      # 分支名称
      label: master
      # 配置文件名称
      name: config
      # 读取后缀名称,上述3个综合:master分支上config-dev.yml的配置文件被读取http://config-3344.com:3344/master/config-dev.yml
      profile: dev
      # 配置中心地址
      uri: http://localhost:3344

      # 服务注册到eureka地址
      eureka:
      client:
      service-url:
      defaultZone: http://localhost:7001/eureka

      # 暴露监控端点以实现动态刷新
      management:
      endpoints:
      web:
      exposure:
      include: "*"
    • 主启动类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      @EnableEurekaClient
      @SpringBootApplication
      public class ConfigClientApplication3355 {

      public static void main(String[] args) {
      SpringApplication.run(ConfigClientApplication3355.class, args);
      }

      }
    • 业务类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      // 动态刷新用
      @RefreshScope
      @RestController
      public class ConfigClientController {

      @Value("${config.info}")
      private String configInfo;

      @GetMapping("/configInfo")
      public String getConfigInfo() {
      return configInfo;
      }

      }
    • 需要运维人员发送Post请求刷新3355

      1
      curl -X POST "http://localhost:3355/actuator/refresh"

✨Nacos

  • Cloud Alibaba篇

服务总线

✖️Bus

  • 概述

    • 用来将分布式系统的节点与轻量级消息系统链接起来的框架,它整合了Java的事件处理机制和消息中间件的功能。Spring Clud Bus目前支持RabbitMQ和Kafka
    • 基本原理
      • ConfigClient实例都监听MQ中同一个topic(默认是Spring Cloud Bus)。当一个服务刷新数据的时候,它会把这个信息放入到Topic中,这样其它监听同一Topic的服务就能得到通知,然后去更新自身的配置
    • 设计思想
      • 利用消息总线触发一个客户端/bus/refresh,而刷新所有客户端的配置(不适合)
        • 打破了微服务的职责单一性,因为微服务本身是业务模块,它本不应该承担配置刷新的职责
        • 破坏了微服务各节点的对等性
        • 有一定的局限性。例如,微服务在迁移时,它的网络地址常常会发生变化,此时如果想要做到自动刷新,那就会增加更多的修改
      • 利用消息总线触发一个服务端ConfigServer的/bus/refresh端点,而刷新所有客户端的配置(适合
  • RabbitMQ环境配置

    • 启动管理功能

      1
      rabbitmq-plugins enable rabbitmq_management
  • 客户端

    • cloud-config-client-3355/cloud-config-client-3366

    • pom

      1
      2
      3
      4
      5
      6
      7
      8
      9
      <!--添加消息总线RabbitMQ支持-->
      <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-bus-amqp</artifactId>
      </dependency>
      <dependency>
      <groupId>org-springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
      </dependency>
    • yaml

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      # rabbitmq相关配置 15672是Web管理界面的端口;5672是MQ访问的端口
      spring:
      ...
      rabbitmq:
      host: localhost
      port: 5672
      username: guest
      password: guest

      # 暴露监控端点以实现动态刷新
      management:
      endpoints:
      web:
      exposure:
      include: "*"
    • 主启动类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      @EnableEurekaClient
      @SpringBootApplication
      public class ConfigClientApplication3355 {

      public static void main(String[] args) {
      SpringApplication.run(ConfigClientApplication3355.class, args);
      }

      }
    • 业务类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      // 动态刷新用
      @RefreshScope
      @RestController
      public class ConfigClientController {

      @Value("${config.info}")
      private String configInfo;

      @Value("${server.port}")
      private String serverPort;

      @GetMapping("/configInfo")
      public String getConfigInfo() {
      return "serverPort: " + serverPort + "\t\tconfigInfo: " + configInfo;
      }

      }
  • 服务端

    • cloud-config-center-3344

      • pom

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        <!--添加消息总线RabbitNQ支持-->
        <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-bus-amqp</artifactId>
        </dependency>

        <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
      • yaml

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        # rabbitmq相关配置 15672是Web管理界面的端口;5672是MQ访问的端口
        spring:
        ...
        rabbitmq:
        host: localhost
        port: 5672
        username: guest
        password: guest

        # rabbitmq相关配置,暴露bus刷新配置的端点
        management:
        # 暴露bus刷新配置的端点
        endpoints:
        web:
        exposure:
        include: 'bus-refresh'
  • 全局通知

    • 修改Github上配置文件内容

    • 发送POST请求

      1
      curl -X POST "http://localhost:3344/actuator/busrefresh"
    • 一次发送,处处生效

  • 定点通知

    • 指定具体某一个实例生效

      1
      2
      3
      http://localhost:3344/actuator/busrefresh/{destination}

      curl -X POST "http://localhost:3344/actuator/busrefresh/config-client:3355"
    • /bus/refresh请求不再发送到具体的服务实例上,而是发给config server通过destination参数类指定需要更新配置的服务或实例

✨Nacos

  • Cloud Alibaba篇

消息驱动

Stream

概述
  • 作用
    • 屏蔽底层消息中间件的差异
    • 降低切换成本
    • 统一消息的编程模型
  • 通过定义绑定器Binder作为中间层,实现了应用程序与消息中间件细节之间的隔离
    • INPUT对应于消费者
    • OUTPUT对应于生产者
  • Stream中的消息通信方式遵循了发布-订阅模式
    • Topic主题进行广播
      • 在RabbitMQ就是Exchange
      • 在Kakfa中就是Topic
  • 目前仅支持RabbitMQ和Kakfa
编码API和常用注解
  • Middleware:中间件,目前只支持RabbitMQ和Kafka
  • Binder:应用与消息中间件之间的封装,目前实行了Kafka和RabbitMQ的Binder,通过Binder可以很方便的连接中间件,可以动态的改变消息类型(对应于Kafka的topic,RabbitMQ的exchange),这些都可以通过配置文件来实现
  • Channel:是队列Queue的一种抽象,在消息通讯系统中就是实现存储和转发的媒介,通过Channel对队列进行配置
  • Source和Sink:简单的可理解为参照对象是Spring Cloud Stream自身,从Stream发布消息就是输出,接受消息就是输入
  • @Input:注解标识输入通道,通过该输乎通道接收到的消息进入应用程序
  • @Output:注解标识输出通道,发布的消息将通过该通道离开应用程序
  • @StreamListener:监听队列,用于消费者的队列的消息接收
  • @EnableBinding:指信道channel和exchange绑定在一起
生产者
  • cloud-stream-rabbitmq-provider8801

  • pom

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>

    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>

    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
    </dependency>

    <!--基础配置-->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
    </dependency>

    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
    </dependency>

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>
    </dependencies>
  • yaml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    server:
    port: 8801

    spring:
    application:
    name: cloud-stream-provider
    cloud:
    stream:
    # 在此处配置要绑定的rabbitmq的服务信息
    binders:
    # 表示定义的名称,用于于binding整合
    defaultRabbit:
    # 消息组件类型
    type: rabbit
    # 设置rabbitmq的相关的环境配置
    environment:
    spring:
    rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
    # 服务的整合处理
    bindings:
    # 这个名字是一个通道的名称
    output:
    # 表示要使用的Exchange名称定义
    destination: studyExchange
    # 设置消息类型,本次为json,文本则设置“text/plain”
    content-type: application/json
    # 设置要绑定的消息服务的具体设置
    binder: defaultRabbit

    eureka:
    # 客户端进行Eureka注册的配置
    client:
    service-url:
    defaultZone: http://localhost:7001/eureka
    instance:
    # 设置心跳的时间间隔(默认是30秒)
    lease-renewal-interval-in-seconds: 2
    # 如果现在超过了5秒的间隔(默认是90秒)
    lease-expiration-duration-in-seconds: 5
    # 在信息列表时显示主机名称
    instance-id: send-8801.com
    # 访问的路径变为IP地址
    prefer-ip-address: true
  • 主启动类

    1
    2
    3
    4
    5
    6
    7
    8
    @SpringBootApplication
    public class StreamApplication8801 {

    public static void main(String[] args) {
    SpringApplication.run(StreamApplication8801.class, args);
    }

    }
  • 业务类

    • 发送消息接口

      1
      2
      3
      4
      5
      public interface IMessageProvider {

      String send();

      }
    • 发送消息接口实现类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      // 定义消息的推送管道
      @EnableBinding(Source.class)
      public class MessageProviderImpl implements IMessageProvider {

      // 消息发送管道
      @Resource
      private MessageChannel output;

      @Override
      public String send() {
      String serial = UUID.randomUUID().toString();
      output.send(MessageBuilder.withPayload(serial).build());
      System.out.println("serial: " + serial);
      return null;
      }

      }
    • Controller

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      @RestController
      public class SendMessageController {

      @Resource
      private IMessageProvider messageProvider;

      @GetMapping(value = "/sendMessage")
      public String sendMessage() {
      return messageProvider.send();
      }

      }
消费者
  • cloud-stream-rabbitmq-consumer8802/cloud-stream-rabbitmq-consumer8803

  • pom

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>

    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
    </dependency>

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>

    <!--基础配置-->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
    </dependency>

    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
    </dependency>

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>
    </dependencies>
  • yaml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    server:
    port: 8802

    spring:
    application:
    name: cloud-stream-consumer
    cloud:
    stream:
    # 在此处配置要绑定的rabbitmq的服务信息;
    binders:
    # 表示定义的名称,用于于binding整合
    defaultRabbit:
    # 消息组件类型
    type: rabbit
    # 设置rabbitmq的相关的环境配置
    environment:
    spring:
    rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
    # 服务的整合处理
    bindings:
    # 这个名字是一个通道的名称
    input:
    # 表示要使用的Exchange名称定义
    destination: studyExchange
    # 设置消息类型,本次为对象json,如果是文本则设置“text/plain”
    content-type: application/json
    # 设置要绑定的消息服务的具体设置
    binder: defaultRabbit

    eureka:
    # 客户端进行Eureka注册的配置
    client:
    service-url:
    defaultZone: http://localhost:7001/eureka
    instance:
    # 设置心跳的时间间隔(默认是30秒)
    lease-renewal-interval-in-seconds: 2
    # 如果现在超过了5秒的间隔(默认是90秒)
    lease-expiration-duration-in-seconds: 5
    # 在信息列表时显示主机名称
    instance-id: receive-8802.com
    # 访问的路径变为IP地址
    prefer-ip-address: true
  • 主启动类

    1
    2
    3
    4
    5
    6
    7
    8
    @SpringBootApplication
    public class StreamApplication8802 {

    public static void main(String[] args) {
    SpringApplication.run(StreamApplication8802.class, args);
    }

    }
  • 业务类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @Slf4j
    @Component
    @EnableBinding(Sink.class)
    public class ReceiveMessageListenerController {

    @Value("${server.port}")
    private String serverPort;

    @StreamListener(Sink.INPUT)
    public void input(Message<String> message) {
    log.info("{}接受到的消息: {}", serverPort, message.getPayload());
    }

    }
分组消费与持久化
  • 分组和持久化属性group(重要)

    • 不同组可以全面消费(重复消费),同一组存在竞争关系,只有一个可以消费

      • 8802/8803都变成相同组
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      spring:
      ...
      # 服务的整合处理
      bindings:
      # 这个名字是一个通道的名称
      input:
      # 表示要使用的Exchange名称定义
      destination: studyExchange
      # 设置消息类型,本次为对象json,如果是文本则设置“text/plain”
      content-type: application/json
      # 设置要绑定的消息服务的具体设置
      binder: defaultRabbit
      group: A_group
    • 有分组属性配置才有持久化

分布式请求链路跟踪

Sleuth

概述
  • 类似于tracert
  • 提供了一套完整的服务跟踪的解决方案
  • 在分布式系统中提供追踪解决方案并且兼容支持了zipkin
zipkin
  • https://dl.bintray.com/openzipkin/maven/io/zipkin/java/zipkin-server/
  • SpringCloud从F版起已不需要自己构建Zipkin Server了,只需调用jar包即可
  • 一条链路通过Trace ld唯一标识,Span标识发起的请求信息,各span通过parent id关联起来
  • Trace:类似于树结构的Span集合,表示一条调用链路,存在唯一标识
  • span:表示调用链路来源,通俗的理解span就是一次请求信息
服务提供者
  • cloud-provider-payment8001

  • pom

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-sleuth-zipkin</artifactId>
    </dependency>

    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-sleuth</artifactId>
    </dependency>
  • yaml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    spring:
    ...
    zipkin:
    base-url: http://localhost:9411
    # 关键
    sleuth:
    sampler:
    # 采样率值介于 0 到 1 之间,1 则表示全部采集
    probability: 1
  • 业务类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @Slf4j
    @RestController
    @RequestMapping(value = "/payment")
    public class PaymentController {

    ...

    @GetMapping("/zipkin")
    public String paymentZipkin() {
    return "hi ,i'm payment zipkin server fall back,welcome to here";
    }

    }
服务消费者
  • cloue-consumer-order80

  • pom

  • yaml

  • 业务类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @Slf4j
    @RestController
    @RequestMapping("/consumer/payment")
    public class OrderController {

    ...

    // zipkin + sleuth
    @GetMapping("/zipkin")
    public String paymentZipkin() {
    return restTemplate.getForObject("http://localhost:8001" + "/payment/zipkin/", String.class);
    }

    }

Cloud Alibaba

概述

  • Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用微服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务

  • 依托 Spring Cloud Alibaba,只需要添加一些注解和少量配置,就可以将 Spring Cloud 应用接入阿里微服务解决方案,通过阿里中间件来迅速搭建分布式应用系统

  • 作用

    • 服务限流降级:默认支持 WebServlet、WebFlux, OpenFeign、RestTemplate、Spring Cloud Gateway, Zuul, Dubbo 和 RocketMQ 限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级 Metrics 监控。
    • 服务注册与发现:适配 Spring Cloud 服务注册与发现标准,默认集成了 Ribbon 的支持。
    • 分布式配置管理:支持分布式系统中的外部化配置,配置更改时自动刷新。
    • 消息驱动能力:基于 Spring Cloud Stream 为微服务应用构建消息驱动能力。
    • 分布式事务:使用 @GlobalTransactional 注解, 高效并且对业务零侵入地解决分布式事务问题。
    • 阿里云对象存储:阿里云提供的海量、安全、低成本、高可靠的云存储服务。支持在任何应用、任何时间、任何地点存储和访问任意类型的数据。
    • 分布式任务调度:提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。同时提供分布式的任务执行模型,如网格任务。网格任务支持海量子任务均匀分配到所有 Worker(schedulerx-client)上执行。
    • 阿里云短信服务:覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通
  • 依赖

    • 如果需要使用已发布的版本,在 dependencyManagement 中添加如下配置,然后在 dependencies 中添加自己所需使用的依赖即可使用

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      <dependencyManagement>
      <dependencies>
      <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>
      </dependencies>
      </dependencyManagement>
  • 文档

Sentinel

概述

  • https://sentinelguard.io/zh-cn/docs/introduction.html

  • 把流量作为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性

  • 懒加载

  • 与Hystrix比较

    • Hystrix
      • 需要手工搭建监控平台
      • 没有一套web界面可以给我们进行更加细粒度化得配置流控、速率控制、服务熔断、服务降级
    • Sentinel
      • 单独一个组件,可以独立出来
      • 直接界面化的细粒度统一配置
  • 两个部分

    • 核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持
    • 控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器
  • 默认端口8080

    1
    java [-Dserver.port=8800] -jar sentinel-dashboard-1.8.3.jar
    • 登录账号密码均为sentinel

初始化

  • cloudalibaba-sentinel-service8401

  • pom

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    <dependencies>
    <dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
    <groupId>cc.mousse.springcloud</groupId>
    <artifactId>cloud-api-commons</artifactId>
    <version>1.0-SNAPSHOT</version>
    </dependency>
    <!--SpringCloud ailibaba nacos -->
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!--SpringCloud ailibaba sentinel-datasource-nacos 后续做持久化用到-->
    <!-- https://mvnrepository.com/artifact/com.alibaba.csp/sentinel-datasource-nacos -->
    <dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
    <version>1.8.3</version>
    </dependency>
    <!--SpringCloud ailibaba sentinel -->
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    </dependency>
    <!--openfeign-->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <!-- SpringBoot整合Web组件+actuator -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!--日常通用jar包配置-->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
    </dependency>
    <!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
    <dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.7.22</version>
    </dependency>
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>
    </dependencies>
  • yaml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    server:
    port: 8401

    spring:
    application:
    name: cloudalibaba-sentinel-service
    cloud:
    nacos:
    discovery:
    # Nacos服务注册中心地址
    server-addr: localhost:8848
    sentinel:
    transport:
    # 配置Sentinel dashboard地址
    dashboard: localhost:8800
    port: 8719

    management:
    endpoints:
    web:
    exposure:
    include: '*'

    feign:
    sentinel:
    # 激活Sentinel对Feign的支持
    enabled: true
  • 主启动

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @SpringBootApplication
    @EnableDiscoveryClient
    public class SentinelApplication8401 {

    public static void main(String[] args) {
    SpringApplication.run(SentinelApplication8401.class, args);
    }

    }
  • 业务类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    @Slf4j
    @RestController
    public class FlowLimitController {

    @GetMapping("/testA")
    public String testA() {
    return "------testA";
    }

    @GetMapping("/testB")
    public String testB() {
    log.info(Thread.currentThread().getName() + "\t" + "...testB");
    return "------testB";
    }

    }
    // Sentinel采用的懒加载说明
    // 执行一次访问即可
    // http://localhost:8401/testA
    // http://localhost:8401/testB

流控规则

  • 资源名:唯一名称,默认请求路径
  • 针对来源:Sentinel可以针对调用者进行限流,填写微服务名,默认default(不区分来源)
  • 阈值类型/单机阈值
    • QPS(每秒钟的请求数量)︰当调用该API的QPS达到阈值的时候,进行限流
    • 线程数:当调用该API的线程数达到阈值的时候,进行限流
  • 是否集群:不需要集群
  • 流控模式
    • 直接:API达到限流条件时,直接限流
    • 关联:当关联的资源达到阈值时,就限流自己
      • 例:当与A关联的资源B达到阈值后限流A自己
    • 链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)【API级别的针对来源】
  • 流控效果
    • 快速失败:直接失败,抛异常
    • Warm up:根据Code Factor(冷加载因子,默认3)的值,从阈值/codeFactor,经过预热时长,才达到设置的QPS阈值
      • 即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过”冷启动”,让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮
      • 例:阈值10,预热时长5,启动时刚开始的阈值为10 / 3,5秒种内逐渐涨为10
    • 排队等待:匀速排队,让请求以匀速的速度通过,阈值类型必须设置为QPS,否则无效
      • 方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法
      • 例:QPS=2时,每隔500ms才允许下一个请求
      • 暂时不支持 QPS > 1000 的场景

降级规则

  • 复杂链路上的某一环不稳定,就可能会层层级联,最终导致整个链路都不可用。因此我们需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置
  • Sentinel熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误
  • 当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)
  • RT(平均响应时间,秒级)
    • 在时间窗口内通过的请求>=5且平均响应时间超出阈值
    • 窗口期过后关闭断路器
    • RT最大4900(更大的需要通过-Dcsp.sentinel.statistic.max.rt=XXXX才能生效)
    • Sentinel1.7.0才有平均响应时间DEGRADE_GRADE_RT),Sentinel 1.8.0的没有这项,取而代之的是慢调用比例 (SLOW_REQUEST_RATIO)
    • 慢调用比例:选择以慢调用比例作为阈值,需要设置允许的慢调用 RT,请求的响应时间大于该值则统计为慢调用。当单位统计时长内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断
  • 异常比列(秒级)
    • QPS >= 5且异常比例(秒级统计)超过阈值时,触发降级;时间窗口结束后,关闭降级,异常比率的阈值范围是[0.0, 1.0],代表0% -100%
    • 1.8.0:当单位统计时长内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态,若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断
  • 异常数(分钟级)
    • 异常数(分钟统计)超过阈值时,触发降级;时间窗口结束后,关闭降级,由于统计时间窗口是分钟级别的,若timeWindow小于60s,则结束熔断状态后码可能再进入熔断状态
    • 1.8.0:当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断

热点key限流

  • 很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:

    • 商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制
    • 用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制
  • 热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效

  • 利用LRU策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控,支持集群模式

  • 热点参数的注意点,参数必须是基本类型或者String

  • 运行时出错不兜底

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    @Slf4j
    @RestController
    public class FlowLimitController {

    @GetMapping("/testHotKey")
    @SentinelResource(value/*对应热点规则资源名*/ = "testHotKey", blockHandler/*兜底方法*/ = "deal_testHotKey")
    public String testHotKey(
    // 参数下标从0开始
    @RequestParam(value = "p1", required = false) String p1,
    @RequestParam(value = "p2", required = false) String p2
    ) {
    //int age = 10/0;
    return "------testHotKey";
    }

    // 兜底方法
    public String deal_testHotKey(String p1, String p2, BlockException exception) {
    // sentinel系统默认的提示:Blocked by Sentinel (flow limiting)
    return "------deal_testHotKey,o(╥﹏╥)o";
    }

    }
  • 参数例外项

    • 希望参数当它是某个特殊值时,它的限流值和平时不一样

系统自适应限流

  • 从整体维度应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性
  • 模式
    • Load 自适应(仅对 Linux/Unix-like 机器生效):系统的 load1 作为启发指标,进行自适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)
    • CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0),比较灵敏
    • 平均 RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒
    • 并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护
    • 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护
  • 三个核心API
    • SphU定义资源
    • Tracer定义统计
    • ContextUtil定义了上下文

@SentinelResource

  • 注解方式埋点不支持 private 方法

  • 按资源名称限流

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @GetMapping(/*按URL地址限流*/"/byResource")
    @SentinelResource(value/*按资源名称限流*/ = "byResource", blockHandler = "handleException")
    public CommonResult<Object> byResource() {
    return new CommonResult<>(200, "按资源名称限流测试OK", new Payment(2020L, "serial001"));
    }

    public CommonResult<Object> handleException(BlockException exception) {
    return new CommonResult<>(444, exception.getClass().getCanonicalName() + "\t 服务不可用");
    }
  • 按URL地址限流

    • 通过访问的URL来限流,会返回Sentinel自带默认的限流处理信息
    1
    2
    3
    4
    5
    @GetMapping("/rateLimit/byUrl")
    @SentinelResource(value = "byUrl")
    public CommonResult<Object> byUrl() {
    return new CommonResult<>(200, "按url限流测试OK", new Payment(2020L, "serial002"));
    }
自定义限流处理
  • 创建CustomerBlockHandler类用于自定义限流处理逻辑

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class CustomerBlockHandler {

    public static CommonResult<Object> handlerException(BlockException exception) {
    return new CommonResult<>(4444, "按客戶自定义,global handlerException----1");
    }

    public static CommonResult<Object> handlerException2(BlockException exception) {
    return new CommonResult<>(4444, "按客戶自定义,global handlerException----2");
    }

    }
  • 业务类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @GetMapping("/rateLimit/customerBlockHandler")
    @SentinelResource(
    value = "customerBlockHandler",
    // 自定义限流处理类
    blockHandlerClass = CustomerBlockHandler.class,
    // 自定义方法
    blockHandler = "handlerException2"
    )
    public CommonResult<Object> customerBlockHandler() {
    return new CommonResult<>(200, "按客戶自定义", new Payment(2020L, "serial003"));
    }
属性
  • value:资源名称,必需项(不能为空)

  • entryType:entry 类型,可选项(默认为 EntryType.OUT)

  • blockHandler / blockHandlerClass: blockHandler 对应处理 BlockException 的函数名称,可选项。blockHandler 函数访问范围需要是 public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。

  • fallback / fallbackClass:fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了exceptionsToIgnore里面排除掉的异常类型)进行处理。fallback 函数签名和位置要求:

    • 返回值类型必须与原函数返回值类型一致;
    • 方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
    • fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
  • defaultFallback(since 1.6.0):默认的 fallback 函数名称,可选项,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。默认 fallback 函数可以针对所有类型的异常(除了exceptionsToIgnore里面排除掉的异常类型)进行处理。若同时配置了 fallback 和 defaultFallback,则只有 fallback 会生效。defaultFallback 函数签名要求:

    • 返回值类型必须与原函数返回值类型一致;
    • 方法参数列表需要为空,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
    • defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
  • exceptionsToIgnore(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出

    1
    @SentinelResource(value = "fallback", fallback = "handlerFallback", blockHandler = "blockHandler", exceptionsToIgnore = {IllegalArgumentException.class})

服务熔断

Ribbon/LoadBalance
  • 没有任何配置:给用户error页面,不友好
  • 只配置fallback:只负责业务异常
  • 只配置blockHandler:只负责sentinel控制台配置违规
  • fallback和blockHandler都配置:被限流降级而抛出BlockException时只会进入blockHandler处理逻辑
提供者
  • cloudalibaba-provider-payment9003/9004

  • pom

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    <dependencies>
    <!--SpringCloud ailibaba nacos -->
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
    <groupId>cc.mousse.springcloud</groupId>
    <artifactId>cloud-api-commons</artifactId>
    <version>1.0-SNAPSHOT</version>
    </dependency>
    <!-- SpringBoot整合Web组件 -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!--日常通用jar包配置-->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
    </dependency>
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>
    </dependencies>
  • yaml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    server:
    port: 9003

    spring:
    application:
    name: nacos-payment-provider
    cloud:
    nacos:
    discovery:
    # 配置Nacos地址
    server-addr: localhost:8848

    management:
    endpoints:
    web:
    exposure:
    include: '*'
  • 主启动

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @SpringBootApplication
    @EnableDiscoveryClient
    public class PaymentApplication9003 {

    public static void main(String[] args) {
    SpringApplication.run(PaymentApplication9003.class, args);
    }

    }
  • 业务类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    @RestController
    public class PaymentController {
    @Value("${server.port}")
    private String serverPort;

    //模拟数据库
    public static HashMap<Long, Payment> hashMap = new HashMap<>();

    static {
    hashMap.put(1L, new Payment(1L, "28a8c1e3bc2742d8848569891fb42181"));
    hashMap.put(2L, new Payment(2L, "bba8c1e3bc2742d8848569891ac32182"));
    hashMap.put(3L, new Payment(3L, "6ua8c1e3bc2742d8848569891xt92183"));
    }

    @GetMapping(value = "/paymentSQL/{id}")
    public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id) {
    Payment payment = hashMap.get(id);
    return new CommonResult<>(200, "from mysql,serverPort: " + serverPort, payment);
    }

    }
消费者
  • cloudalibaba-consumer-nacos-order84

  • pom

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    <dependencies>
    <!--SpringCloud openfeign -->
    <!--
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    -->
    <!--SpringCloud ailibaba nacos -->
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!-- 新版已放弃Ribbon,需要loadbalancer做为负载均衡 -->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
    </dependency>
    <!--SpringCloud ailibaba sentinel -->
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    </dependency>
    <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
    <dependency>
    <groupId>cc.mousse.springcloud</groupId>
    <artifactId>cloud-api-commons</artifactId>
    <version>1.0-SNAPSHOT</version>
    </dependency>
    <!-- SpringBoot整合Web组件 -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!--日常通用jar包配置-->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
    </dependency>
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>
    </dependencies>
  • yaml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    server:
    port: 84

    spring:
    application:
    name: nacos-order-consumer
    cloud:
    nacos:
    discovery:
    server-addr: localhost:8848
    sentinel:
    transport:
    # 配置Sentinel dashboard地址
    dashboard: localhost:8800
    # 默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
    port: 8719

    # 消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者)
    service-url:
    nacos-user-service: http://nacos-payment-provider

    # 激活Sentinel对Feign的支持
    feign:
    sentinel:
    enabled: false
  • 主启动

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @EnableDiscoveryClient
    @SpringBootApplication
    public class OrderNacosApplication84 {

    public static void main(String[] args) {
    SpringApplication.run(OrderNacosApplication84.class, args);
    }

    }
  • 配置类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Configuration
    public class ApplicationContextConfig {

    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate() {
    return new RestTemplate();
    }

    }
  • 业务类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    @Slf4j
    @RestController
    public class CircleBreakerController {
    public static final String SERVICE_URL = "http://nacos-payment-provider";

    @Resource
    private RestTemplate restTemplate;

    @RequestMapping("/consumer/fallback/{id}")
    @SentinelResource(value = "fallback")//没有配置
    public CommonResult<Payment> fallback(@PathVariable Long id) {
    CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class, id);
    if (id == 4) {
    throw new IllegalArgumentException("IllegalArgumentException,非法参数异常....");
    } else if (result.getData() == null) {
    throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常");
    }
    return result;
    }

    }
OpenFeign
提供者
  • pom

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

    1
    2
    3
    4
    # 激活Sentinel对Feign的支持
    feign:
    sentinel:
    enabled: true
  • 主启动

    1
    @EnableFeignClients
  • 业务类

    1
    2
    3
    4
    5
    6
    7
    @FeignClient(value = "nacos-payment-provider", fallback = PaymentFallbackService.class)
    public interface PaymentService {

    @GetMapping(value = "/paymentSQL/{id}")
    CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);

    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Component
    public class PaymentFallbackService implements PaymentService {

    @Override
    public CommonResult<Payment> paymentSQL(Long id) {
    return new CommonResult<>(44444, "服务降级返回,---PaymentFallbackService", new Payment(id, "errorSerial"));
    }

    }
    1
    2
    3
    4
    5
    6
    7
    8
    // OpenFeign
    @Resource
    private PaymentService paymentService;

    @GetMapping(value = "/consumer/paymentSQL/{id}")
    public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id) {
    return paymentService.paymentSQL(id);
    }
框架比较
Sentinel Hystrix resilience4j
隔离策略 信号量隔离(并发线程数限流) 线程池隔商/信号量隔离 信号量隔离
熔断降级策略 基于响应时间、异常比率、异常数 基于异常比率 基于异常比率、响应时间
实时统计实现 滑动窗口(LeapArray) 滑动窗口(基于RxJava) Ring Bit Buffer
动态规则配置 支持多种数据源 支持多种数据源 有限的支持
扩展性 多个扩展点 插件的形式 接口的形式
基于注解的支持 支持 支持 支持
限流 基于QPS,支持基于调用关系的限流 有限的支持 Rate Limiter
流量整形 支持预热模式匀速器模式、预热排队模式 不支持 简单的Rate Limiter模式
系统自适应保护 支持 不支持 不支持
控制台 提供开箱即用的控制台,可配置规则、查看秒级监控,机器发观等 简单的监控查看 不提供控制台,可对接其它监控系统

持久化

  • 一旦重启应用,sentinel规则将消失,生产环境需要将配置规则进行持久化
  • 将限流配置规则持久化进Nacos保存,只要Nacos里面的配置不删除,针对8401上sentinel上的流控规则持续有效
提供者
  • cloudalibaba-sentinel-service8401

  • pom

    1
    2
    3
    4
    5
    <dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
    <version>1.8.3</version>
    </dependency>
  • yaml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    spring:
    application:
    name: cloudalibaba-sentinel-service
    cloud:
    nacos:
    discovery:
    # Nacos服务注册中心地址
    server-addr: localhost:8848
    sentinel:
    transport:
    # 配置Sentinel dashboard地址
    dashboard: localhost:8800
    port: 8719
    # 添加Nacos数据源配置
    datasource:
    ds1:
    nacos:
    server-addr: localhost:8848
    dataId: cloudalibaba-sentinel-service
    groupId: DEFAULT_GROUP
    data-type: json
    rule-type: flow
  • 添加Nacos业务规则配置

    • Data ID:cloudalibaba-sentinel-service

    • JSON

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      [{
      // resource:资源名称
      "resource": "/rateLimit/byUrl",
      // limitApp:来源应用
      "IimitApp": "default",
      // grade:阈值类型,0表示线程数, 1表示QPS
      "grade": 1,
      // count:单机阈值
      "count": 1,
      // strategy:流控模式,0表示直接,1表示关联,2表示链路
      "strategy": 0,
      // controlBehavior:流控效果,0表示快速失败,1表示Warm Up,2表示排队等待
      "controlBehavior": 0,
      // clusterMode:是否集群
      "clusterMode": false
      }]

Nacos

概述

  • 一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台

  • Nacos就是注册中心+配置中心的组合Nacos = Eureka + Config + Bus

  • AP/CP

  • 端口8848,默认账号密码都是nacos

  • 运行命令:./startup.sh

    • 若启动错误,把文件中export MODE="cluster"改为export MODE="standalone"
  • 支持负载均衡:spring-cloud-starter-alibaba-nacos-discovery内含netflix-ribbon包(新版已放弃,需引入loadbalancer

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

注册中心

提供者
  • pom

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <dependencyManagement>
    <dependencies>
    <!--spring cloud alibaba 2.1.0.RELEASE-->
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-dependencies</artifactId>
    <version>2021.1</version>
    <type>pom</type>
    <scope>import</scope>
    </dependency>
    </dependencies>
    </dependencyManagement>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    <dependencies>
    <!-- SpringCloudAlibabaNacos -->
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!-- SpringBoot整合Web组件 -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!--日常通用jar包配置-->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
    </dependency>
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>
    </dependencies>
  • yaml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    server:
    port: 9001

    spring:
    application:
    name: nacos-payment-provider
    cloud:
    nacos:
    discovery:
    # 配置Nacos地址
    server-addr: localhost:8848

    management:
    endpoints:
    web:
    exposure:
    include: '*'
  • 主启动

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @EnableDiscoveryClient
    @SpringBootApplication
    public class PaymentApplication9001 {

    public static void main(String[] args) {
    SpringApplication.run(PaymentApplication9001.class, args);
    }

    }
  • 业务类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @RestController
    public class PaymentController {

    @Value("${server.port}")
    private String serverPort;

    @GetMapping(value = "/payment/nacos/{id}")
    public String getPayment(@PathVariable("id") Integer id) {
    return "nacos registry, serverPort: " + serverPort + "\t id" + id;
    }

    }
消费者
  • pom

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    <dependencies>
    <!-- SpringCloudAilibabaNacos -->
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!-- 新版已放弃Ribbon,需要loadbalancer做为负载均衡 -->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
    </dependency>
    <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
    <dependency>
    <groupId>cc.mousse.springcloud</groupId>
    <artifactId>cloud-api-commons</artifactId>
    <version>1.0-SNAPSHOT</version>
    </dependency>
    <!-- SpringBoot整合Web组件 -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!--日常通用jar包配置-->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
    </dependency>
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>
    </dependencies>
  • yaml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    server:
    port: 83

    spring:
    application:
    name: nacos-order-consumer
    cloud:
    nacos:
    discovery:
    server-addr: localhost:8848

    # 消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者)
    service-url:
    nacos-user-service: http://nacos-payment-provider
  • 主启动

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @EnableDiscoveryClient
    @SpringBootApplication
    public class OrderNacosApplication83 {

    public static void main(String[] args) {
    SpringApplication.run(OrderNacosApplication83.class, args);
    }

    }
  • 业务类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Configuration
    public class ApplicationContextConfig {

    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate() {
    return new RestTemplate();
    }

    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @RestController
    public class OrderNacosController {

    @Resource
    private RestTemplate restTemplate;

    @Value("${service-url.nacos-user-service}")
    private String serverURL;

    @GetMapping(value = "/consumer/payment/nacos/{id}")
    public String paymentInfo(@PathVariable("id") Long id) {
    return restTemplate.getForObject(serverURL + "/payment/nacos/" + id, String.class);
    }

    }
对比
  • Nacos与其他注册中心特性对比

    Nacos Eureka Consul CoreDNS Zookeeper
    致性协议 CP+AP AP CP CP
    健康检查 TCP/ATTP/MySQL/Client Beat Client Beat TCP/HTTP/gRPC/Cmd Client Beat
    负载均衡 权重/DSL/metadata/CMDB Ribbon Fabio RR
    雪崩保护 支持 支持 不支持 不支持 不支持
    自动注销实例 支持 支持 不支持 不支持 支持
    访问协议 HTTP/DNS/UDP HTTP HTTP/DNS DNS TCP
    监听支持 支持 支持 支持 不支持 支持
    多数据中心 支持 支持 支持 不支持 不支持
    跨注册中心 支持 不支持 支持 不支持 不支持
    SpringCloud集成支持 支持 支持 支持 不支持 不支持
    Dubbo集成 支持 不支持 不支持 不支持 支持
    K8s集成 支持 不支持 支持 支持 不支持
  • Nacos支持AP和CP模式的切换

    1
    curl -X PUT '$NACOS_SERVER:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=CP
    • 如果不需要存储服务级别的信息且服务实例是通过nacos-client注册,并能够保持心跳上报,那么就可以选择AP模式。当前主流的服务如Spring cloud和Dubbo服务,都适用于AP模式,AP模式为了服务的可能性而减弱了一致性,因此AP模式下只支持注册临时实例
    • 如果需要在服务级别编辑或者存储配置信息,那么CP是必须,K8S服务和DNS服务则适用于CP模式。CP模式下则支持注册持久化实例,此时则是以Raft协议为集群运行模式,该模式下注册实例之前必须先注册服务,如果服务不存在,则会返回错误

配置中心

基础配置
  • cloudalibaba-config-nacos-client3377

  • pom

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    <dependencies>
    <!-- nacos-config -->
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>
    <!-- 用来读取bootstrap.yaml -->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bootstrap</artifactId>
    </dependency>
    <!-- nacos-discovery -->
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!-- web + actuator -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!-- 一般基础配置 -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
    </dependency>
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>
    </dependencies>
  • yaml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    # Nacos同springcloud-config一样,在项目初始化时,要保证先从配置中心进行配置拉取,拉取配置之后,才能保证项目的正常启动。
    # springboot中配置文件的加载是存在优先级顺序的,bootstrap优先级高于application
    # bootstrap.yaml
    server:
    port: 3377

    spring:
    application:
    name: nacos-config-client
    cloud:
    nacos:
    discovery:
    # Nacos服务注册中心地址
    server-addr: localhost:8848
    config:
    # Nacos作为配置中心地址
    server-addr: localhost:8848
    # 指定yaml格式的配置
    file-extension: yaml
    # 与nacos上的group对应
    group: DEV_GROUP
    namespace: 7d8f0f5a-6a53-4785-9686-dd460158e5d4


    # ${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension}
    # nacos-config-client-dev.yaml

    # nacos-config-client-test.yaml ----> config.info
    1
    2
    3
    4
    5
    6
    7
    # application.yaml
    spring:
    profiles:
    # 表示开发环境
    active: dev
    #active: test
    #active: info
  • 主启动

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @EnableDiscoveryClient
    @SpringBootApplication
    public class NacosConfigClientApplication3377 {

    public static void main(String[] args) {
    SpringApplication.run(NacosConfigClientApplication3377.class, args);
    }

    }
  • 业务类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 支持Nacos的动态刷新功能
    @RefreshScope
    @RestController
    public class ConfigClientController {

    @Value("${config.info}")
    private String configInfo;

    @GetMapping("/config/info")
    public String getConfigInfo() {
    return configInfo;
    }

    }
匹配规则
  • https://nacos.io/zh-cn/docs/quick-start-spring-cloud.html

  • 之所以需要配置spring.application.name,是因为它是构成Nacos配置管理dataId 字段的一部分

  • dataId的完整格式

    1
    ${prefix}-${spring-profile.active}.${file-extension}
    • prefix默认为spring.application.name的值,也可以通过配置项spring.cloud.nacos.config.prefix来配置
    • spring.profile.active即为当前环境对应的 profile,详情可以参考 Spring Boot文档
      • 注意:当spring.profile.active为空时,对应的连接符 - 也将不存在,datald 的拼接格式变成${prefix}.${file-extension}
    • file-exetension为配置内容的数据格式,可以通过配置项spring .cloud.nacos.config.file-extension来配置。目前只支持properties和yaml类型
    • 通过Spring Cloud 原生注解@RefreshScope实现配置自动更新
  • 最后公式

    1
    2
    3
    ${spring.application.name)}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}

    # nacos-config-client-dev.yaml
  • Nacos配置内容

    1
    2
    3
    # config后面一定要加空格!!
    config:
    info: config info for dev, version = 1
命名空间/分组/DataID
  • 多环境多项目管理
  • 类似Java里面的package名和类名:最外层的namespace是可以用于区分部署环境的,Group和DatalD逻辑上区分两个目标对象
  • 默认情况
    • Namespace:public,Namespace主要用来实现隔离
      • 比方说我们现在有三个环境:开发、测试、生产环境,我们就可以创建三个Namespace,不同的Namespace之间是隔离的
    • Group:DEFAULT_GROUP,Group可以把不同的微服务划分到同一个分组里面去
    • Service就是微服务:一个Service可以包含多个Cluster (集群),Nacos默认Cluster是DEFAULT,Cluster是对指定微服务的一个虚拟划分
      • 比方说为了容灾,将Service微服务分别部署在了杭州机房和广州机房,这时就可以给杭州机房的Service微服务起一个集群名称(HZ) ,给广州机房的Service微服务起一个集群名称(GZ),还可以尽量让同一个机房的微服务互相调用,以提升性能。
    • 最后是Instance,就是微服务的实例
    • Namespace > Group > Service > Cluster > Instance

集群/持久化

概述
  • https://nacos.io/zh-cn/docs/cluster-mode-quick-start.html

    1
    2
    3
    4
    5
    6
    7
    				 请求
    ↙ ↘
    Nginx1 Nginx2
    ↙ ↓ ↘
    Nacos1 Nacos2 Nacos3

    高可用MySQL集群
  • 默认Nacos使用嵌入式数据库实现数据的存储。Nacos采用了集中式存储的方式来支持集群化部署,目前只支持MySQL的存储

  • 三种部署模式

    • 单机模式:用于测试和单机试用
    • 集群模式:用于生产环境,确保高可用
    • 多集群模式:用于多数据中心场景
持久化配置
  • nacos-server\nacos\conf录下找到nacos-mysql.sql文件,执行脚本

  • nacos-server\nacos\conf目录下找到application.properties,添加配置

    1
    2
    3
    4
    5
    6
    spring.datasource.platform=mysql

    db.num=1
    db.url.0=jdbc:mysql://localhost:3306/nacos_devtest?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
    db.user=root
    db.password=root

集群配置

  • 配置持久化

  • 编辑cluster.conf

    1
    2
    3
    4
    5
    6
    # 不能写127.0.0.1,必须是Linux命令hostname -i能够识别的IP
    # it is ip
    192.168.31.10:1111
    192.168.31.10:2222
    192.168.31.10:3333

  • 编辑Nacos的启动脚本startup.sh,使它能够接受不同的启动端口(新版本自带)

  • 启动方式

    1
    startup.sh - p 端口号
Nginx配置
  • nginx.conf

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    upstream cluster {
    server 192.168.31.10:1111;
    server 192.168.31.10:2222;
    server 192.168.31.10:3333;
    }

    server {
    listen 80;
    server_name localhost;
    }

    location / {
    proxy_pass http://cluster;
    }
  • 指定启动

    1
    nginx -c /etc/nginx/nginx.conf

Seata

概述

  • https://seata.io/zh-cn/
  • 开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务
    • 一次业务操作需要跨多个数据源或需要跨多个系统进行远程调用,就会产生分布式事务问题
  • @GlobalTransactional加在业务类上
  • 分布式事务处理过程的一ID+三组件模型
    • Transaction ID XID:全局唯一的事务ID
    • 三组件概念
      • TC (Transaction Coordinator) - 事务协调者:维护全局和分支事务的状态,驱动全局事务提交或回滚。
      • TM (Transaction Manager) - 事务管理器:定义全局事务的范围:开始全局事务、提交或回滚全局事务。
      • RM (Resource Manager) - 资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚
  • 过程
    1. TM向TC申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的XID
    2. XID在微服务调用链路的上下文中传播
    3. RM向TC注册分支事务,将其纳入XID对应全局事务的管辖
    4. TM向TC发起针对XID的全局提交或回滚决议
    5. TC调度XID下管辖的全部分支事务完成提交或回滚请求

步骤

  • 备份file.conf文件并修改

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    store {
    ...
    mode = "db"
    ...
    db {
    ...
    url = "jdbc:mysql://172.17.0.2:3306/seata?rewriteBatchedStatements=true"
    user = "root"
    password = "root"
    ...
    }
    ...
    }
  • 新建数据库并导入db_store.sql表(新版需要在https://seata.io/zh-cn/blog/download.html下载source文件,在文件的/script/db下)

  • 修改registry.conf配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    registry {
    type = "nacos"

    nacos {
    serverAddr = "127.0.0.1:8848"
    namespace = ""
    group = "SEATA_GROUP"
    username = "nacos"
    password = "nacos"
    dataId = "seataServer.properties"
    }
    ...
    }
    ...
    config {
    type = "nacos"

    nacos {
    serverAddr = "127.0.0.1:8848"
    namespace = ""
    group = "SEATA_GROUP"
    username = "nacos"
    password = "nacos"
    dataId = "seataServer.properties"
    }

    }

  • 复制config.txt配置和nacos-config.sh配置

    • config.txt配置放在安装seata的目录下,与bin目录同级
    • nacos-config.shcopy下来的文件放在安装seata目录下的conf目录
  • 修改config.txt中的部分配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    ...
    service.vgroupMapping.my_test_tx_group=fsp_tx_group
    ...
    store.mode=db
    ...
    store.db.driverClassName=com.mysql.cj.jdbc.Driver
    store.db.url=jdbc:mysql://172.17.0.2:3306/seata?useUnicode=true&rewriteBatchedStatements=true
    store.db.user=root
    store.db.password=root
    ...
  • 将seata的配置导入到nacos的配置中心

    1
    2
    sh nacos-config.sh -h localhost -p 8848 -g SEATA_GROUP  -u nacos -w nacos
    # 命令解析:-h -p 指定nacos的端口地址;-g 指定配置的分组,注意,是配置的分组;-t 指定命名空间id; -u -w指定nacos的用户名和密码,同样,这里开启了nacos注册和配置认证的才需要指定

业务数据库准备

  • 创建三个服务,一个订单服务,一个库存服务,一个账户服务

  • 当用户下单时,会在订单服务中创建一个订单, 然后通过远程调用库存服务来扣减下单商品的库存,再通过远程调用账户服务来扣减用户账户里面的余额,最后在订单服务中修改订单状态为已完成,该操作跨越三个数据库,有两次远程调用,很明显会有分布式事务问题

  • 下订单—>扣库存—>减账户(余额)

  • SQL

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    CREATE DATABASE seata_order;
    CREATE DATABASE seata_storage;
    CREATE DATABASE seata_account;

    USE seata_order;
    CREATE TABLE t_order
    (
    `id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
    `user_id` BIGINT(11) DEFAULT NULL COMMENT '用户id',
    `product_id` BIGINT(11) DEFAULT NULL COMMENT '产品id',
    `count` INT(11) DEFAULT NULL COMMENT '数量',
    `money` DECIMAL(11, 0) DEFAULT NULL COMMENT '金额',
    `status` INT(1) DEFAULT NULL COMMENT '订单状态: 0:创建中; 1:已完结'
    ) ENGINE = INNODB
    AUTO_INCREMENT = 1
    DEFAULT CHARSET = utf8;

    USE seata_storage;
    CREATE TABLE t_storage
    (
    `id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
    `product_id` BIGINT(11) DEFAULT NULL COMMENT '产品id',
    `total` INT(11) DEFAULT NULL COMMENT '总库存',
    `used` INT(11) DEFAULT NULL COMMENT '已用库存',
    `residue` INT(11) DEFAULT NULL COMMENT '剩余库存'
    ) ENGINE = INNODB
    AUTO_INCREMENT = 1
    DEFAULT CHARSET = utf8;
    INSERT INTO seata_storage.t_storage(`id`, `product_id`, `total`, `used`, `residue`)
    VALUES ('1', '1', '100', '0', '100');

    USE seata_account;
    CREATE TABLE t_account
    (
    `id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'id',
    `user_id` BIGINT(11) DEFAULT NULL COMMENT '用户id',
    `total` DECIMAL(10, 0) DEFAULT NULL COMMENT '总额度',
    `used` DECIMAL(10, 0) DEFAULT NULL COMMENT '已用余额',
    `residue` DECIMAL(10, 0) DEFAULT '0' COMMENT '剩余可用额度'
    ) ENGINE = INNODB
    AUTO_INCREMENT = 1
    DEFAULT CHARSET = utf8;
    INSERT INTO seata_account.t_account(`id`, `user_id`, `total`, `used`, `residue`)
    VALUES ('1', '1', '1000', '0', '1000');
  • 按照上述3库分别建对应的回滚日志表

    • 订单-库存-账户3个库下都需要建各自的回滚日志表
    • \seata-server-0.9.0\seata\conf目录下的db_ undo_ log.sql
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    CREATE TABLE `undo_log`
    (
    `id` bigint(20) NOT NULL AUTO_INCREMENT,
    `branch_id` bigint(20) NOT NULL,
    `xid` varchar(100) NOT NULL,
    `context` varchar(128) NOT NULL,
    `rollback_info` longblob NOT NULL,
    `log_status` int(11) NOT NULL,
    `log_created` datetime NOT NULL,
    `log_modified` datetime NOT NULL,
    PRIMARY KEY (`id`),
    UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
    ) ENGINE = InnoDB
    AUTO_INCREMENT = 1
    DEFAULT CHARSET = utf8;

配置搭建

  • 下订单 -> 减库存 -> 扣余额 -> 改(订单)状态

  • seata-order-service2001

    • pom

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      <dependencies>
      <!--nacos-->
      <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
      </dependency>
      <!--seata-->
      <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
      </dependency>
      <!--feign-->
      <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-openfeign</artifactId>
      <version>3.1.0</version>
      </dependency>
      <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-loadbalancer -->
      <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-loadbalancer</artifactId>
      </dependency>
      <!--web-actuator-->
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
      </dependency>
      <!--mysql-druid-->
      <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      </dependency>
      <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid-spring-boot-starter</artifactId>
      </dependency>
      <dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-starter</artifactId>
      </dependency>
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
      </dependency>
      <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
      </dependency>
      </dependencies>
    • yaml

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      server:
      port: 2001

      spring:
      application:
      name: seata-order-service
      cloud:
      nacos:
      discovery:
      server-addr: http://192.168.31.186:8848
      datasource:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/seata_order
      username: root
      password: root

      logging:
      level:
      io:
      seata: info

      mybatis:
      mapperLocations: classpath:mapper/*.xml

      seata:
      # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址
      registry:
      # 参考tc服务自己的registry.conf中的配置
      type: nacos
      # tc
      nacos:
      server-addr: 192.168.31.186:8848
      namespace: ""
      group: SEATA_GROUP
      # tc服务在nacos中的服务名称
      application: seata-server
      username: nacos
      password: nacos
      # 事务组,根据这个获取tc服务的cluster名称
      tx-service-group: fsp_tx_group
      service:
      vgroup-mapping:
      # 事务组与TC服务cluster的映射关系
      fsp_tx_group: default
    • domain

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      @Data
      @NoArgsConstructor
      @AllArgsConstructor
      public class CommonResult<T> {

      private Integer code;
      private String message;
      private T data;

      public CommonResult(Integer code, String message) {
      this(code, message, null);
      }

      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      @Data
      @NoArgsConstructor
      @AllArgsConstructor
      public class Order {

      private Long id;
      private Long userId;
      private Long productId;
      private Integer count;
      private BigDecimal money;
      //订单状态:0:创建中;1:已完结
      private Integer status;

      }

@GlobalTransactional

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {

@Resource
private OrderDao orderDao;
@Resource
private StorageService storageService;
@Resource
private AccountService accountService;

/**
* 创建订单->调用库存服务扣减库存->调用账户服务扣减账户余额->修改订单状态
* 简单说:下订单->扣库存->减余额->改状态
*/
@Override
@GlobalTransactional(name = "fsp-create-order", rollbackFor = Exception.class)
public void create(Order order) {
log.info("----->开始新建订单");
//1 新建订单
orderDao.create(order);
//2 扣减库存
log.info("----->订单微服务开始调用库存,做扣减Count");
storageService.decrease(order.getProductId(), order.getCount());
log.info("----->订单微服务开始调用库存,做扣减end");
//3 扣减账户
log.info("----->订单微服务开始调用账户,做扣减Money");
accountService.decrease(order.getUserId(), order.getMoney());
log.info("----->订单微服务开始调用账户,做扣减end");
//4 修改订单状态,从零到1,1代表已经完成
log.info("----->修改订单状态开始");
orderDao.update(order.getUserId(), 0);
log.info("----->修改订单状态结束");
log.info("----->下订单结束了,O(∩_∩)O哈哈~");
}

}

原理

  • 步骤
    • TM开启分布式事务(TM向TC注册全局事务记录)
    • 按业务场景,编排数据库、服务等事务内资源(RM向TC汇报资源准备状态)
    • TM结束分布式事务,事务一阶段结束(TM通知TC提交/回滚分布式事务)
    • TC汇总事务信息,决定分布式事务是提交还是回滚
    • TC通知所有RM提交/回滚资源,事务二阶段结束
    • TC(seata服务器)
    • TM(@GlobalTransactional)
    • RM(数据库)
  • 详细步骤
    • 一阶段加载
      • 拦截“业务SQL”,解析SQL语义,找到“业务SQL” 要更新的业务数据,在业务数据被更新前,将其保存成”before image”,执行“业务SQL” 更新业务数据,在业务数据更新之后,其保存成”after image”,最后生成行锁
    • 二阶段提交
      • 二阶段如果顺利提交的话,因为”业务SQL”在一阶段已经提交至数据库,所以Seata框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可
    • 二阶段回滚
      • 二阶段如果是回滚的话,Seata 就需要回滚一阶段已经执行的 “业务SQL”,还原业务数据。
      • 回滚方式便是用”before image”还原业务数据;但在还原前要首先要校验脏写,对比“数据库当前业务数据”和”after image”。
      • 如果两份数据完全一致就说明没有脏写, 可以还原业务数据,如果不一致就说明有脏写, 出现脏写就需要转人工处理

RocketMQ

一款开源的分布式消息系统,基于高可用分布式集群技术,提供低延时的、高可靠的消息发布与订阅服务

Dubbo

Apache Dubbo™ 是一款高性能 Java RPC 框架

OSS

阿里云对象存储服务(Object Storage Service,简称 OSS),是阿里云提供的海量、安全、低成本、高可靠的云存储服务。您可以在任何应用、任何时间、任何地点存储和访问任意类型的数据

SchedulerX

阿里中间件团队开发的一款分布式任务调度产品,提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务

SMS

覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道

雪花算法

概述

  • 在复杂分布式系统中,往往需婴对大量的数据和消息进行唯一标识
  • 硬性要求
    • 全局唯一:不能出现重复的ID号,既然是唯一-标识,这是最基本的要求
    • 趋势递增:在MySQL的InnoDB引擎中使用的是聚集索引,由于多数RDBMS使用Btree的数据结构来存储索引数据,在主键的选择上面我们应该尽量使用有序的主键保证写入性能。
    • 单调递增:保证下一个ID一定大于上一个ID,例如事务版本号、IM增量消息、排序等特殊需求
    • 信息安全:如果ID是连续的,恶意用户的扒取工作就非常容易做了,直接按照顺序下载指定URL即可。如果是订单号就更危险了,竞对可以直接知道我们一天的单量。所以在一些应用场景下,需要ID无规则不规则,让竞争对手否好猜。
    • 含时间戳:这样就能够在开发中快速了解这个分布式id的生成时间。
  • ID号生成系统的可用性要求
    • 高可用:发一个获取分布式ID的请求,服务器就要保证99.999%的情况下给我创建一个唯一分布式ID。
    • 低延迟:发一个获取分布式ID的请求,服务器就要快,极速。
    • 高QPS:假如并发一口气10万个创建分布式ID请求同时杀过来,服务器要顶的住且一下子成功创建10万个分布式ID
  • 一般通用方案
    • UUID
      • 优点:本地生成,没有网络消耗
      • 缺点:入数据库性能差
        • MySQL官方推荐主键要尽量越短越好
        • MySQL的索引是通过B+树来实现的,每一次新的UUID数据的插入,为了查询的优化,都会对索引底层的B+树进行修改,因为UUID数据是无序的,所以每一次UUID数据的插入都会对主键地械的B+树进行很大的修改,这一点很不好。 插入完全无序,不但会导致一-些中间节点产生分裂,也会白白创造出很多不饱和的节点,这样大大降低了数据库插入的性能
    • 数据库自增主键
      • 优点:适合单机
        • 数据库自增ID和MySQL数据库的replace into实现的
        • 跟inset功能类似,不同点在于:replace into首先尝试插入数据列表中,如果发现表中已经有此行数据(根据主键或唯一索引判断)则先删除,再插入。否则直接插入新数据
      • 缺点:不适合集群
        • 系统水平扩展比较困难
        • 数据库压力很大
    • Redis
      • 优点:天生保证原子性,可以使用原子操作INCR和INCRBY来实现
        • 在Redis集群情况下,同样和MySQL一样需要设置不同的增长步长,同时key一定要设置有效期可以使用Redis集群来获取更高的吞吐量
        • 假如一个集群中有5台Redis。可以初始化每台Redis的值分别是1,2,3,4,5,然后步长都是5

雪花算法

概述

  • 每秒能够产生26万个自增可排序的ID
  • 能够按照时间有序生成
  • 生成ID的结果是一个64bit大小的整数, 为一个Long型(转换成字符串后长度最多19)
  • 分布式系统内不会产生ID碰撞(由datacenter和workerld作区分)并且效率较高
  • 缺点:
    • 依赖机器时钟,如果机器时钟回拨,会导致重复ID生成。
    • 在单机上是递增的,但是由于设计到分布式环境,每台机器上的时钟不可能完全同步,有时候会出现不是全局递增的情况。(此缺点可以认为无所谓,一般分布式ID只要求趋势递增,并不会严格要求递增,90%的需求都只要求趋势递增)

结构

  • 1bit符号位
    • 不用,因为二进制中最高位是符号位,1表示负数,0表示正数。生成的id一般都是用整数,所以最高位固定为0
  • 41bit时间戳位
    • 41位可以表示 2^41 − 1个毫秒的值,转化成单位年则是69年(到2039年)
  • 10bit工作进程位
    • 工作机器ID,用来记录工作机器ID
    • 可以部署1024个节点,包括5位DataCenterId和5位Workerld
  • 12bit序列号为
    • 用来记录同毫秒内产生的不同id
    • 可以表示同一机器同一时间截(毫秒)内产生的4095个ID序号

示例

  • pom

    1
    2
    3
    4
    5
    <dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-captcha</artifactId>
    <version>4.6.8</version>
    </dependency>
  • java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    @Slf4j
    @Component
    public class IdGeneratorSnowflake{
    private long workerId = 0;
    private long datacenterId = 1;
    private Snowflake snowflake = IdUtil.createSnowflake(workerId, datacenterId);

    public synchronized long snowflakeId(){
    return snowflake.nextId();
    }

    public synchronized long snowflakeId(long workerId, long datacenterId){
    Snowflake snowflake = IdUtil.createSnowflake(workerId, datacenterId);
    return snowflake.nextId();
    }

    public static void main(String[] args){
    IdGeneratorSnowflake idGenerator = new IdGeneratorSnowflake();
    System.out.println(idGenerator.snowflakeId());

    ExecutorService threadPool = Executors.newFixedThreadPool(5);
    for (int i = 1; i <= 20; i++){
    threadPool.submit(() -> {
    System.out.print1n(idGenerator.snowflakeId());
    });
    }

    threadPool.shutdown();

    }
    }