1.1 什么是SpringCloud?(Dubbo, Dubbox,SpringCloud Netflix(Eureka/Hystrix/SpringCloud Config/zuul/Feign),SpringCloud Alibaba)
SpringCloud是基于SpringBoot的一整套实现微服务的框架。他提供了微服务开发所需的配置管理、服务发现、断路器、智能路由、微代理、控制总线、全局锁、决策竞选、分布式会话和集群状态管理等组件。最重要的是,跟spring boot框架一起使用的话,会让你开发微服务架构的云服务非常好的方便。
1.2 什么是微服务?
单体架构中,所有的代码集中在同一个项目中。虽然便于管理,但是当项目足够庞大时,所有的业务模块都集中在一个JVM进程中,会面临很多问题:
1、项目过于臃肿,难以维护
2、资源无法隔离,某个资源出现问题,整个系统崩溃
3、拓展性差,通常只能水平拓展,缺乏灵活性
什么是微服务?
简单来说,微服务架构风格是一种将一个单一应用程序开发为一组小型服务的方法,每个服务运行在自己的进程中,服务间通信采用轻量级通信机制(通常用HTTP资源API)。这些服务围绕业务能力构建并且可通过全自动部署机制独立部署。这些服务共用一个最小型的集中式的管理,服务可用不同的语言开发,使用不同的数据存储技术。
微服务的特点:
1、根据业务模块划分服务
2、每个服务可以独立部署并且互相隔离
3、通过轻量的 API 调用服务
4、服务需要保证良好的高可用性
三高(高并发、高性能、高可用)
1.3 SpringCloud Alibaba简介
Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用微服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。
依托 Spring Cloud Alibaba,您只需要添加一些注解和少量配置,就可以将 Spring Cloud 应用接入阿里微服务解决方案,通过阿里中间件来迅速搭建分布式应用系统。
SpringCloudAlibaba - 官网
Spring Cloud Alibaba
SpringCloud Alibaba - GitHub地址
https://github.com/alibaba/spring-cloud-alibaba
https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.md - 中文文档
SpringBoot、SpringCloud Netflix、SpringCloud Alibaba兼容版本
Pom父工程统一管理SpringBoot/SpringCloud/SpringCloud Alibaba的依赖
2.3.0.RELEASE Hoxton.SR9 2.2.5.RELEASE org.springframework.boot spring-boot-dependencies ${spring-boot.version} import pom org.springframework.cloud spring-cloud-dependencies ${spring-cloud.version} import pom com.alibaba.cloud spring-cloud-alibaba-dependencies ${spring-cloud-ailbaba.version} import pom
2.1 什么是nacos?
Nacos官网 - https://nacos.io/zh-cn/index.html
Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。
Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。 Nacos 是构建以“服务”为中心的现代应用架构 (例如微服务范式、云原生范式) 的服务基础设施。
Nacos与多注册中心对比
Nacos - AP和CP之间的切换命令
curl -X PUT '192.168.195.135:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=CP'
什么是注册中心?为什么需要注册中心?
核心作用:
1)服务和服务间调用的解耦,每个服务只需要和注册中心发生耦合,而服务之间没有直接联系,降低维护成本
2)轻松实现服务之间的负载均衡调用
2.2 nacos安装 - 原生方式
2.2.1 下载Nacos服务端
https://github.com/alibaba/nacos/releases/tag/2.0.1
注意:可以根据自己的需要选择相应的版本
2.2.2 上传到Linux服务器并且解压
tar -zxf nacos-server-2.0.1.tar.gz
2.2.3 进入并且运行Nacos服务
cd nacos/bin
./startup.sh -m standalone
注意:-m standalone表示单机模式
2.2.4 访问Nacos服务
http://ip:8848/nacos
2.2.5 登录Nacos服务端
账号/密码默认都是:nacos
2.3 nacos安装 - docker方式
2.3.1 准备docker-compose.yml文件
version: "3.1" services: mysql: image: mysql:5.7 container_name: mysql restart: always ports: - 3306:3306 environment: MYSQL_ROOT_PASSWORD: root volumes: - ./mysql/conf:/etc/mysql/conf.d - ./mysql/data:/var/lib/mysql nacos: image: nacos/nacos-server container_name: nacos restart: always environment: SPRING_DATASOURCE_PLATFORM: mysql #数据源平台 仅支持mysql或不保存empty MODE: standalone MYSQL_SERVICE_HOST: mysql MYSQL_SERVICE_DB_NAME: nacos MYSQL_SERVICE_PORT: 3306 MYSQL_SERVICE_USER: root MYSQL_SERVICE_PASSWORD: root NACOS_APPLICATION_PORT: 8848 JVM_XMS: 256m JVM_XMX: 512m volumes: - ./nacos/logs/:/home/nacos/logs - ./nacos/plugins/:/home/nacos/plugins ports: - 8848:8848
2.3.2 启动docker容器
docker-compose up -d
2.4 nacos的服务注册步骤
2.4.1 创建springboot pom工程,引入springcloud alibaba依赖管理
1.8 UTF-8 UTF-8 2.3.7.RELEASE 2.2.5.RELEASE org.springframework.boot spring-boot-dependencies ${spring-boot.version} pom import com.alibaba.cloud spring-cloud-alibaba-dependencies ${spring-cloud-alibaba.version} pom import
2.4.2 微服务添加nacos注册相关依赖
com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery
2.4.3 微服务配置application.yml
spring: application: name: server-test1 cloud: nacos: server-addr: ip:8848 server: port: 8081
注意:如果不想使用Nacos进行服务注册和发现,则可以设置spring.cloud.nacos.discovery为false。
2.4.4 启动类添加注解
@EnableDiscoveryClient
2.4.5 查看nacos后台服务注册情况
ps nacos服务注册的相关配置
2.5 nacos的配置中心步骤
2.5.1 微服务添加依赖
com.alibaba.cloud spring-cloud-starter-alibaba-nacos-config
2.5.2 创建bootstrap.yml配置文件
spring: cloud: nacos: config: #配置中心的地址 server-addr: ip:8848 #配置中心文件的格式 file-extension: yaml profiles: #当前的运行环境 active: dev application: #微服务的名称 name: server-test2
2.5.3 nacos服务新增配置
2.5.4 在相应的类上添加自动刷新配置的注解
@RefreshScope
PS、Nacos新增配置时 dataId的完整格式
{spring.profiles.active}.${file-extension}
注意:当 spring.profiles.active 为空时,对应的连接符 - 也将不存在,dataId 的拼接格式变成 {file-extension}
PS、Nacos多配置文件的读取
spring: cloud: nacos: config: #配置中心的地址 server-addr: 192.168.195.135:8848 #配置中心文件的格式 file-extension: yaml #多配置文件读取 shared-configs: - dataId: new-config-${spring.profiles.active}.yaml group: NEW_GROUP refresh: true profiles: #当前的运行环境 active: dev application: name: server-test2
2.6 nacos集群与持久化
2.6.1 nacos集群的模式
1)模式一:直连模式
http://ip1:port/openAPI 直连ip模式,机器挂则需要修改ip才可以使用。
2)模式二:直连负载均衡模式
http://SLB:port/openAPI 挂载SLB模式(内网SLB,不可暴露到公网,以免带来安全风险),
直连SLB即可,下面挂server真实ip,可读性不好。
3)模式三:域名 + 负载均衡模式(官方推荐)
http://nacos.com:port/openAPI 域名 + SLB模式(内网SLB,不可暴露到公网,以免带来安全风险),
可读性好,而且换ip方便,推荐模式
2.6.2 nacos集群的搭建
2.6.2.1 准备3个nacos服务(伪集群)
2.6.2.2 分别修改端口:nacos/conf/application.properties
2.6.2.3 修改每个Nacos服务目录nacos/conf下的文件cluster.conf文件
#it is ip #example 192.168.195.135:8848 192.168.195.135:8849 192.168.195.135:8850
2.6.2.4 配置外部数据源(持久化Nacos配置数据)
a) 导入数据源到外部mysql
#安装mysql数据库
#创建mysql数据库create database nacos_config;
#导入nacos/conf/nacos-mysql.sql文件
b) 配置nacos连接mysql(nacos/conf/**application.properties配置文件**)
![](https://cdn.nlark.com/yuque/0/2022/png/26763702/1647525435427-cc741602-7486-43c1-bae7-c6acd7cc0e21.png#crop=0&crop=0&crop=1&crop=1&id=mfTwk&originHeight=254&originWidth=1009&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
2.6.2.5 依次启动nacos服务
#内置数据源启动方式
startup.sh -p embedded
#使用外置数据源启动方式
nacos/bin/startup.sh
2.6.2.6 查看集群状态
2.6.2.7 代码端配置连接到nacos集群 - 直连模式
spring: application: name: server-test2 cloud: nacos: server-addr: 192.168.195.135:8848,192.168.195.135:8849,192.168.195.135:8850
2.6.3 nacos集群(Docker版)
version: "3.1" services: mysql: image: mysql:5.7 container_name: mysql restart: always ports: - 3306:3306 environment: MYSQL_ROOT_PASSWORD: root volumes: - ./mysql/conf:/etc/mysql/conf.d - ./mysql/data:/var/lib/mysql nacos1: network_mode: host #配置网络模式为host,表示容器直接使用宿主机网络 image: nacos/nacos-server container_name: nacos1 restart: always environment: MODE: cluster #配置集群模式 NACOS_SERVERS: "192.168.195.188:8848 192.168.195.188:8850 192.168.195.188:8852" #配置集群地址 NACOS_APPLICATION_PORT: 8848 #配置nacos服务的启动端口 SPRING_DATASOURCE_PLATFORM: mysql MYSQL_SERVICE_HOST: 192.168.195.188 MYSQL_SERVICE_PORT: 3306 MYSQL_SERVICE_DB_NAME: nacos_config MYSQL_SERVICE_USER: root MYSQL_SERVICE_PASSWORD: root JVM_XMS: 256m JVM_XMX: 512m volumes: - ./nacos1/logs/:/home/nacos/logs - ./nacos1/plugins/:/home/nacos/plugins nacos2: network_mode: host image: nacos/nacos-server container_name: nacos2 restart: always environment: MODE: cluster NACOS_SERVERS: "192.168.195.188:8848 192.168.195.188:8850 192.168.195.188:8852" NACOS_APPLICATION_PORT: 8850 SPRING_DATASOURCE_PLATFORM: mysql MYSQL_SERVICE_HOST: 192.168.195.188 MYSQL_SERVICE_PORT: 3306 MYSQL_SERVICE_DB_NAME: nacos_config MYSQL_SERVICE_USER: root MYSQL_SERVICE_PASSWORD: root JVM_XMS: 256m JVM_XMX: 512m volumes: - ./nacos2/logs/:/home/nacos/logs - ./nacos2/plugins/:/home/nacos/plugins nacos3: network_mode: host image: nacos/nacos-server container_name: nacos3 restart: always environment: MODE: cluster NACOS_SERVERS: "192.168.195.188:8848 192.168.195.188:8850 192.168.195.188:8852" NACOS_APPLICATION_PORT: 8852 SPRING_DATASOURCE_PLATFORM: mysql MYSQL_SERVICE_HOST: 192.168.195.188 MYSQL_SERVICE_PORT: 3306 MYSQL_SERVICE_DB_NAME: nacos_config MYSQL_SERVICE_USER: root MYSQL_SERVICE_PASSWORD: root JVM_XMS: 256m JVM_XMX: 512m volumes: - ./nacos3/logs/:/home/nacos/logs - ./nacos3/plugins/:/home/nacos/plugins
3.1 什么是Ribbon?
Ribbon本质上是一个客户端负载均衡器,在SpringCloud中主要的作用包含两个:
1、去Nacos上发现服务
2、负载均衡调用指定微服务
什么是服务端负载均衡?
什么是客户端负载均衡?
注意:客户端负载均衡往往会配合注册中心一起使用
3.2 Ribbon的服务调用
1)创建一个班级服务,注册到注册中心之上,实现学生服务对班级服务的调用
2)调用方添加依赖
org.springframework.cloud spring-cloud-starter-netflix-ribbon
3)注册RestTemplate对象
/** * 注册RestTemplate对象 * @return */ @Bean @LoadBalanced public RestTemplate getRestTemplate(){ return new RestTemplate(); }
4)在调用方使用RestTemplate发送请求
@Autowired private RestTemplate restTemplate; ... String clsName = restTemplate .getForObject("http://micro-cls/cls/queryName/" + sid, String.class);
注意:
1、RestTemplate需要添加@LoadBalanced 才会配合Ribbon进行负载均衡和服务发现
2、RestTemplate请求的地址必须填写微服务的名称
3.3 Ribbon负载均衡的策略
Ribbon实现负载均衡策略的核心接口 - IRule
设置负载均衡策略
/** * 设置负载均衡策略 * @return */ @Bean public IRule getRule(){ return new RandomRule(); }
Feign其实本质上就是Ribbon + Hystrix,提供了更加面向对象的服务调用方式
4.1 Feign的服务调用
1)创建教师微服务,注册到注册中心上
2)调用方(班级服务)添加依赖
org.springframework.cloud spring-cloud-starter-openfeign
3)编写Feign的调用接口
@FeignClient(name = "micro-tea") public interface TeaFeign { @RequestMapping("/tea/queryName/{tid}") String queryTeaName(@PathVariable Integer tid); }
4)启动类添加注解,配置Feign接口的扫描
@EnableFeignClients(basePackages = "com.qf.feign")
5)使用Feign调用微服务
4.2 Feign的超时时间和重试
Feign本身拥有超时和重试的设定,但是默认是关闭的。Feign默认采用的是底层Ribbon的重试和超时的设定,一旦Feign的超时和重试开启,那么就会覆盖Ribbon的设置。
Ribbon的读超时默认是1s,连接超时默认也是1s,跨服务重试的次数默认1次,同服务重试的次数默认1次
ribbon的超时和重试的设置
ribbon: ConnectTimeout: 1000 #毫秒 连接超时时间 ReadTimeout: 1000 #毫秒 读超时时间 MaxAutoRetries: 0 # 对当前实例的最大重试次数 MaxAutoRetriesNextServer: 1 # 切换实例的最大重试次数(如果还失败就切换下
feign的超时和重试的设置
#配置Feign的超时 feign: client: config: default: connectTimeout: 1000 readTimeout: 1000
//配置Feign的重试 @Bean public Retryer getRetryer(){ //参数1:重试的间隔时间 默认100毫秒 //参数2:重试的最大间隔时间 默认1秒 重试间隔时间按照一定的规则逐渐增大,但不能超过最大间隔时间 //参数3:最大重试次数 默认5次 (包含第一次请求) return new Retryer.Default(100, 500, 2); }
注意:
1、实际开发过程中,可以根据业务适当的调整Read超时的大小
2、实际开发过程中,根据需要可以开启或者关闭重试,但是需要注意,如果开启了重试,就有重复请求的风险,尽可能的需要保证服务的被调用方的接口幂等
如何构造一个幂等的接口?
1、给数据库的某些字段设置唯一性
2、结合缓存服务器Redis实现接口幂等
官网地址
Spring Cloud Gateway
为什么需要路由网关?
路由网关会作为微服务架构体系的入口组件,所有的外部请求,都必须经过路由网关分发(内部互相访问没有影响),这样可以屏蔽微服务体系结构复杂性,并且可以提供请求路由、负载访问、请求过滤整形等功能。
5.1 搭建Gateway
1)创建一个SpringBoot工程(依赖Eureka-Client、Gateway,千万不要依赖web)
org.springframework.cloud spring-cloud-starter-gateway com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery
2)启动类注解
@EnableDiscoveryClient
3)配置application.yml
server: port: 80 spring: application: name: gateway cloud: nacos: server-addr: ken:8848
4)配置getaway的路由功能(静态路由)
spring: application: name: gateway cloud: gateway: #配置静态路由 routes: #----路由规则1--------- - id: guize1 #当前的路由规则标识,不重复的合法标识即可 predicates: #配置路由断言,路由的条件 - Path=/stu/** uri: lb://micro-stu #----路由规则2--------- - id: guize2 #当前的路由规则标识,不重复的合法标识即可 predicates: #配置路由断言,路由的条件 - Path=/cls/** - Query=token uri: lb://micro-cls
5.2 动态路由
????????
5.3 过滤器链
5.3.1 局部过滤器
package com.qf.gateway.filter; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; import org.springframework.core.Ordered; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; /** * 局部过滤器 */ @Component public class MyGatewayFilter extends AbstractGatewayFilterFactory implements Ordered { public GatewayFilter apply(Object config) { return new GatewayFilter() { public Monofilter(ServerWebExchange exchange, GatewayFilterChain chain) { System.out.println("局部过滤器触发!!!" + exchange.getRequest().getURI().toString()); return chain.filter(exchange); } }; } public String name() { return "HelloGateway"; } public int getOrder() { return 0; } }
局部过滤器的使用
server: port: 80 spring: application: name: gateway cloud: nacos: server-addr: ken:8848 gateway: #配置gateway的路由规则 - 请求转发给谁? #uri - 当前路由转发的微服务名称 #predicates - 断言,怎么样的请求会匹配到当前的路由 #filters - 指定局部过滤器 routes: - uri: lb://classes predicates: - Path=/cls/** - Query=token - uri: lb://student predicates: - Path=/stu/** filters: - name: HelloGateway
5.3.2 全局过滤器
对所有请求都进行过滤处理
全局过滤器的配置
@Component public class MyGlobalFilter implements GlobalFilter, Ordered { @Override public Monofilter(ServerWebExchange exchange, GatewayFilterChain chain) { System.out.println("全局过滤器生效!!!"); return chain.filter(exchange); } @Override public int getOrder() { return 50; } }
5.3.3 基于过滤器的请求限流
限流的实现方案
请求计数限流
漏桶算法限流
令牌桶算法限流
令牌桶算法的Lua脚本
--令牌桶的key local key = 'tokentong_'..KEYS[1] --需要多少令牌 local getTokens = tonumber(ARGV[1]) --获得令牌桶中的参数 --令牌桶中拥有的令牌 local hasToken = tonumber(redis.call('hget', key, 'hasToken')) --令牌桶的最大令牌数 local maxToken = tonumber(redis.call('hget', key, 'maxToken')) --每秒产生的令牌数 local tokensSec = tonumber(redis.call('hget', key, 'tokensSec')) --下一次可以计算生成令牌的时间(微秒) local nextTimes = tonumber(redis.call('hget', key, 'nextTimes')) --进行一些参数计算 --计算多久产生一个令牌(微秒) local oneTokenTimes = 1000000/tokensSec --获取当前时间 local now = redis.call('time') --计算当前微秒的时间戳 local nowTimes = tonumber(now[1]) * 1000000 + tonumber(now[2]) --生成令牌 if nowTimes > nextTimes then --计算生成的令牌数 local createTokens = (nowTimes - nextTimes) / oneTokenTimes --计算拥有的令牌数 hasToken = math.min(createTokens + hasToken, maxToken) --更新下一次可以计算令牌的时间 nextTimes = nowTimes end --获取令牌 --当前能够拿到的令牌数量 local canTokens = math.min(getTokens, hasToken) --需要预支的令牌数量 local reserveTokens = getTokens - canTokens --根据预支的令牌数,计算需要预支多少时间(微秒) local reserveTimes = reserveTokens * oneTokenTimes --更新下一次可以计算令牌的时间 nextTimes = nextTimes + reserveTimes --更新当前剩余的令牌 hasToken = hasToken - canTokens --更新redis redis.call('hmset', key, 'hasToken', hasToken, 'nextTimes', nextTimes) --返回本次获取令牌需要等待的时间 return math.max(nextTimes - nowTimes, 0)
有超时时间的版本
--令牌桶的key local key = 'tokentong_'..KEYS[1] --需要多少令牌 local getTokens = tonumber(ARGV[1]) --允许等待的最大时间(超时时间) 微秒 local timeouts = tonumber(ARGV[2] or -1) --获得令牌桶中的参数 --令牌桶中拥有的令牌 local hasToken = tonumber(redis.call('hget', key, 'hasToken')) --令牌桶的最大令牌数 local maxToken = tonumber(redis.call('hget', key, 'maxToken')) --每秒产生的令牌数 local tokensSec = tonumber(redis.call('hget', key, 'tokensSec')) --下一次可以计算生成令牌的时间(微秒) local nextTimes = tonumber(redis.call('hget', key, 'nextTimes')) --进行一些参数计算 --计算多久产生一个令牌(微秒) local oneTokenTimes = 1000000/tokensSec --获取当前时间 local now = redis.call('time') --计算当前微秒的时间戳 local nowTimes = tonumber(now[1]) * 1000000 + tonumber(now[2]) --判断超时时间范围内时候能够成功预支到令牌,如果不行,直接返回-1 --如果下一次计算令牌的时间,比当前时间要大,就需要等待,就有可能超过过期时间 if timeouts ~= -1 then --在过期时间内,无法获得令牌 if nextTimes - nowTimes > timeouts then return -1 end end --生成令牌 if nowTimes > nextTimes then --计算生成的令牌数 local createTokens = (nowTimes - nextTimes) / oneTokenTimes --计算拥有的令牌数 hasToken = math.min(createTokens + hasToken, maxToken) --更新下一次可以计算令牌的时间 nextTimes = nowTimes end --获取令牌 --当前能够拿到的令牌数量 local canTokens = math.min(getTokens, hasToken) --需要预支的令牌数量 local reserveTokens = getTokens - canTokens --根据预支的令牌数,计算需要预支多少时间(微秒) local reserveTimes = reserveTokens * oneTokenTimes --更新下一次可以计算令牌的时间 nextTimes = nextTimes + reserveTimes --更新当前剩余的令牌 hasToken = hasToken - canTokens --更新redis redis.call('hmset', key, 'hasToken', hasToken, 'nextTimes', nextTimes) --返回本次获取令牌需要等待的时间 return math.max(nextTimes - nowTimes, 0)
什么是链路追踪?
微服务会有很多的服务互相调用,我们的一个业务到底用了多少微服务,每个微服务耗时多少,中间出现了异常,到底是谁出现了异常,所以我需要对请求的链路进行追踪
6.1 使用Docker搭建链路追踪的监控平台 - zipkin
1)拉取zipkin的镜像
docker pull openzipkin/zipkin
2)配置zipkin的docker-compose模板
version: "3.1" services: zipkin: image: openzipkin/zipkin restart: always volumes: - ./zipkin/localtime:/etc/localtime ports: - 9411:9411 container_name: zipkin
3)执行命令,创建容器
docker-compose up -d
4)开放端口,访问zipkin图形化页面
6.2 给所有微服务配置链路追踪
1)微服务添加依赖
org.springframework.cloud spring-cloud-starter-zipkin
2)配置application.yml
spring: zipkin: #配置zipkin服务的地址 base-url: http://192.168.195.188:9411 sleuth: sampler: #链路采样率,默认0.1 probability: 1
6.3 整合MySQL持久化zipkin链路数据
1)创建mysql容器
mysql: image: mysql:5.7 ports: - 3306:3306 container_name: mysql environment: MYSQL_ROOT_PASSWORD: root volumes: - ./mysql/conf:/etc/mysql/conf.d - ./mysql/logs:/logs - ./mysql/data:/var/lib/mysql restart: always
2)连接mysql创建zipkin的表结构
create database `zipkin` /*!40100 DEFAULT CHARACTER SET utf8 */; use zipkin; create table if not exists zipkin_spans ( `trace_id_high` bigint not null default 0 comment 'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit', `trace_id` bigint not null, `id` bigint not null, `name` varchar(255) not null, `remote_service_name` varchar(255), `parent_id` bigint, `debug` bit(1), `start_ts` bigint comment 'Span.timestamp(): epoch micros used for endTs query and to implement TTL', `duration` bigint comment 'Span.duration(): micros used for minDuration and maxDuration query', primary key (`trace_id_high`, `trace_id`, `id`) ) engine=innodb row_format=compressed character set=utf8 collate utf8_general_ci; alter table zipkin_spans add index(`trace_id_high`, `trace_id`) comment 'for getTracesByIds'; alter table zipkin_spans add index(`name`) comment 'for getTraces and getSpanNames'; alter table zipkin_spans add index(`remote_service_name`) comment 'for getTraces and getRemoteServiceNames'; alter table zipkin_spans add index(`start_ts`) comment 'for getTraces ordering and range'; create table if not exists zipkin_annotations ( `trace_id_high` bigint not null default 0 comment 'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit', `trace_id` bigint not null comment 'coincides with zipkin_spans.trace_id', `span_id` bigint not null comment 'coincides with zipkin_spans.id', `a_key` varchar(255) not null comment 'BinaryAnnotation.key or Annotation.value if type == -1', `a_value` blob comment 'BinaryAnnotation.value(), which must be smaller than 64KB', `a_type` int not null comment 'BinaryAnnotation.type() or -1 if Annotation', `a_timestamp` bigint comment 'Used to implement TTL; Annotation.timestamp or zipkin_spans.timestamp', `endpoint_ipv4` int comment 'Null when Binary/Annotation.endpoint is null', `endpoint_ipv6` binary(16) comment 'Null when Binary/Annotation.endpoint is null, or no IPv6 address', `endpoint_port` smallint comment 'Null when Binary/Annotation.endpoint is null', `endpoint_service_name` varchar(255) comment 'Null when Binary/Annotation.endpoint is null' ) engine=innodb row_format=compressed character set=utf8 collate utf8_general_ci; alter table zipkin_annotations add unique key(`trace_id_high`, `trace_id`, `span_id`, `a_key`, `a_timestamp`) comment 'Ignore insert on duplicate'; alter table zipkin_annotations add index(`trace_id_high`, `trace_id`, `span_id`) comment 'for joining with zipkin_spans'; alter table zipkin_annotations add index(`trace_id_high`, `trace_id`) comment 'for getTraces/ByIds'; alter table zipkin_annotations add index(`endpoint_service_name`) comment 'for getTraces and getServiceNames'; alter table zipkin_annotations add index(`a_type`) comment 'for getTraces and autocomplete values'; alter table zipkin_annotations add index(`a_key`) comment 'for getTraces and autocomplete values'; alter table zipkin_annotations add index(`trace_id`, `span_id`, `a_key`) comment 'for dependencies job'; create table if not exists zipkin_dependencies ( `day` date not null, `parent` varchar(255) not null, `child` varchar(255) not null, `call_count` bigint, `error_count` bigint, primary key (`day`, `parent`, `child`) ) engine=innodb row_format=compressed character set=utf8 collate utf8_general_ci;
3)重新创建zipkin的容器,需要连接数据库
zipkin: image: openzipkin/zipkin restart: always volumes: - ./zipkin/localtime:/etc/localtime ports: - 9411:9411 container_name: zipkin environment: MYSQL_USER: root MYSQL_PASS: root MYSQL_HOST: 192.168.195.148 STORAGE_TYPE: mysql MYSQL_DB: zipkin MYSQL_TCP_PORT: 3306
4)重新构建zipkin的容器
docker-compose up -d --build zipkin
7.1 Sentinel简介
sentinel - github地址
https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D
sentinel是什么?
sentinel:分布式系统的流量防卫兵。随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
Sentinel 具有以下特征:
sentinel的核心部分
7.2 Sentinel控制台的安装
1、下载Sentinel控制台程序(jar文件)
https://github.com/alibaba/Sentinel/releases/tag/1.8.1
2、将控制台程序上传的Linux服务器, 并且通过命令启动
java -jar sentinel-dashboard-1.8.1.jar 命令: nohup java -jar sentinel-dashboard-1.8.1.jar > sentinel.log 2>&1 & 该命令可以让jar程序在后台运行 注意:需要服务器预留8080端口
3、访问sentinel控制台程序
http://192.168.195.135:8080
注意:账号密码都为sentinel
7.3 使用docker安装
docker-compose配置
sentinel: image: bladex/sentinel-dashboard container_name: sentinel restart: always ports: - 8858:8858
7.4 微服务使用Sentinel配合仪表盘进行访问监控
1、调用方微服务添加相关依赖
com.alibaba.cloud spring-cloud-starter-alibaba-sentinel
2、配置微服务访问sentinel仪表盘的地址
spring: application: name: server-test2 cloud: nacos: server-addr: 192.168.195.135:8848 sentinel: transport: #配置sentinel仪表盘的访问地址 dashboard: 192.168.195.135:8080 #在启动当前服务时,sentinel还会启动一个服务接收仪表盘的请求,该服务的默认端口为8719 port: 8719
3、启动微服务,并且访问仪表盘查看监控情况
注意:
1、sentinel默认为懒加载形式,仪表盘的记录需要在访问过微服务接口后,才会显示
2、sentinel-dashboard必须搭建在内网(sentinel控制台程序会主动访问微服务,也就是8719端口,如果是外网,sentinel控制台因为网段的原因会访问不到微服务)
7.5 Sentinel的流量控制
什么是流量控制?
流量控制(flow control),其原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。
流量控制规则的元素组成
流控类型 - 怎样的条件下会触发流控
流控效果 - 流控触发后会导致的结果
注意:匀速排队模式暂时不支持 QPS > 1000 的场景。
注意:流控类型如果为并发线程数,则流控效果只支持直接拒绝。
流控模式 - 流控中服务与服务之间的调用关系
流控来源 - 针对调用者进行限流
流控规则中的 limitApp 字段用于根据调用来源进行流量控制。该字段的值有以下三种选项,分别对应不同的场景:
通过代码的方式配置流控
private void initFlowQpsRule() { Listrules = new ArrayList<>(); FlowRule rule = new FlowRule(resourceName); // set limit qps to 20 rule.setCount(20); rule.setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setLimitApp("default"); rules.add(rule); FlowRuleManager.loadRules(rules); }
7.5.1 Sentinel整合SpringCloud GateWay实现网关流控
1)gateway添加相关依赖
com.alibaba.cloud spring-cloud-alibaba-sentinel-gateway com.alibaba.cloud spring-cloud-starter-alibaba-sentinel
2)配置yml
spring: application: name: gateway cloud: nacos: server-addr: 192.168.195.135:8848 gateway: routes: - id: server-test1 uri: lb://server-test1 predicates: - Path=/test1/** - id: server-test2 uri: lb://server-test2 predicates: - Path=/test2/** sentinel: transport: #配置sentinel控制台地址 dashboard: 192.168.195.135:8080
3)自定义限流降级后的处理方法
@Bean public void init(){ GatewayCallbackManager.setBlockHandler(new BlockRequestHandler() { @Override public MonohandleRequest(ServerWebExchange serverWebExchange, Throwable throwable) { System.out.println("触发了限流!!!!" + throwable); return ServerResponse.status(HttpStatus.BAD_REQUEST) .header("content-type", "text/html;charset=utf-8") .bodyValue("已经触发了限流!!!!"); } }); }
4)登录sentinel控制台配置流控规则
7.6 Sentinel熔断降级
什么是熔断降级?
除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。
由于调用关系的复杂性,如果调用链路中的某个资源不稳定,最终会导致请求发生堆积。Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)。
降级策略
平均响应时间 (DEGRADE_GRADE_RT):当 1s 内持续进入 N 个请求,对应时刻的平均响应时间(秒级)均超过阈值(count,以 ms 为单位),那么在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地熔断(抛出 DegradeException)。
注意:Sentinel 默认统计的 RT 上限是 4900 ms,超出此阈值的都会算作 4900 ms,若需要变更此上限可以通过启动配置项 -Dcsp.sentinel.statistic.max.rt=xxx 来配置。
异常比例 (DEGRADE_GRADE_EXCEPTION_RATIO):当资源的每秒请求量 >= N(可配置),并且每秒异常总数占通过量的比值超过阈值(DegradeRule 中的 count)之后,资源进入降级状态,即在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
异常数 (DEGRADE_GRADE_EXCEPTION_COUNT):当资源近 1 分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若 timeWindow 小于 60s,则结束熔断状态后仍可能再进入熔断状态。
注意:异常降级仅针对业务异常,对 Sentinel 限流降级本身的异常(BlockException)不生效。
使用代码的方式配置熔断降级
@Bean public void init(){ System.out.println("代码设置熔断降级!"); Listrules = new ArrayList<>(); DegradeRule rule = new DegradeRule(); //设置资源名称 rule.setResource("/test1"); //设置最大RT rule.setCount(500); //设置熔断类型 - //RuleConstant.DEGRADE_GRADE_RT - 慢调用比例 //RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO - 异常比例 //RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT - 异常数 rule.setGrade(RuleConstant.DEGRADE_GRADE_RT); //设置阈值 0.0 ~ 1.0 rule.setSlowRatioThreshold(0.5); //熔断时长 rule.setTimeWindow(12); //最小请求数 rule.setMinRequestAmount(3); //统计时长 - 滑动窗口时长 rule.setStatIntervalMs(10000); rules.add(rule); DegradeRuleManager.loadRules(rules); }
7.6.1 Sentinel整合Feign实现调用方的降级
1)添加配置
feign: sentinel: enabled: true
2)编写Feign接口的默认降级实现类
package com.qf.stu.application.feign; import org.springframework.stereotype.Component; /** * description: * author: Ken * 公众号:Java架构栈 */ @Component public class ClsServiceFallback implements ClsService { @Override public String getCls(Integer cid) { return "Feign的降级方法!!!"; } }
3)配置Feign接口的注解
package com.qf.stu.application.feign; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; /** * description: * author: Ken * 公众号:Java架构栈 */ @FeignClient(value = "cls-server", fallback = ClsServiceFallback.class) public interface ClsService { @RequestMapping("/cls/get") String getCls(@RequestParam("cid") Integer cid); }
7.7 Sentinel热点参数限流
什么是热点参数限流?
何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:
配置热点参数限流
1)定义热点限流资源
2)控制台设置资源限流参数
3)发送请求测试
注意:只有相同参数值达到阈值时才会触发限流,比如阈值为3,当单位时间内p1=100,请求3次就会触发限流,而p1=100/101/102,请求3次不会触发限流
设置参数的额外项 - 如果参数值匹配上指定的项,就会根据额外设定的阈值触发限流
1)修改热点限流设置
2)选择高级选项,设置额外项
7.8 Sentinel注解支持 - @SentinelResource
@SentinelResource的作用
Sentinel 提供了 @SentinelResource 注解用于定义资源,并提供了 AspectJ 的扩展用于自动定义资源、处理 BlockException 等。
@SentinelResource注解的属性介绍
注意:
1、blockHandler主要用来指定Sentinel限流或者熔断后(也就是报BlockException异常)的处理方法
2、fallback主要用来指定方法抛出的所有异常(非BlockException、非exceptionsToIgnore指定的异常)处理方法
3、blockHandler指定的方法最后一个参数必须为BlockException
4、@SentinelResource注解、blockHandler、fallback指定的方法必须为公共方法(public)
7.9 Nacos持久化管理控制参数
1)微服务添加依赖
com.alibaba.csp sentinel-datasource-nacos
2)微服务添加相应代码(启动微服务时触发)
package com.qf.classes.application; import com.alibaba.csp.sentinel.datasource.ReadableDataSource; import com.alibaba.csp.sentinel.datasource.nacos.NacosDataSource; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.TypeReference; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; import java.util.List; @Component public class NacosSentinelInit implements CommandLineRunner { @Override public void run(String... args) throws Exception { //配置Nacos配置文件地址,获取流控配置 ReadableDataSource> flowDataSource = new NacosDataSource<>("nacos地址:端口", "配置的group", "配置的dataId", json -> JSON.parseObject(json, new TypeReference >() {})); //将nacos中的流控信息设置到微服务的Sentinel组件中 FlowRuleManager.register2Property(flowDataSource.getProperty()); //配置Nacos配置文件地址,获取降级配置 ReadableDataSource
> degradeDataSource = new NacosDataSource<>("nacos地址:端口", "配置的group", "配置的dataId", json -> JSON.parseObject(json, new TypeReference >() {})); //将nacos中的降级信息设置到微服务的Sentinel组件中 DegradeRuleManager.register2Property(degradeDataSource.getProperty()); } }
3)在nacos中添加对应的配置文件
//流控的json内容 [ { "resource": "/resource",//资源名称 "count": 1000,//限流阈值 "grade": 1,//限流类型 0-线程数 1-QPS "limitApp": "default",//流控针对的调用来源,若为 default 则不区分调用来源 "strategy": 0,//调用关系限流策略 0-直接 1-关联 2-链路 "controlBehavior": 0//流量控制效果 0-直接拒绝 1-Warm Up 2-匀速排队 }, ... ]
//降级的json内容 [ { "resource": "/resource",//资源名称 "grade": 0,//熔断策略,0-慢调用比例 1-异常比例 2-异常数策略 "count": 500, //慢调用比例模式下为慢调用临界 RT(超出该值计为慢调用);异常比例/异常数模式下为对应的阈值 "timeWindow": 10,//熔断时长,单位为 s "slowRatioThreshold": 0.5,//慢调用比例阈值,仅慢调用比例模式有效 "minRequestAmount": 5,//熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断 "statIntervalMs": 1000//滑动窗口时长(统计单位时间,ms) }, .... ]
网关流控
ReadableDataSource> flowDataSource = new NacosDataSource<>("192.168.195.188:8848", "DEFAULT_GROUP", "gateway-flow-rule", json -> JSON.parseObject(json, new TypeReference >() {})); GatewayRuleManager.register2Property(flowDataSource.getProperty());
[ { "resource": "xxxx",//资源名称 "resourceMode": 0, //规则是针对 API Gateway 的 route(RESOURCE_MODE_ROUTE_ID)还是用户在 Sentinel 中定义的 API 分组(RESOURCE_MODE_CUSTOM_API_NAME)" "grade": 1,//限流类型 0-线程数 1-QPS "count": 10, //限流阈值 "intervalSec": 1 //统计时间窗口,单位是秒,默认是 1 秒。 }, ]
7.10 集群流控
什么是集群流控?
假设我们希望给某个用户限制调用某个 API 的总 QPS 为 50,但机器数可能很多(比如有 100 台)。这时候我们很自然地就想到,找一个 server 来专门来统计总的调用量,其它的实例都与这台 server 通信来判断是否可以调用。这就是最基础的集群流控的方式。
另外集群流控还可以解决流量不均匀导致总体限流效果不佳的问题。假设集群中有 10 台机器,我们给每台机器设置单机限流阈值为 10 QPS,理想情况下整个集群的限流阈值就为 100 QPS。不过实际情况下流量到每台机器可能会不均匀,会导致总量没有到的情况下某些机器就开始限流。因此仅靠单机维度去限制的话会无法精确地限制总体流量。而集群流控可以精确地控制整个集群的调用总量,结合单机限流兜底,可以更好地发挥流量控制的效果。
集群限流的模式
集群流控中共有两种身份:
7.10.1 集群流控服务端的配置
1)初始化服务端配置
com.alibaba.csp sentinel-cluster-server-default com.alibaba.cloud spring-cloud-starter-alibaba-sentinel com.alibaba.csp sentinel-datasource-nacos
package com.ken.ability.sentinel.application; import com.alibaba.csp.sentinel.cluster.server.ClusterTokenServer; import com.alibaba.csp.sentinel.cluster.server.SentinelDefaultTokenServer; import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager; import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfig; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; /** * Sentinel集群流控Server端 初始化配置 */ @Component public class SentinelClusterInit implements CommandLineRunner { @Value("${server.port}") private Integer port; @Override public void run(String... args) throws Exception { // 加载服务配置 ClusterServerConfigManager.loadGlobalTransportConfig(new ServerTransportConfig() .setIdleSeconds(600) .setPort(port)); //启动服务 ClusterTokenServer tokenServer = new SentinelDefaultTokenServer(); tokenServer.start(); //配置为服务端 ClusterStateManager.applyState(ClusterStateManager.CLUSTER_SERVER); //根据namespace 加载对应的集群限流规则 ClusterFlowRuleManager.setPropertySupplier(namespace -> { ReadableDataSource> ds = new NacosDataSource<>( "nacos地址:端口", "group组名称", namespace + "流控配置文件的dataid", source -> JSON.parseObject(source, new TypeReference >() {})); SentinelProperty
> property = ds.getProperty(); return property; }); //配置动态的命名空间数据源 -- 必须放在最后 ReadableDataSource
> namespaceDs = new NacosDataSource<>( "nacos地址:端口", "group组名称", "namespace配置的dataid", source -> JSON.parseObject(source, new TypeReference >() {})); //注册Sentinel集群流控服务端的namespace列表(每个namespace就代表一个微服务) ClusterServerConfigManager.registerNamespaceSetProperty(namespaceDs.getProperty()); } }
2)nacos上提供对应的配置信息
//namespace的配置,每个namespace对应一个微服务 [ "appA", "appB", .... ]
appA-flow-rule
//appA - 对应的流控规则 [ { "resource":"/resources", "grade":1, "count":1000, "clusterMode":true, //是否开启集群流控 true表示开启 "clusterConfig":{ "flowId":1231241, // (必需)全局唯一的规则 ID,由集群限流管控端分配 "thresholdType":1, //阈值模式,默认(0)为单机均摊,1 为全局阈值. "strategy": 0, //集群策略 "fallbackToLocalWhenFail":true //在 client 连接失败或通信失败时,是否退化到本地的限流模式 } } ]
7.10.2 集群流控客户端的配置
1)配置服务端地址,连接到服务端
com.alibaba.csp sentinel-cluster-client-default
/** * 配置当前模式为客户端 */ ClusterStateManager.applyState(ClusterStateManager.CLUSTER_CLIENT); //配置服务端地址 ReadableDataSourceds = new NacosDataSource<>( "nacos地址:端口", "group组名称", "流控服务端地址配置文件的dataid", source -> JSON.parseObject(source, new TypeReference () {})); ClusterClientConfigManager.registerServerAssignProperty(ds.getProperty()); //配置客户端相关配置 ReadableDataSource ds2 = new NacosDataSource<>( "nacos地址:端口", "group组名称", "流控服务端地址配置文件的dataid", source -> JSON.parseObject(source, new TypeReference () {})); ClusterClientConfigManager.registerClientConfigProperty(ds2.getProperty());
2)配置从nacos上读取流控规则
//配置从Nacos上获取流控规则 ReadableDataSource> ds3 = new NacosDataSource<>( "nacos地址:端口", "group组名称", appName + "流控规则配置文件的dataid", source -> JSON.parseObject(source, new TypeReference >() {})); FlowRuleManager.register2Property(ds3.getProperty());
3)Nacos上提供对应的配置信息
//流控服务端的地址 { "serverHost":"ip", "serverPort":端口, "requestTimeout": 1000//超时时间 }
分布式事务的常规解决方案
1、2PC - 两段提交协议(3PC)
2、TCC - 柔性事务(2PC)
3、基于MQ的最终一致性
8.1 Seata简介
Seata官网
Seata
Seata是什么?
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
Seata相关概念
TC (Transaction Coordinator) - 事务协调者维护全局和分支事务的状态,驱动全局事务提交或回滚。
TM (Transaction Manager) - 事务管理器定义全局事务的范围:开始全局事务、提交或回滚全局事务。
RM (Resource Manager) - 资源管理器管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
8.2 Seata服务端安装(Transaction Coordinator)
1)下载seata-server端的程序
2)上传至linux服务,并且解压
unzip seata-server-1.4.2.zip cd seata/seata-server-1.4.2
3)配置seata-server注册到nacos上并且使用nacos管理seata-server配置(conf/register.conf文件)
registry { # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa type = "nacos" #修改注册中心类型为nacos nacos { #配置nacos的相关地址 application = "seata-server" serverAddr = "127.0.0.1:8848" group = "SEATA_GROUP" namespace = "" cluster = "default" username = "nacos" password = "nacos" } ...... } config { # file、nacos 、apollo、zk、consul、etcd3 type = "nacos" #修改配置中心的类型为nacos nacos { #配置nacos的相关地址 serverAddr = "127.0.0.1:8848" namespace = "" group = "SEATA_GROUP" username = "nacos" password = "nacos" } ..... }
4)基于seata官方脚本,将seata-server相关配置上传到nacos,并做相关修改
上传命令: nacos/nacos-config.sh -h localhost -p 8848 -g SEATA_GROUP -t 5a3c7d6c-f497-4d68-a71a-2e5e3340b3ca -u username -w password -h nacos的服务器地址 -p nacos端口 -g 配置分组名称 -t 配置命名空间 -u nacos认证用户名 -p nacos认证密码
修改nacos上的配置数据,改为数据库的存储方式
5)mysql数据创建seata数据库,并且导入相关表(基于官网给出的数据库文件)
6)启动seata-server,并查看nacos注册服务
bin/seata-server.sh [-h 指定注册到注册中心的ip地址]
8.3 Seata服务端安装(Docker版)
1)准备docker-compose.yml
version: "3.1" services: seata-server: image: seataio/seata-server:1.4.2 hostname: seata-server container_name: seata-server restart: always ports: - "8091:8091" environment: - SEATA_PORT=8091 #指定seata-server启动的端口, 默认为 8091 - SEATA_IP=xx.xx.xx.xx #指定seata-server启动的IP, 该IP用于向注册中心注册时使用, - SEATA_CONFIG_NAME=file:/root/seata-config/registry #指定配置文件位置, 如 file:/root/registry, 将会加载 /root/registry.conf 作为配置文件 volumes: - ./seata/config:/root/seata-config
8.4 微服务配置Seata客户端,实现分布式事务
1)微服务添加相关依赖
com.alibaba.cloud spring-cloud-starter-alibaba-seata
2)application.yml添加相关配置
....... seata: #配置seata注册到nacos registry: type: nacos nacos: application: seata-server server-addr: 192.168.195.135:8848 group : "SEATA_GROUP" namespace: "public" username: "nacos" password: "nacos" #配置seata读取nacos配置中心 config: type: nacos nacos: server-addr: 192.168.195.135:8848 group: "SEATA_GROUP" namespace: "seata-config" username: "nacos" password: "nacos" #配置分布式事务组的名称 tx-service-group: my_test_tx_group
3)在微服务业务链中所有的业务库依次添加回滚记录表
-- 注意此处0.7.0+ 增加字段 context 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;
4)在业务主方法上添加指定注解,并且测试
@Override @GlobalTransactional public int insertUser(User user) { //业务方法体 return result; }
8.5 Seata AT模式原理与使用
前置条件
机制原理
两段提交协议的演变:
工作流程
业务表:product
Field |
Type |
Key |
id |
bigint(20) |
PRI |
name |
varchar(100) |
|
since |
varchar(100) |
AT 分支事务的业务逻辑:
update product set name = 'GTS' where name = 'TXC';
**一阶段**
过程:
select id, name, since from product where name = 'TXC';
得到前镜像:
1 |
TXC |
2014 |
id |
name |
since |
select id, name, since from product where id = 1;
id |
name |
since |
1 |
GTS |
2014 |
{ "branchId": 641789253, "undoItems": [{ "afterImage": { "rows": [{ "fields": [{ "name": "id", "type": 4, "value": 1 }, { "name": "name", "type": 12, "value": "GTS" }, { "name": "since", "type": 12, "value": "2014" }] }], "tableName": "product" }, "beforeImage": { "rows": [{ "fields": [{ "name": "id", "type": 4, "value": 1 }, { "name": "name", "type": 12, "value": "TXC" }, { "name": "since", "type": 12, "value": "2014" }] }], "tableName": "product" }, "sqlType": "UPDATE" }], "xid": "xid:xxx" }
**二阶段-回滚**
update product set name = 'TXC' where id = 1;
**二阶段-提交**
注意:Seata默认的分布式事务就是基于AT模式的实现
8.6 Seata TCC模式的原理与实现
TCC机制
TCC 模式,是指支持把自定义的分支事务纳入到全局事务的管理中,并不依赖于底层数据资源的事务支持:
Seata实现TCC模式
1)创建TCC接口
package com.qf.tcc; import com.qf.entity.User; import io.seata.rm.tcc.api.BusinessActionContext; import io.seata.rm.tcc.api.BusinessActionContextParameter; import io.seata.rm.tcc.api.LocalTCC; import io.seata.rm.tcc.api.TwoPhaseBusinessAction; @LocalTCC public interface IUserTCC { @TwoPhaseBusinessAction(name = "userService", commitMethod = "insertUserCommit", rollbackMethod = "insertUserRollback") int insertUser(@BusinessActionContextParameter(paramName = "userKey") User user); boolean insertUserCommit(BusinessActionContext context); boolean insertUserRollback(BusinessActionContext context); }
**2)实现TCC接口,并在业务层调用该TCC方法**
package com.qf.service.impl; import com.qf.entity.User; import com.qf.feign.Test1Feign; import com.qf.service.IUserService; import com.qf.tcc.IUserTCC; import io.seata.spring.annotation.GlobalTransactional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class UserServiceImpl implements IUserService { @Autowired private Test1Feign test1Feign; @Autowired private IUserTCC userTCC; @Override @GlobalTransactional public int insertUser(User user) { user.setId(1); int result = userTCC.insertUser(user); System.out.println("业务层添加用户:" + user); String result2 = test1Feign.test1(user.getId()); System.out.println("远程调用添加用户详细信息:" + result2); return result; } }
TCC注解详解
注意:Seata允许多种模式混合使用管理分布式事务,比如微服务A使用AT模式,微服务B使用TCC模式