SpringCloud Alibaba总结(一)

273 阅读22分钟

1. SpringCloud Alibaba基础框架搭建

1.1 创建maven项目

打开Idea,File->New->Maven

image.png

1.2 修改pom.xml内容

创建完成后,修改一下pom.xml文件内容,效果如下 :

image.png

pom.xml内容 :

<?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">

    <modelVersion>4.0.0</modelVersion>
    <groupId>tm.learn</groupId>
    <artifactId>TM-Learn-SpringCloud-Alibaba</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>TM-Learn-SpringCloud-Alibaba</name>
    <packaging>pom</packaging>
    <description>TM-Learn-SpringCloud-Alibaba</description>

    <properties>
        <spring.boot.version>2.6.3</spring.boot.version>
        <spring.cloud.dependencies.version>2021.0.1</spring.cloud.dependencies.version>
        <spring.cloud.alibaba.version>2021.0.1.0</spring.cloud.alibaba.version>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

    <dependencyManagement>
        <dependencies>

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

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

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring.boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring.cloud.dependencies.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring.cloud.alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

        </dependencies>
    </dependencyManagement>

</project>

1.3 添加.gitignore文件

image.png

# Compiled class file
*.class
target

# Log file
*.log

# BlueJ files
*.ctxt

# Mobile Tools for Java (J2ME)
.mtj.tmp/

# Package Files #
*.jar
*.war
*.ear
*.zip
*.tar.gz
*.rar
*.iml
.idea

# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*

2. 创建 tm-springcloud-alibaba-common 模块

根项目名称上右键 New->Module 创建 tm-springcloud-alibaba-common 模块

image.png

创建完成后,先创建一些基础文件目录,用于将来开发,并且修改pom.xml内容,添加一些公共依赖,效果如下 :

image.png

pom.xml 内容

<?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>TM-Learn-SpringCloud-Alibaba</artifactId>
        <groupId>tm.learn</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>tm-springcloud-alibaba-common</artifactId>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <mybatisplus.version>3.4.2</mybatisplus.version>
        <mysql.version>8.0.25</mysql.version>
        <commons.lang.version>3.7</commons.lang.version>
        <commons.io.version>2.5</commons.io.version>
        <fastjson.version>1.2.76</fastjson.version>
        <hutool.version>5.1.2</hutool.version>
        <gson.version>2.8.6</gson.version>
        <knife4j.version>2.0.8</knife4j.version>
        <lombok.version>1.18.18</lombok.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <version>${knife4j.version}</version>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>${gson.version}</version>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>${hutool.version}</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>${commons.lang.version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fastjson.version}</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatisplus.version}</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>${commons.io.version}</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <skip>true</skip>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

3. 创建 tm-springcloud-alibaba-nacos-provider 模块

3.1 nacos配置命名空间

创建nacos模块之前,首先登录nacos服务,配置三个命名空间,分别为dev、prod、test,代表三个环境

image.png

3.2 springcloud添加nacos模块

根项目名称上右键 New->Module->Spring Initializr 创建 tm-springcloud-alibaba-nacos-provider 模块

3.2.1 修改pom.xml内容

主要包括以下依赖 : mysqldruidcommonnacos-discoverynacos-configbootstrap

<?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>
        <artifactId>TM-Learn-SpringCloud-Alibaba</artifactId>
        <groupId>tm.learn</groupId>
        <version>1.0-SNAPSHOT</version>
        <relativePath/>
    </parent>
    <groupId>com.learn</groupId>
    <artifactId>nacos</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>nacos</name>
    <description>Learn For Nacos Provider</description>
    <properties>
        <java.version>1.8</java.version>
        <mysql.version>8.0.21</mysql.version>
        <druid.version>1.1.10</druid.version>
        <common.version>1.0-SNAPSHOT</common.version>
        <bootstrap.version>3.1.0</bootstrap.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${druid.version}</version>
        </dependency>
        <dependency>
            <groupId>tm.learn</groupId>
            <artifactId>tm-springcloud-alibaba-common</artifactId>
            <version>${common.version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
            <version>${bootstrap.version}</version>
        </dependency>
    </dependencies>
</project>

3.2.2 修改bootstrap.yml内容

注意这里名字是bootstrap.yml不是application.yml

server:
  port: 8090
spring:
  application:
    name: nacos-provider
  profiles:
    active: test
  datasource:
    url: jdbc:mysql://192.168.198.100:3306/learn?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false&allowMultiQueries=true
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456

# dev
---
spring:
  profiles: dev
  cloud:
    nacos:
      config:
        # 配置中心地址
        server-addr: 192.168.198.100:8848
        # 文件后缀,可以省略不配置
        file-extension: yml
        # 命名空间 默认public
        namespace: 0c8d097b-2aaa-45f4-889a-44545ae50d3c
        # 配置中心的配置文件分组
        group: DEFAULT_GROUP
      #        prefix: nacos-provider
      discovery:
        # 服务发现地址
        server-addr: 192.168.198.100:8848
        # 需要注册到的命名空间
        namespace: 0c8d097b-2aaa-45f4-889a-44545ae50d3c
        # 注册服务的默认分组
        group: DEFAULT_GROUP

# prod
---
spring:
  profiles: prod
  cloud:
    nacos:
      config:
        # 配置中心地址
        server-addr: 192.168.198.100:8848
        # 文件后缀,可以省略不配置
        file-extension: yml
        # 命名空间 默认public
        namespace: 91785852-93d6-4696-b20a-ba97c57989b7
        # 配置中心的配置文件分组
        group: DEFAULT_GROUP
      #        prefix: nacos-provider
      discovery:
        # 服务发现地址
        server-addr: 192.168.198.100:8848
        # 需要注册到的命名空间
        namespace: 91785852-93d6-4696-b20a-ba97c57989b7
        # 注册服务的默认分组
        group: DEFAULT_GROUP
---
# test
spring:
  profiles: test
  cloud:
    nacos:
      config:
        # 配置中心地址
        server-addr: 192.168.198.100:8848
        # 文件后缀,可以省略不配置
        file-extension: yml
        # 命名空间 默认public
        namespace: d441c00a-a930-4324-a71f-2751ec071f24
        # 配置中心的配置文件分组
        group: DEFAULT_GROUP
      #        prefix: nacos-provider
      discovery:
        # 服务发现地址
        server-addr: 192.168.198.100:8848
        # 需要注册到的命名空间
        namespace: d441c00a-a930-4324-a71f-2751ec071f24
        # 注册服务的默认分组
        group: DEFAULT_GROUP

3.2.3 启动类添加注解

//开启nacos服务的发现
@EnableDiscoveryClient
@SpringBootApplication
public class NacosApplication {
    public static void main(String[] args) {
        SpringApplication.run(NacosApplication.class, args);
    }
}

3.2.4 测试类

注意这里需要加上@RefreshScope实现nacos配置自动更新,否则不会自动刷新

@Slf4j
@RestController
@RefreshScope//实现nacos配置自动更新
@RequestMapping("/tm")
@Api(tags = "生产者测试类")
public class ProviderController {

    @Value("${testName}")
    private String testName;

    @GetMapping("/msg")
    @ApiOperation(value = "provider测试")
    public R getMsg(){
        return R.ok(testName);
    }

}

3.3 nacos创建各个环境配置文件

三个配置文件,名称格式为

(1) nacos-provider = spring.application.name 的值

(2) -dev 或 -test 或 -prod = spring.profiles.active 的值

(3) yml = spring.cloud.nacos.config.file-extension 的值

dev :nacos-provider-dev.yml image.png prod :nacos-provider-prod.yml image.png test : nacos-provider-test.yml image.png

3.4 测试

启动nacos-provider服务和nacos服务,此时启动环境为test,可以在nacos的服务列表的test栏里看到nacos-provider服务已经被注册上去了。

image.png

使用浏览器访问:http://localhost:8090/tm/msg 调用接口,可以看到结果为

{"code":0,"data":"nacos-provider-test","msg":"执行成功"}

即确实读到了nacos里配置文件的值。此时我们修改配置文件里的值,也可以看到配置自动更新了,如果没有自动更新,需要检查以下对应的controller类上是否添加了自动刷新注解@RefreshScope

4. 创建 tm-springcloud-alibaba-gateway 模块

4.1 修改bootstrap.yml内容

注意 : 如果想要使用 uri: lb://nacos-provider 而不是 uri: http://192.168.198.100:8090 则需要配置 discovery.locator.enabled: true

server:
  port: 8091
spring:
  profiles:
    active: dev
  application:
    name: api-gateway
  cloud:
    gateway:
      routes:
        # 当前路由的唯一标识
        - id: tm-nacos
          # 请求要转发到的地址
#          uri: http://192.168.198.100:8090
          uri: lb://nacos-provider
          # 路由的优先级,数字越小,优先级越高
          order: 1
          # 断言(路由转发要满足的条件)
          predicates:
            - Path=/nacos-provider/**
          # 过滤器,即转发之前去掉1层路径
          filters:
            - StripPrefix=1
      discovery:
        locator:
          enabled: true

# dev
---
spring:
  profiles: dev
  cloud:
    nacos:
      config:
        # 配置中心地址
        server-addr: 192.168.198.100:8848
        # 文件后缀,可以省略不配置
        file-extension: yml
        # 命名空间 默认public
        namespace: 0c8d097b-2aaa-45f4-889a-44545ae50d3c
        # 配置中心的配置文件分组
        group: DEFAULT_GROUP
      #        prefix: nacos-provider
      discovery:
        # 服务发现地址
        server-addr: 192.168.198.100:8848
        # 需要注册到的命名空间
        namespace: 0c8d097b-2aaa-45f4-889a-44545ae50d3c
        # 注册服务的默认分组
        group: DEFAULT_GROUP

# prod
---
spring:
  profiles: prod
  cloud:
    nacos:
      config:
        # 配置中心地址
        server-addr: 192.168.198.100:8848
        # 文件后缀,可以省略不配置
        file-extension: yml
        # 命名空间 默认public
        namespace: 91785852-93d6-4696-b20a-ba97c57989b7
        # 配置中心的配置文件分组
        group: DEFAULT_GROUP
      #        prefix: nacos-provider
      discovery:
        # 服务发现地址
        server-addr: 192.168.198.100:8848
        # 需要注册到的命名空间
        namespace: 91785852-93d6-4696-b20a-ba97c57989b7
        # 注册服务的默认分组
        group: DEFAULT_GROUP
---
# test
spring:
  profiles: test
  cloud:
    nacos:
      config:
        # 配置中心地址
        server-addr: 192.168.198.100:8848
        # 文件后缀,可以省略不配置
        file-extension: yml
        # 命名空间 默认public
        namespace: d441c00a-a930-4324-a71f-2751ec071f24
        # 配置中心的配置文件分组
        group: DEFAULT_GROUP
      #        prefix: nacos-provider
      discovery:
        # 服务发现地址
        server-addr: 192.168.198.100:8848
        # 需要注册到的命名空间
        namespace: d441c00a-a930-4324-a71f-2751ec071f24
        # 注册服务的默认分组
        group: DEFAULT_GROUP

4.2 修改pom.xml内容

这里要注意:

(1) 一定要加loadbalancer依赖,如果不加可能会报503错误。

503错误的原因是Spring Cloud 2020.0.0版本之前会自动引入Ribbon依赖, 功能类似于loadbalancer,因为Netflix公司停止维护Ribbon后,Spring Cloud 2020.0.0以后的版本使用了loadbalancer替代了Ribbon,但是loadbalancer依赖需要手动引入,所以如果你使用的版本是Spring Cloud 2020.0.0以上,则需要添加loadbalancer依赖。

(2) 不要添加common依赖 tm-springcloud-alibaba-common 这是因为common依赖中存在mysql、druid相关的驱动,需要我们在配置文件中添加mysql相关的配置才不会报错,而gateway中我们又不需要操作数据库,所以就不用添加了。或者将这些依赖移出common包也是可以的。

***************************
APPLICATION FAILED TO START
***************************

Description:

Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.

Reason: Failed to determine a suitable driver class


Action:

Consider the following:
	If you want an embedded database (H2, HSQL or Derby), please put it on the classpath.
	If you have database settings to be loaded from a particular profile you may need to activate it (the profiles dev are currently active).
	

(3) 在springboot整合gateway时, gateway组件中的 【spring-boot-starter-webflux】 和 springboot作为web项目启动必不可少的 [spring-boot-starter-web] 会出现冲突,所以处理办法有两个,或者你不引入,或者使用exclusions标签来排除。

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

依赖如下 :

<?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>
        <artifactId>TM-Learn-SpringCloud-Alibaba</artifactId>
        <groupId>tm.learn</groupId>
        <version>1.0-SNAPSHOT</version>
        <relativePath/>
    </parent>
    <groupId>com.learn</groupId>
    <artifactId>gateway</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>gateway</name>
    <description>Learn For Gateway</description>

    <properties>
        <java.version>1.8</java.version>
        <bootstrap.version>3.1.0</bootstrap.version>
        <loadbalancer.version>3.0.2</loadbalancer.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
            <version>${bootstrap.version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-loadbalancer</artifactId>
        </dependency>
    </dependencies>

</project>

4.3 启动类上添加注解

@EnableDiscoveryClient
@SpringBootApplication
public class GatewayApplication {

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

}

4.4 nacos里创建各个环境配置文件

三个配置文件 :

api-gateway-dev.yml,api-gateway-prod.yml,api-gateway-test.yml

内容分别为 :

testGatewayName: gateway-dev、testGatewayName: gateway-prod、testGatewayName: gateway-test

4.5 测试

(1) 首先直接调用nacos-provider服务 : http://localhost:8090/tm/msg 可以访问成功

(2) 然后测试通过调用gateway来路由到nacos-provider : http://localhost:8091/nacos-provider/tm/msg 可以访问成功

(3) 最后直接调用api-gateway服务 : http://localhost:8091/tm/msg 也可以调用成功,说明nacos中的配置文件生效。

服务列表里也可以看到gateway成功注册

image.png

5. 创建 tm-springcloud-alibaba-openfeign 模块

5.1 创建服务生产者代码

这里以nacos-provider服务作为服务提供者

在对应的controller中添加如下代码 :

@RefreshScope//实现nacos配置自动更新
@RestController
@RequestMapping("/tm")
public class ProviderController {

    @GetMapping("/getMsgById/{id}")
    public R getMsgById(@PathVariable("id") Long id) {
        return R.ok("查询成功,id为"+id);
    }

    @PostMapping("insertMsg")
    public R insertMsg(@RequestBody User user) {
        return R.ok(user);
    }
}

网关添加配置

- id: tm-openfeign
  # 请求要转发到的地址
  # uri: http://192.168.198.100:8090
  uri: lb://api-openfeign
  # 路由的优先级,数字越小,优先级越高
  order: 1
  # 断言(路由转发要满足的条件)
  predicates:
    - Path=/api-openfeign/**
  # 过滤器,即转发之前去掉1层路径
  filters:
    - StripPrefix=1

这里user类自己随便写一个就可以了,可以放到common模块里

5.2 创建服务消费者代码

5.2.1 修改pom.xml文件

<?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>
        <artifactId>TM-Learn-SpringCloud-Alibaba</artifactId>
        <groupId>tm.learn</groupId>
        <version>1.0-SNAPSHOT</version>
        <relativePath/>
    </parent>
    <groupId>com.learn</groupId>
    <artifactId>openfeign</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>openfeign</name>
    <description>Learn For OpenFeign</description>

    <properties>
        <java.version>1.8</java.version>
        <common.version>1.0-SNAPSHOT</common.version>
        <test.version>2.2.13.RELEASE</test.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>tm.learn</groupId>
            <artifactId>tm-springcloud-alibaba-common</artifactId>
            <version>${common.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <version>${test.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-loadbalancer</artifactId>
        </dependency>
    </dependencies>

</project>

5.2.2 修改bootstrap.yml文件

server:
  port: 8092
spring:
  profiles:
    active: dev
  application:
    name: api-openfeign
  datasource:
    url: jdbc:mysql://192.168.198.100:3306/learn?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false&allowMultiQueries=true
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456
# dev
---
spring:
  profiles: dev
  cloud:
    nacos:
      config:
        # 配置中心地址
        server-addr: 192.168.198.100:8848
        # 文件后缀,可以省略不配置
        file-extension: yml
        # 命名空间 默认public
        namespace: 0c8d097b-2aaa-45f4-889a-44545ae50d3c
        # 配置中心的配置文件分组
        group: DEFAULT_GROUP
      #        prefix: nacos-provider
      discovery:
        # 服务发现地址
        server-addr: 192.168.198.100:8848
        # 需要注册到的命名空间
        namespace: 0c8d097b-2aaa-45f4-889a-44545ae50d3c
        # 注册服务的默认分组
        group: DEFAULT_GROUP

# prod
---
spring:
  profiles: prod
  cloud:
    nacos:
      config:
        # 配置中心地址
        server-addr: 192.168.198.100:8848
        # 文件后缀,可以省略不配置
        file-extension: yml
        # 命名空间 默认public
        namespace: 91785852-93d6-4696-b20a-ba97c57989b7
        # 配置中心的配置文件分组
        group: DEFAULT_GROUP
      #        prefix: nacos-provider
      discovery:
        # 服务发现地址
        server-addr: 192.168.198.100:8848
        # 需要注册到的命名空间
        namespace: 91785852-93d6-4696-b20a-ba97c57989b7
        # 注册服务的默认分组
        group: DEFAULT_GROUP
---
# test
spring:
  profiles: test
  cloud:
    nacos:
      config:
        # 配置中心地址
        server-addr: 192.168.198.100:8848
        # 文件后缀,可以省略不配置
        file-extension: yml
        # 命名空间 默认public
        namespace: d441c00a-a930-4324-a71f-2751ec071f24
        # 配置中心的配置文件分组
        group: DEFAULT_GROUP
      #        prefix: nacos-provider
      discovery:
        # 服务发现地址
        server-addr: 192.168.198.100:8848
        # 需要注册到的命名空间
        namespace: d441c00a-a930-4324-a71f-2751ec071f24
        # 注册服务的默认分组
        group: DEFAULT_GROUP

5.2.3 启动类上添加启动注解

@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class OpenfeignApplication {

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

}

5.2.4 测试类

@Slf4j
@RefreshScope//实现nacos配置自动更新
@RestController
@RequestMapping("/tm")
public class OpenFeignController {

    @Autowired
    OpenFeignService openFeignService;

    @Value("${testOpenFeignName}")
    private String testOpenFeignName;

    @GetMapping("/msg")
    @ApiOperation(value = "openFeign测试")
    public R getMsg() {
        return R.ok(testOpenFeignName);
    }

    @GetMapping("/getMsgById/{id}")
    public R getMsgById(@PathVariable("id") Long id) {
        return openFeignService.getMsgById(id);
    }

    @PostMapping("/insertMsg")
    public R insertMsg(@RequestBody User user) {
        return openFeignService.insertMsg(user);
    }

}

=====================================================================

@Service
@FeignClient(name = "nacos-provider")//这里填写的就是你要远程调用的服务名
public interface OpenFeignService {

    @GetMapping("/tm/getMsgById/{id}")
    public R getMsgById(@PathVariable("id") Long id);

    @PostMapping("/tm/insertMsg")
    public R insertMsg(@RequestBody User user);

}

调用效果 :

(1) 访问 localhost:8092/tm/msg 验证nacos的配置文件是生效的 image.png

(2) 访问localhost:8091/api-openfeign/tm/msg验证通过网关的配置也是正确的 image.png

(3) 使用postman调用 localhost:8092/tm/getMsgById/1localhost:8091/api-openfeign/tm/getMsgById/1 ,调用成功,结果为 :

{
    "code": 0,
    "data": "查询成功,id为1",
    "msg": "执行成功"
}

(4) 使用postman调用 localhost:8092/tm/insertMsglocalhost:8091/api-openfeign/tm/insertMsg,参数为{"id":1,"name":"张三"} ,调用成功,结果为 :

{
    "code": 0,
    "data": {
        "id": 1,
        "name": "张三",
        "age": null,
        "tel": null,
        "sex": null,
        "address": null,
        "email": null,
        "birthday": null
    },
    "msg": "执行成功"
}

5.3 超时情况处理

5.3.1 bootstrap.yml添加配置

首先我们在消费者模块配置文件中添加配置 :

feign:
  client:
    # 添加此配置后修改超时时间就不需要重启服务
    refresh-enabled: true
    config:
      default:
        connectTimeout: 1000
        readTimeout: 1000

5.3.2 添加休眠代码

然后在生产者模块的方法中添加代码 Thread.sleep(1000)

@GetMapping("/getMsgById/{id}")
    public R getMsgById(@PathVariable("id") Long id) throws InterruptedException {
        Thread.sleep(1000);
        return R.ok("查询成功,id为"+id);
    }

由于我设置的超时时间就是1000,那么此接口的调用时间肯定是超过了1s,所以控制台报错,去掉此行代码后,发现接口调用成功,由此判定生效。

java.net.SocketTimeoutException: Read timed out

5.3.3 contextId属性配置

那么此种方式是针对所有的接口的配置,那么如果想要单独的针对某一个模块的配置添加的话,做法如下 :

FeignClient 注解上配置 contextId 属性,如 :

@Service
@FeignClient(name = "nacos-provider",contextId = "nacos-provider-core")
public interface OpenFeignService {

    @GetMapping("/tm/getMsgById/{id}")
    public R getMsgById(@PathVariable("id") Long id);

    @PostMapping("/tm/insertMsg")
    public R insertMsg(@RequestBody User user);

}

然后bootstrap.yml配置修改如下 :

即默认超时时间都为1s,但是 contextId = "nacos-provider-core" 对应的接口超时时间是2s。

feign:
  client:
    # 添加此配置后修改超时时间就不需要重启服务
    refresh-enabled: true
    config:
      default:
        connectTimeout: 1000
        readTimeout: 1000
      nacos-provider-core:
        connectTimeout: 2000
        readTimeout: 2000

此时虽然我在生产者代码里添加了休眠1s的代码 Thread.sleep(1000);,但是接口仍可以调用成功。

5.4 重试情况处理

5.4.1 添加配置文件

代码 new Default(100, TimeUnit.SECONDS.toMillis(1), 5) 的三个参数含义如下 :

  • period:周期,重试间隔时间
  • maxPeriod:最大周期,重试间隔时间按照一定的规则逐渐增大,但不能超过最大周期
  • maxAttempts:最大尝试次数,重试次数
import feign.RetryableException;
import feign.Retryer;
import java.util.concurrent.TimeUnit;

public class MyRetryer implements Retryer {
    @Override
    public void continueOrPropagate(RetryableException e) {
        throw e;
    }

    @Override
    public Retryer clone() {
        return new Default(100, TimeUnit.SECONDS.toMillis(1), 5);
    }
}

5.4.2 修改bootstrap.yml配置文件

添加 retryer 配置, 值 com.learn.openfeign.config.MyRetryer 为上面创建的MyRetryer类的路径

feign:
  client:
    # 添加此配置后修改超时时间就不需要重启服务
    refresh-enabled: true
    config:
      default:
        connectTimeout: 1000
        readTimeout: 1000
        retryer: com.learn.openfeign.config.MyRetryer

5.4.3 测试

将服务提供者的睡眠时间修改为超过配置的超时时间,以便可以多次触发重试,有利于看出效果。效果如下 :

调用1次,当前时间为2023-01-12 15:45:43:227
调用1次,当前时间为2023-01-12 15:45:44:290
调用1次,当前时间为2023-01-12 15:45:45:523
调用1次,当前时间为2023-01-12 15:45:46:877
调用1次,当前时间为2023-01-12 15:45:48:399

6. 创建 tm-springcloud-alibaba-sleuth 模块

链路追踪

6.1 整合sleuth

6.1.1 添加pom.xml依赖

这里我将sleuth相关的依赖都添加到了common里

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

pom.xml内容

<?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>
        <artifactId>TM-Learn-SpringCloud-Alibaba</artifactId>
        <groupId>tm.learn</groupId>
        <version>1.0-SNAPSHOT</version>
        <relativePath/>
    </parent>
    <groupId>com.learn</groupId>
    <artifactId>sleuth</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>tm-springcloud-alibaba-sleuth</name>
    <description>Learn For Sleuth</description>

    <properties>
        <java.version>1.8</java.version>
        <common.version>1.0-SNAPSHOT</common.version>
        <test.version>2.2.13.RELEASE</test.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>tm.learn</groupId>
            <artifactId>tm-springcloud-alibaba-common</artifactId>
            <version>${common.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <version>${test.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-loadbalancer</artifactId>
        </dependency>
    </dependencies>
</project>

6.1.2 添加bootstrap.yml配置

server:
  port: 8093
spring:
  profiles:
    active: dev
  application:
    name: api-sleuth
  datasource:
    url: jdbc:mysql://192.168.198.100:3306/learn?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false&allowMultiQueries=true
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456

feign:
  client:
    # 添加此配置后修改超时时间就不需要重启服务
    refresh-enabled: true
    config:
      default:
        connectTimeout: 1000
        readTimeout: 1000
# dev
---
spring:
  profiles: dev
  cloud:
    nacos:
      config:
        # 配置中心地址
        server-addr: 192.168.198.100:8848
        # 文件后缀,可以省略不配置
        file-extension: yml
        # 命名空间 默认public
        namespace: 0c8d097b-2aaa-45f4-889a-44545ae50d3c
        # 配置中心的配置文件分组
        group: DEFAULT_GROUP
      #        prefix: nacos-provider
      discovery:
        # 服务发现地址
        server-addr: 192.168.198.100:8848
        # 需要注册到的命名空间
        namespace: 0c8d097b-2aaa-45f4-889a-44545ae50d3c
        # 注册服务的默认分组
        group: DEFAULT_GROUP

# prod
---
spring:
  profiles: prod
  cloud:
    nacos:
      config:
        # 配置中心地址
        server-addr: 192.168.198.100:8848
        # 文件后缀,可以省略不配置
        file-extension: yml
        # 命名空间 默认public
        namespace: 91785852-93d6-4696-b20a-ba97c57989b7
        # 配置中心的配置文件分组
        group: DEFAULT_GROUP
      #        prefix: nacos-provider
      discovery:
        # 服务发现地址
        server-addr: 192.168.198.100:8848
        # 需要注册到的命名空间
        namespace: 91785852-93d6-4696-b20a-ba97c57989b7
        # 注册服务的默认分组
        group: DEFAULT_GROUP
---
# test
spring:
  profiles: test
  cloud:
    nacos:
      config:
        # 配置中心地址
        server-addr: 192.168.198.100:8848
        # 文件后缀,可以省略不配置
        file-extension: yml
        # 命名空间 默认public
        namespace: d441c00a-a930-4324-a71f-2751ec071f24
        # 配置中心的配置文件分组
        group: DEFAULT_GROUP
      #        prefix: nacos-provider
      discovery:
        # 服务发现地址
        server-addr: 192.168.198.100:8848
        # 需要注册到的命名空间
        namespace: d441c00a-a930-4324-a71f-2751ec071f24
        # 注册服务的默认分组
        group: DEFAULT_GROUP

6.1.3 启动类添加注解

@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class SleuthApplication {

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

}

6.1.4 gateway网关添加配置

- id: tm-sleuth
  # 请求要转发到的地址
  uri: lb://api-sleuth
  # 路由的优先级,数字越小,优先级越高
  order: 1
  # 断言(路由转发要满足的条件)
  predicates:
    - Path=/api-sleuth/**
  # 过滤器,即转发之前去掉1层路径
  filters:
    - StripPrefix=1

6.1.5 测试类

@Slf4j
@RefreshScope//实现nacos配置自动更新
@RestController
@RequestMapping("/tm")
public class SleuthController {

    @Autowired
    SleuthService sleuthService;

    @Value("${testSleuthName}")
    private String testSleuthName;

    @GetMapping("/msg")
    @ApiOperation(value = "sleuth测试")
    public R getMsg() {
        log.info("sleuth测试");
        return R.ok(testSleuthName);
    }

    @GetMapping("/getMsgById/{id}")
    public R getMsgById(@PathVariable("id") Long id) {
        log.info("getMsgById测试");
        return sleuthService.getMsgById(id);
    }

    @PostMapping("/insertMsg")
    public R insertMsg(@RequestBody User user) {
        log.info("insertMsg测试");
        return sleuthService.insertMsg(user);
    }

}

@Service
@FeignClient(name = "nacos-provider")//这里填写的就是你要远程调用的服务名
public interface SleuthService {

    @GetMapping("/tm/getMsgById/{id}")
    public R getMsgById(@PathVariable("id") Long id);

    @PostMapping("/tm/insertMsg")
    public R insertMsg(@RequestBody User user);

}

http://localhost:8093/tm/msg 直接访问接口,控制台日志打印效果为 :

2023-01-13 10:01:51.156  INFO [api-sleuth,6789636f0df505eb,6789636f0df505eb] 4824 --- [nio-8093-exec-1] c.l.sleuth.controller.SleuthController   : sleuth测试

http://localhost:8091/api-sleuth/tm/getMsgById/1 通过网关并远程调用nacos-provider服务访问,控制台日志打印效果为 :

2023-01-13 10:02:13.223  INFO [api-sleuth,380c08c2c366eec6,380c08c2c366eec6] 4824 --- [nio-8093-exec-2] c.l.sleuth.controller.SleuthController   : getMsgById测试

6.2 整合Zipkin

Zipkin 是用来图形化展示 Sleuth 收集来的信息的,这里使用docker启动一个zipkin服务,这里不做过多介绍。

启动之后,发现报错日志,信息为

Caused by: java.lang.NoClassDefFoundError: Could not initialize class org.mariadb.jdbc.internal.com.send.authentication.SendGssApiAuthPacket
        at jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[?:?]
        at jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source) ~[?:?]
        at jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source) ~[?:?]
        at java.lang.reflect.Constructor.newInstanceWithCaller(Unknown Source) ~[?:?]
        at java.lang.reflect.Constructor.newInstance(Unknown Source) ~[?:?]

经过网上查询,解决办法为使用mysql5.7或修改配置信息,这里为了简单起见,我又用docker开了一个mysql5.7的服务

6.2.1 修改bootstrap.yml配置文件

主要修改内容为 :

(1) mysql连接driver-class-name改成了5.7版本的

(2) 服务调用者即sleuth模块,添加了zipkin相关配置

server:
  port: 8093
spring:
  profiles:
    active: dev
  application:
    name: api-sleuth
  datasource:
    url: jdbc:mysql://192.168.198.100:3307/learn?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver
    username: root
    password: 123456
  sleuth:
    enabled: true
    sampler:
      rate: 10000 #每秒数据采集量,多余将丢弃
      probability: 1.0 #采样百分比:收集请求书数量的百分比。(默认为0.1,如果100次访问只将10次上传给zipkin )
  zipkin:
    sender:
      type: web
    # zipkin server的请求地址
    base-url: http://192.168.198.100:9411/
    #让nacos把它当成一个URL,而不要当做服务名
    discovery-client-enabled: false
    service:
      name: api-zipkin

feign:
  client:
    # 添加此配置后修改超时时间就不需要重启服务
    refresh-enabled: true
    config:
      default:
        connectTimeout: 1000
        readTimeout: 1000

# dev
---
spring:
  profiles: dev
  cloud:
    nacos:
      config:
        # 配置中心地址
        server-addr: 192.168.198.100:8848
        # 文件后缀,可以省略不配置
        file-extension: yml
        # 命名空间 默认public
        namespace: 0c8d097b-2aaa-45f4-889a-44545ae50d3c
        # 配置中心的配置文件分组
        group: DEFAULT_GROUP
      #        prefix: nacos-provider
      discovery:
        # 服务发现地址
        server-addr: 192.168.198.100:8848
        # 需要注册到的命名空间
        namespace: 0c8d097b-2aaa-45f4-889a-44545ae50d3c
        # 注册服务的默认分组
        group: DEFAULT_GROUP

# prod
---
spring:
  profiles: prod
  cloud:
    nacos:
      config:
        # 配置中心地址
        server-addr: 192.168.198.100:8848
        # 文件后缀,可以省略不配置
        file-extension: yml
        # 命名空间 默认public
        namespace: 91785852-93d6-4696-b20a-ba97c57989b7
        # 配置中心的配置文件分组
        group: DEFAULT_GROUP
      #        prefix: nacos-provider
      discovery:
        # 服务发现地址
        server-addr: 192.168.198.100:8848
        # 需要注册到的命名空间
        namespace: 91785852-93d6-4696-b20a-ba97c57989b7
        # 注册服务的默认分组
        group: DEFAULT_GROUP
---
# test
spring:
  profiles: test
  cloud:
    nacos:
      config:
        # 配置中心地址
        server-addr: 192.168.198.100:8848
        # 文件后缀,可以省略不配置
        file-extension: yml
        # 命名空间 默认public
        namespace: d441c00a-a930-4324-a71f-2751ec071f24
        # 配置中心的配置文件分组
        group: DEFAULT_GROUP
      #        prefix: nacos-provider
      discovery:
        # 服务发现地址
        server-addr: 192.168.198.100:8848
        # 需要注册到的命名空间
        namespace: d441c00a-a930-4324-a71f-2751ec071f24
        # 注册服务的默认分组
        group: DEFAULT_GROUP

6.2.2 添加依赖

sleuth模块添加mysql5的依赖

<mysql5version>5.1.47</mysql5version>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>${mysql5version}</version>
    <scope>runtime</scope>
</dependency>

common模块添加了zipkin相关的依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>

6.2.3 被调用模块添加zipkin配置

nacos-provider模块添加如下配置 :

spring:
  zipkin:
    sender:
      type: web
    # zipkin server的请求地址
    base-url: http://192.168.198.100:9411/
    #让nacos把它当成一个URL,而不要当做服务名
    discovery-client-enabled: false
    service:
      name: api-zipkin-nacos-provider

注意 : 如果没有配置,nacos-provider模块会打印出如下报错 : 访问的是localhost的ip

org.springframework.web.client.ResourceAccessException: I/O error on POST request for "http://localhost:9411/api/v2/spans": connect timed out; nested exception is java.net.SocketTimeoutException: connect timed out
	at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:785) ~[spring-web-5.3.15.jar:5.3.15]
	at org.springframework.cloud.sleuth.zipkin2.ZipkinRestTemplateWrapper.doExecute(ZipkinRestTemplateWrapper.java:69) ~[spring-cloud-sleuth-zipkin-3.1.1.jar:3.1.1]
	at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:660) ~[spring-web-5.3.15.jar:5.3.15]
	at org.springframework.cloud.sleuth.zipkin2.RestTemplateSender.post(RestTemplateSender.java:51) ~[spring-cloud-sleuth-zipkin-3.1.1.jar:3.1.1]
	at org.springframework.cloud.sleuth.zipkin2.RestTemplateSender.lambda$new$0(RestTemplateSender.java:44) ~[spring-cloud-sleuth-zipkin-3.1.1.jar:3.1.1]
	at org.springframework.cloud.sleuth.zipkin2.HttpSender.post(HttpSender.java:137) ~[spring-cloud-sleuth-zipkin-3.1.1.jar:3.1.1]
	at org.springframework.cloud.sleuth.zipkin2.HttpSender$HttpPostCall.doExecute(HttpSender.java:150) ~[spring-cloud-sleuth-zipkin-3.1.1.jar:3.1.1]
	at org.springframework.cloud.sleuth.zipkin2.HttpSender$HttpPostCall.doExecute(HttpSender.java:140) ~[spring-cloud-sleuth-zipkin-3.1.1.jar:3.1.1]
	at zipkin2.Call$Base.execute(Call.java:391) ~[zipkin-2.23.2.jar:na]
	at zipkin2.reporter.AsyncReporter$BoundedAsyncReporter.flush(AsyncReporter.java:299) ~[zipkin-reporter-2.16.3.jar:na]
	at zipkin2.reporter.AsyncReporter$Flusher.run(AsyncReporter.java:378) [zipkin-reporter-2.16.3.jar:na]
	at java.lang.Thread.run(Thread.java:748) [na:1.8.0_181]

6.2.4 测试

浏览器访问 : http://localhost:8091/api-sleuth/tm/getMsgById/1 ,调用成功

日志打印 :

# 这里的23f04450350b68fa就是trace ID
2023-01-13 15:00:58.190  INFO [api-zipkin,23f04450350b68fa,23f04450350b68fa] 24400 --- [nio-8093-exec-4] c.l.sleuth.controller.SleuthController   : getMsgById测试

浏览器访问 : http://192.168.198.100:9411/zipkin/ 右上角搜索 23f04450350b68fa ,点击run query可以看到结果如下 :

image.png

点击show按钮,调用者(sleuth)和被调用者(nacos-provider)链路的详细信息展示如下 :

image.png

image.png

重启zipkin服务,发现数据还在,即持久化生效。

7. 创建 tm-springcloud-alibaba-seata 模块

需要的seata依赖,可以创建在公用的common模块中。

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>

为了测试seata,这里需要准备 :

(1) 两个数据库,端口分别为3308 3309 并均创建数据库名为learn ,创建表undo_log,业务表 user

(2) 两个服务,分别连接上面的两个数据库

(3) seata-server这里是使用docker的方式安装的。

-- 业务表
CREATE TABLE `user` (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '姓名',
  `age` int DEFAULT NULL COMMENT '年龄',
  `tel` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '电话',
  `sex` int DEFAULT NULL COMMENT '性别 0:女 1:男',
  `address` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '地址',
  `email` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '邮箱',
  `birthday` date DEFAULT NULL COMMENT '出生日期',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;

-- 回滚表
CREATE TABLE `undo_log`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `context` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int(11) NOT NULL,
  `log_created` datetime(0) NOT NULL,
  `log_modified` datetime(0) NOT NULL,
  `ext` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `ux_undo_log`(`xid`, `branch_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

7.1 seata-server在nacos里的配置信息

这里要注意 : 如果调用接口报错io.seata.core.exception.RmTransactionException: Response[ TransactionException[Could not found global transaction xid = xxx.xxx.xxx.xxx:8091:36378942915387401, may be has finished.] ] 就说明是接口时间太长,默认是1000,可以改大一点。

io.seata.core.exception.RmTransactionException: Response[ TransactionException[Could not found global transaction xid = 192.168.198.100:8091:36378942915387401, may be has finished.] ]
	at io.seata.rm.AbstractResourceManager.branchRegister(AbstractResourceManager.java:69) ~[seata-all-1.4.2.jar:1.4.2]
	at io.seata.rm.DefaultResourceManager.branchRegister(DefaultResourceManager.java:96) ~[seata-all-1.4.2.jar:1.4.2]
	at io.seata.rm.datasource.ConnectionProxy.register(ConnectionProxy.java:272) ~[seata-all-1.4.2.jar:1.4.2]
	at io.seata.rm.datasource.ConnectionProxy.processGlobalTransactionCommit(ConnectionProxy.java:250) ~[seata-all-1.4.2.jar:1.4.2]
	at io.seata.rm.datasource.ConnectionProxy.doCommit(ConnectionProxy.java:230) ~[seata-all-1.4.2.jar:1.4.2]
	at io.seata.rm.datasource.ConnectionProxy.lambda$commit$0(ConnectionProxy.java:188) ~[seata-all-1.4.2.jar:1.4.2]
# 二阶段提交未完成状态全局事务重试提交线程间隔时间 默认1000,单位毫秒
server.recovery.committingRetryPeriod=6000
# 二阶段异步提交状态重试提交线程间隔时间 默认1000,单位毫秒
server.recovery.asynCommittingRetryPeriod=6000
# 二阶段回滚状态重试回滚线程间隔时间
server.recovery.rollbackingRetryPeriod=6000
# 超时状态检测重试线程间隔时间 默认1000 单位毫秒 检测出超时将全局事务置入回滚会话管理器
server.recovery.timeoutRetryPeriod=6000

seata-dev.properties

client.tm.defaultGlobalTransactionTimeout=60000
client.tm.degradeCheck=false
client.tm.degradeCheckAllowTimes=10
client.tm.degradeCheckPeriod=2000
client.tm.interceptorOrder=-2147482648
store.mode=db
store.lock.mode=db
store.session.mode=file
store.publicKey=
store.file.dir=file_store/data
store.file.maxBranchSessionSize=16384
store.file.maxGlobalSessionSize=512
store.file.fileWriteBufferCacheSize=16384
store.file.flushDiskMode=async
store.file.sessionReloadReadSize=100
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://192.168.198.100:3306/seata_config?useSSL=true&serverTimezone=GMT
store.db.user=root
store.db.password=123456
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.distributedLockTable=distributed_lock
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000
store.redis.mode=single
store.redis.single.host=127.0.0.1
store.redis.single.port=6379
store.redis.sentinel.masterName=
store.redis.sentinel.sentinelHosts=
store.redis.maxConn=10
store.redis.minConn=1
store.redis.maxTotal=100
store.redis.database=0
store.redis.password=
store.redis.queryLimit=100
server.recovery.committingRetryPeriod=6000
server.recovery.asynCommittingRetryPeriod=6000
server.recovery.rollbackingRetryPeriod=6000
server.recovery.timeoutRetryPeriod=6000
server.maxCommitRetryTimeout=-1
server.maxRollbackRetryTimeout=-1
server.rollbackRetryTimeoutUnlockEnable=false
server.distributedLockExpireTime=10000
client.undo.dataValidation=true
client.undo.logSerialization=jackson
client.undo.onlyCareUpdateColumns=true
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
client.undo.logTable=undo_log
client.undo.compress.enable=true
client.undo.compress.type=zip
client.undo.compress.threshold=64k
log.exceptionRate=100
transport.serialization=seata
transport.compressor=none
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898
tcc.fence.logTableName=tcc_fence_log
tcc.fence.cleanPeriod=1h
server.session.branchAsyncQueueSize=5000
server.session.enableBranchAsyncRemove=true
service.vgroupMapping.default_tx_group=default
service.vgroupMapping.uat_tx_group=default
service.vgroupMapping.test_tx_group=default
service.vgroupMapping.prod_tx_group=default

7.2 创建第一个服务

image.png

7.2.1 业务代码

业务代码里主要注意 最开始的调用方需要添加@GlobalTransactional注解,其他的模块的被调用方不需要添加

@Slf4j
@RefreshScope
@RestController
@RequestMapping("/tm")
public class UserController {

    @Autowired
    private UserService userService;

    @PostMapping("/insertMsg")
    public R insertMsg(@RequestBody User user){
        return userService.saveUser(user);
    }

}

//----------------------------------------------

@Data
@TableName("user")
@ApiModel
public class User implements Serializable {
    @ApiModelProperty(position = 0,value = "用户ID")
    @TableId(value = "id",type = IdType.AUTO)
    private Long id;
    @ApiModelProperty(position = 1,value = "用户姓名")
    @TableField("name")
    private String name;
    @ApiModelProperty(position = 2,value = "用户年龄")
    private Long age;
    @ApiModelProperty(position = 3,value = "用户电话号")
    private String tel;
    @ApiModelProperty(position = 4,value = "用户性别")
    private Long sex;
    @ApiModelProperty(position = 5,value = "用户地址")
    private String address;
    @ApiModelProperty(position = 6,value = "用户邮箱")
    private String email;
    @ApiModelProperty(position = 7,value = "用户生日")
    private Date birthday;
}

//----------------------------------------------

@Service
@FeignClient(name = "api-seatatwo")
public interface FeignService {

    @PostMapping("/tm/insertMsg")
    public R insertMsg(@RequestBody User user);

}

//----------------------------------------------

@Mapper
public interface UserMapper extends BaseMapper<User> {
}

//----------------------------------------------

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private FeignService feignService;

    @Override
    @GlobalTransactional
    public R saveUser(User user) {
        userMapper.insert(user);
        feignService.insertMsg(user);
        return R.ok("保存成功");
    }
}

//----------------------------------------------

public interface UserService extends IService<User> {

    public R saveUser(User user);

}

//----------------------------------------------

@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
@MapperScan(basePackages = "com.learn.seata.consumeone.mapper")
public class ConsumeoneApplication {

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

}

//----------------------------------------------

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.learn.seata.consumeone.mapper.UserMapper">

</mapper>

7.2.2 配置文件bootstrap.yml

这里主要是添加了如下关于seata的配置

#分布式事务 seata 配置
seata:
  service:
    vgroupMapping:
      default_tx_group: default  #default_tx_group分组名称必须要跟tx-service-group的值一样
  #指定事务分组 分组必须在 nacos seata-dev.properties配置中有
  tx-service-group: default_tx_group
  config:
    type: nacos
    nacos:
      #nacos地址
      serverAddr: 192.168.198.100:8848
      group: DEFAULT_GROUP
      namespace: 0c8d097b-2aaa-45f4-889a-44545ae50d3c
      userName: nacos
      password: nacos
      #读取配置文件的 dataId
      dataId: "seata-dev.properties"
  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: 192.168.198.100:8848
      group: DEFAULT_GROUP
      namespace: 0c8d097b-2aaa-45f4-889a-44545ae50d3c
      userName: nacos
      password: nacos
server:
  port: 8094
spring:
  profiles:
    active: dev
  application:
    name: api-seataone
  datasource:
    url: jdbc:mysql://192.168.198.100:3308/learn?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false&allowMultiQueries=true
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456

mybatis-plus:
  mapper-locations: classpath:mapper/*.xml
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  type-aliases-package: com.learn.seata.consumeone.entity

feign:
  client:
    # 添加此配置后修改超时时间就不需要重启服务
    refresh-enabled: true
    config:
      default:
        connectTimeout: 1000
        readTimeout: 1000

#分布式事务 seata 配置
seata:
  service:
    vgroupMapping:
      default_tx_group: default  #default_tx_group分组名称必须要跟tx-service-group的值一样
  #指定事务分组 分组必须在 nacos seata-dev.properties配置中有
  tx-service-group: default_tx_group
  config:
    type: nacos
    nacos:
      #nacos地址
      serverAddr: 192.168.198.100:8848
      group: DEFAULT_GROUP
      namespace: 0c8d097b-2aaa-45f4-889a-44545ae50d3c
      userName: nacos
      password: nacos
      #读取配置文件的 dataId
      dataId: "seata-dev.properties"
  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: 192.168.198.100:8848
      group: DEFAULT_GROUP
      namespace: 0c8d097b-2aaa-45f4-889a-44545ae50d3c
      userName: nacos
      password: nacos

# dev
---
spring:
  profiles: dev
  cloud:
    nacos:
      config:
        # 配置中心地址
        server-addr: 192.168.198.100:8848
        # 文件后缀,可以省略不配置
        file-extension: yml
        # 命名空间 默认public
        namespace: 0c8d097b-2aaa-45f4-889a-44545ae50d3c
        # 配置中心的配置文件分组
        group: DEFAULT_GROUP
      #        prefix: nacos-provider
      discovery:
        # 服务发现地址
        server-addr: 192.168.198.100:8848
        # 需要注册到的命名空间
        namespace: 0c8d097b-2aaa-45f4-889a-44545ae50d3c
        # 注册服务的默认分组
        group: DEFAULT_GROUP

# prod
---
spring:
  profiles: prod
  cloud:
    nacos:
      config:
        # 配置中心地址
        server-addr: 192.168.198.100:8848
        # 文件后缀,可以省略不配置
        file-extension: yml
        # 命名空间 默认public
        namespace: 91785852-93d6-4696-b20a-ba97c57989b7
        # 配置中心的配置文件分组
        group: DEFAULT_GROUP
      #        prefix: nacos-provider
      discovery:
        # 服务发现地址
        server-addr: 192.168.198.100:8848
        # 需要注册到的命名空间
        namespace: 91785852-93d6-4696-b20a-ba97c57989b7
        # 注册服务的默认分组
        group: DEFAULT_GROUP
---
# test
spring:
  profiles: test
  cloud:
    nacos:
      config:
        # 配置中心地址
        server-addr: 192.168.198.100:8848
        # 文件后缀,可以省略不配置
        file-extension: yml
        # 命名空间 默认public
        namespace: d441c00a-a930-4324-a71f-2751ec071f24
        # 配置中心的配置文件分组
        group: DEFAULT_GROUP
      #        prefix: nacos-provider
      discovery:
        # 服务发现地址
        server-addr: 192.168.198.100:8848
        # 需要注册到的命名空间
        namespace: d441c00a-a930-4324-a71f-2751ec071f24
        # 注册服务的默认分组
        group: DEFAULT_GROUP

7.2.3 配置文件pom.xml

<?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>
        <artifactId>TM-Learn-SpringCloud-Alibaba</artifactId>
        <groupId>tm.learn</groupId>
        <version>1.0-SNAPSHOT</version>
        <relativePath/>
    </parent>
    <groupId>com.learn.seata</groupId>
    <artifactId>consumeone</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>tm-springcloud-alibaba-seata-consumeone</name>
    <description>Learn For Seata One</description>

    <properties>
        <java.version>1.8</java.version>
        <common.version>1.0-SNAPSHOT</common.version>
        <test.version>2.2.13.RELEASE</test.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>tm.learn</groupId>
            <artifactId>tm-springcloud-alibaba-common</artifactId>
            <version>${common.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <version>${test.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-loadbalancer</artifactId>
        </dependency>
    </dependencies>

</project>

7.3 创建第二个服务

image.png

7.3.1 业务代码

这里注意,由于是被调用方,所以不需要添加 @GlobalTransactional 注解

@Slf4j
@RefreshScope
@RestController
@RequestMapping("/tm")
public class UserController {

    @Autowired
    private UserService userService;

    @PostMapping("/insertMsg")
    public R insertMsg(@RequestBody User user){
        return userService.saveUser(user);
    }

}

//----------------------------------------------

@Data
@TableName("user")
@ApiModel
public class User implements Serializable {
    @ApiModelProperty(position = 0,value = "用户ID")
    @TableId(value = "id",type = IdType.AUTO)
    private Long id;
    @ApiModelProperty(position = 1,value = "用户姓名")
    @TableField("name")
    private String name;
    @ApiModelProperty(position = 2,value = "用户年龄")
    private Long age;
    @ApiModelProperty(position = 3,value = "用户电话号")
    private String tel;
    @ApiModelProperty(position = 4,value = "用户性别")
    private Long sex;
    @ApiModelProperty(position = 5,value = "用户地址")
    private String address;
    @ApiModelProperty(position = 6,value = "用户邮箱")
    private String email;
    @ApiModelProperty(position = 7,value = "用户生日")
    private Date birthday;
}

//----------------------------------------------

@Mapper
public interface UserMapper extends BaseMapper<User> {
}

//----------------------------------------------

public interface UserService extends IService<User> {

    R saveUser(User user);

}

//----------------------------------------------

@Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

@Autowired
private UserMapper userMapper;

@Override
public R saveUser(User user) {
    userMapper.insert(user);
    //int a = 1/0;
    return R.ok("保存成功");
}

}


//----------------------------------------------

@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
@MapperScan(basePackages = "com.learn.seata.consumetwo.mapper")
public class ConsumetwoApplication {

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

}

//----------------------------------------------

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.learn.seata.consumetwo.mapper.UserMapper">

</mapper>

7.3.2 配置文件bootstrap.yml

主要注意添加如下配置信息 :

#分布式事务 seata 配置
seata:
  service:
    vgroupMapping:
      default_tx_group: default  #default_tx_group分组名称必须要跟tx-service-group的值一样
  #指定事务分组 分组必须在 nacos seata-dev.properties配置中有
  tx-service-group: default_tx_group
  config:
    type: nacos
    nacos:
      #nacos地址
      serverAddr: 192.168.198.100:8848
      group: DEFAULT_GROUP
      namespace: 0c8d097b-2aaa-45f4-889a-44545ae50d3c
      userName: nacos
      password: nacos
      #读取配置文件的 dataId
      dataId: "seata-dev.properties"
  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: 192.168.198.100:8848
      group: DEFAULT_GROUP
      namespace: 0c8d097b-2aaa-45f4-889a-44545ae50d3c
      userName: nacos
      password: nacos
server:
  port: 8095
spring:
  profiles:
    active: dev
  application:
    name: api-seatatwo
  datasource:
    url: jdbc:mysql://192.168.198.100:3309/learn?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false&allowMultiQueries=true
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456

mybatis-plus:
  mapper-locations: classpath:mapper/*.xml
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  type-aliases-package: com.learn.seata.consumetwo.entity

feign:
  client:
    # 添加此配置后修改超时时间就不需要重启服务
    refresh-enabled: true
    config:
      default:
        connectTimeout: 1000
        readTimeout: 1000

#分布式事务 seata 配置
seata:
  service:
    vgroupMapping:
      default_tx_group: default  #default_tx_group分组名称必须要跟tx-service-group的值一样
  #指定事务分组 分组必须在 nacos seata-dev.properties配置中有
  tx-service-group: default_tx_group
  config:
    type: nacos
    nacos:
      #nacos地址
      serverAddr: 192.168.198.100:8848
      group: DEFAULT_GROUP
      namespace: 0c8d097b-2aaa-45f4-889a-44545ae50d3c
      userName: nacos
      password: nacos
      #读取配置文件的 dataId
      dataId: "seata-dev.properties"
  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: 192.168.198.100:8848
      group: DEFAULT_GROUP
      namespace: 0c8d097b-2aaa-45f4-889a-44545ae50d3c
      userName: nacos
      password: nacos

# dev
---
spring:
  profiles: dev
  cloud:
    nacos:
      config:
        # 配置中心地址
        server-addr: 192.168.198.100:8848
        # 文件后缀,可以省略不配置
        file-extension: yml
        # 命名空间 默认public
        namespace: 0c8d097b-2aaa-45f4-889a-44545ae50d3c
        # 配置中心的配置文件分组
        group: DEFAULT_GROUP
      #        prefix: nacos-provider
      discovery:
        # 服务发现地址
        server-addr: 192.168.198.100:8848
        # 需要注册到的命名空间
        namespace: 0c8d097b-2aaa-45f4-889a-44545ae50d3c
        # 注册服务的默认分组
        group: DEFAULT_GROUP

# prod
---
spring:
  profiles: prod
  cloud:
    nacos:
      config:
        # 配置中心地址
        server-addr: 192.168.198.100:8848
        # 文件后缀,可以省略不配置
        file-extension: yml
        # 命名空间 默认public
        namespace: 91785852-93d6-4696-b20a-ba97c57989b7
        # 配置中心的配置文件分组
        group: DEFAULT_GROUP
      #        prefix: nacos-provider
      discovery:
        # 服务发现地址
        server-addr: 192.168.198.100:8848
        # 需要注册到的命名空间
        namespace: 91785852-93d6-4696-b20a-ba97c57989b7
        # 注册服务的默认分组
        group: DEFAULT_GROUP
---
# test
spring:
  profiles: test
  cloud:
    nacos:
      config:
        # 配置中心地址
        server-addr: 192.168.198.100:8848
        # 文件后缀,可以省略不配置
        file-extension: yml
        # 命名空间 默认public
        namespace: d441c00a-a930-4324-a71f-2751ec071f24
        # 配置中心的配置文件分组
        group: DEFAULT_GROUP
      #        prefix: nacos-provider
      discovery:
        # 服务发现地址
        server-addr: 192.168.198.100:8848
        # 需要注册到的命名空间
        namespace: d441c00a-a930-4324-a71f-2751ec071f24
        # 注册服务的默认分组
        group: DEFAULT_GROUP

7.3.3 配置文件pom.xml

<?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>
        <artifactId>TM-Learn-SpringCloud-Alibaba</artifactId>
        <groupId>tm.learn</groupId>
        <version>1.0-SNAPSHOT</version>
        <relativePath/>
    </parent>
    <groupId>com.learn.seata</groupId>
    <artifactId>consumetwo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>tm-springcloud-alibaba-seata-consumetwo</name>
    <description>Learn For Seata</description>

    <properties>
        <java.version>1.8</java.version>
        <common.version>1.0-SNAPSHOT</common.version>
        <test.version>2.2.13.RELEASE</test.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>tm.learn</groupId>
            <artifactId>tm-springcloud-alibaba-common</artifactId>
            <version>${common.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <version>${test.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-loadbalancer</artifactId>
        </dependency>
    </dependencies>

</project>

7.4 网关gateway配置

将两个服务配置上

spring:
  cloud:
    gateway:
      routes:
        - id: tm-seataone
          # 请求要转发到的地址
          uri: lb://api-seataone
          # 路由的优先级,数字越小,优先级越高
          order: 1
          # 断言(路由转发要满足的条件)
          predicates:
            - Path=/api-seataone/**
          # 过滤器,即转发之前去掉1层路径
          filters:
            - StripPrefix=1
        - id: tm-seatatwo
          # 请求要转发到的地址
          uri: lb://api-seatatwo
          # 路由的优先级,数字越小,优先级越高
          order: 1
          # 断言(路由转发要满足的条件)
          predicates:
            - Path=/api-seatatwo/**
          # 过滤器,即转发之前去掉1层路径
          filters:
            - StripPrefix=1

7.5 测试

使用postman访问 localhost:8091/api-seataone/tm/insertMsg

成功 : 两个数据库对应的user表都保存成功一条数据

失败 : 在第二个服务的代码中故意添加错误代码,1/0,使其报错,结果是两个表都没有保存成功,效果实现了。

8. 创建tm-springcloud-alibaba-sentinal 模块

文档地址 : sentinelguard.io/zh-cn/docs/…

8.1 引入依赖

<!--sentinel启动器-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

8.2 添加配置

feign:
  sentinel:
    #开启openFeign对sentinel的整合
    enabled: true

8.3 降级处理

8.3.1 编写业务代码

@Slf4j
@RefreshScope//实现nacos配置自动更新
@RestController
@RequestMapping("/tm")
public class SentinelController {

    @Autowired
    private FeignService feignService;

    @GetMapping("/getMsgById/{id}")
    public R getMsgById(@PathVariable("id") Long id) {
        return feignService.getMsgById(id);
    }

}

@Service
@FeignClient(name = "nacos-provider",fallback = FeignFailServiceImpl.class)
public interface FeignService {

    @GetMapping("/tm/getMsgById/{id}")
    public R getMsgById(@PathVariable("id") Long id);

}

@Component
public class FeignFailServiceImpl implements FeignService {

    @Override
    public R getMsgById(Long id) {
        return R.failed("降级了");
    }

}

//被调用服务故意写入报错代码1/0
@GetMapping("/getMsgById/{id}")
public R getMsgById(@PathVariable("id") Long id) throws InterruptedException {
    int a = 1/0;
    return R.ok("查询成功,id为"+id);
}

8.3.2 测试

访问 :http://localhost:8091/api-sentinel/tm/getMsgById/1 GET请求,结果成功降级

image.png

8.4 sentinel-dashboard

8.4.1 添加配置

这里注意 : 用docker部署的sentinel需要在yml配置client-ip:本地ip才行

spring:
  cloud:
    sentinel:
      transport:
        # sentinel-dashboard地址
        dashboard: 192.168.198.100:8858
        # 默认为8719,如果被占用会自动+1,直到找到为止
        port: 8719
        # 本地机器ip (注意事项,用docker部署的sentinel需要在yml配置client-ip:本地ip才行)
        clientIp: 172.10.18.205

8.4.2 测试

浏览器访问 http://192.168.198.100:8858/ 默认账号 sentinel 密码 sentinel

image.png

调用一次接口,可以看到下图所示,不调用,可能看不到,懒加载

image.png

8.5 限流处理

8.5.1 添加@SentinelResource注解

value将该方法定义为sentinel的资源,blockHandler是流控时调用的方法,也可以使用blockHandlerClass指明流控处理的类。

@GetMapping("/getMsgById/{id}")
@SentinelResource(value = "getMsgById",blockHandler = "getMsgByIdHandler")
public R getMsgById(@PathVariable("id") Long id) {
    return feignService.getMsgById(id);
}

public R getMsgByIdHandler(Long id, BlockException e){
    log.info(e.getMessage());
    return R.failed("限流异常,id: "+id);
}

8.5.2 配置sentinel流控规则

接口一秒钟之内访问超过1次,就会被限流

image.png

8.5.3 测试

1s内多次调用接口,效果如下 : 即确实触发了限流

image.png

8.6 sentinel配置持久化到nacos

Sentinel规则有默认的三种管理模式。

原始模式:Sentinel的默认模式,将规则保存到内存里,重启就会丢失。

pull模式:控制台将配置的规则推送到Sentinel客户端,Sentinel客户端首先会在内存里更新规则,然后将规则持久化到本地文件数据库。其他Sentinel客户端会定时询问数据库文件是否更改,更改的话进行同步数据。(数据容易不一致)

push模式:控制台将规则推送到nacos,Sentinel微服务监听nacos,一旦发现nacos的规则数据发生变化,就对规则进行更新。(推荐)

生产中一般采用push模式

8.6.1 引入依赖

重启sentinel-dashboard。通过测试,可以看到之前定义的流控规则已经消失了,限流也不好使了。

<dependency>
	<groupId>com.alibaba.csp</groupId>
	<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

8.6.2 添加配置

spring:
  cloud:
    sentinel:
      transport:
        # sentinel-dashboard地址
        dashboard: 192.168.198.100:8858
#        # 默认为8719,如果被占用会自动+1,直到找到为止
        port: 8719
#        # 本地机器ip (注意事项,用docker部署的sentinel需要在yml配置client-ip:本地ip才行)
        clientIp: 172.10.18.205
      datasource:
        # 自定义命名
        ds1:
          # 支持多种持久化数据源:file、nacos、zk、apollo、redis、consul
          nacos:
            server-addr: 192.168.198.100:8848
            dataId: sentinel-dashboard-dev.json
            groupId: DEFAULT_GROUP
            namespace: 0c8d097b-2aaa-45f4-889a-44545ae50d3c
            # 仅支持JSON和XML类型
            data-type: json
            # 规则类型:flow、degrade、param-flow、system、authority 如果还要做这些限流规则持久化,可以在datasource下加这些持久化规则。
            rule-type: flow
            # nacos开启了认证需要配置username、password
            username: nacos
            password: nacos

8.6.3 nacos添加配置

根据上步里的配置,这里我在dev环境下创建文件。文件的名字为sentinel-dashboard-dev.json、类型为json、内容如下 :

[
    {
        "resource":"getMsgById",
        "limitApp":"default",
        "grade":1,
        "count":1,
        "strategy":0,
        "controlBehavior":0,
        "clusterMode":false
    }
]

各个含义为 :

  • resource: 资源名称

  • limitApp: 来源应用

  • grade: 阈值类型,0 表示线程数,1 表示 QPS

  • count:单机阈值

  • strategy:流控模式,0 表示直接,1表示关联,2表示链路

  • controlBehavior: 流控效果,0 表示快速失败 ,1 表示 Warm Up ,2 表示排队等待

  • clusterMode: 是否集群

image.png

8.6.4 测试

重启sentinel-dashboard服务,可以看到配置已经自动持久化进来了。测试接口,也生效。完成!

image.png