微服务保护和分布式事务
-
雪崩问题产生的原因:微服务相互调用,服务提供者出现故障或阻塞;服务调用者没有做好异常处理,导致自身故障;调用链中的多有服务级联失败,导致整个集群故障。
-
解决问题的思路:
-
尽量避免服务出现故障或者阻塞
- 尽量保证代码的健壮性;
- 保证网络畅通;
- 能应对较高的并发请;
-
服务调用者做好远程调用异常的后背方案,避免故障扩散
-
-
微服务保护方案
- 请求限流:限制或控制接口访问的并发流量,避免服务因流量激增而出现故障。请求限流往往会有一个限流器,数量高低起伏的并发请求曲线,经过限流器就变的非常平稳。
- 线程隔离:避免某个接口故障或压力过大导致整个服务不可用,可以限定每个接口可以使用的资源范围,也就是将其“隔离”起来。
- 服务熔断:统计服务提供方的异常比例,当比例过高表明该接口会影响到其它服务,应该拒绝调用该接口,而是直接走降级逻辑(服务调用失败后的处理逻辑,根据业务场景,可以抛出异常,也可以返回友好提示或默认数据)。
-
微服务保护的技术有很多,但在目前国内使用较多的还是Sentinel,用一恶搞sentinel技术将上面的问题解决,就是进行微服务的保护。Sentinel 的使用可以分为两个部分,一个是核心库(Jar包),它不依赖任何框架/库,能够运行于 Java 8 及以上的版本的运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。在项目中引入依赖即可实现服务限流、隔离、熔断等功能。还有就是控制台(Dashboard),Dashboard 主要负责管理推送规则、监控、管理机器信息等。
-
Sentinel 的使用
- 下载jar包,运行jar包:运行时要进行端口,路径的修改
java -Dserver.port=8090 -Dcsp.sentinel.dashboard.server=localhost:8090 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar
-
进行访问:访问http://localhost:8090页面,就可以看到sentinel的控制台了
-
整合我们自己的微服务,引入依赖
<!--sentinel--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
- 配置控制台
spring: cloud: sentinel: transport: dashboard: localhost:8090 http-method-specify: true # 开启请求方式前缀
- 访问cart-service的任意端点,点击簇点链路菜单,会看到你的相关访问
-
请求限流
- 在簇点链路后面点击流控按钮,即可对其做限流配置,限流就是极限值每秒的并发数,选择QPS,设置为6
- 利用Jemeter做限流测试,测试每秒发出10个请求,发现接口通过QPS稳定在6附近,而拒绝的QPS在4附近
-
线程隔离:限流可以降低服务器压力,尽量减少因并发流量引起的服务故障的概率,但并不能完全避免服务故障。一旦某个服务出现故障,我们必须隔离对这个服务的调用,避免发生雪崩。
- 开启Feign的sentinel功能
feign: sentinel: enabled: true # 开启feign对sentinel的支持
- 点击簇点资源后面的流控按钮,勾选并发线程数限制,也就是说这个查询功能最多使用5个线程,而不是5QPS。如果查询商品的接口每秒处理2个请求,则5个线程的实际QPS在10左右,而超出的请求自然会被拒绝。
-
编写降级逻辑,利用FallbackFactory,对远程调用的异常做处理
- 实现FallbackFactory
@Slf4j public class ItemClientFallback implements FallbackFactory<ItemClient> { @Override public ItemClient create(Throwable cause) { return new ItemClient() { @Override public List<ItemDTO> queryItemByIds(Collection<Long> ids) { log.error("远程调用ItemClient#queryItemByIds方法出现异常,参数:{}", ids, cause); // 查询购物车允许失败,查询失败,返回空集合 return CollUtils.emptyList(); } @Override public void deductStock(List<OrderDetailDTO> items) { // 库存扣减业务需要触发事务回滚,查询失败,抛出异常 throw new BizIllegalException(cause); } }; } }
-
将ItemClientFallback注册为一个Bean
-
在hm-api模块中的ItemClient接口中使用ItemClientFallbackFactory
-
服务熔断:对于一些不太健康的接口,我们应该停止调用,直接走降级逻辑,避免影响到当前服务。也就是将商品查询接口熔断。Sentinel中的断路器不仅可以统计某个接口的慢请求比例,还可以统计异常请求比例。当这些比例超出阈值时,就会熔断该接口,即拦截访问该接口的一切请求,降级处理;当该接口恢复正常时,再放行对于该接口的请求。
- 在控制台通过点击簇点后的熔断按钮来配置熔断策略
-
分布式事务:举例订单、购物车、商品分别在三个不同的微服务,而每个微服务都有自己独立的数据库,因此下单过程中就会跨多个数据库完成业务。而每个微服务都会执行自己的本地事务,整个业务中,各个本地事务是有关联的。因此每个微服务的本地事务,也可以称为分支事务。多个有关联的分支事务一起就组成了全局事务。我们必须保证整个全局事务同时成功或失败。我们知道每一个分支事务就是传统的单体事务,都可以满足ACID特性,但全局事务跨越多个服务、多个数据库,就不能满足事务的ACID原则了。
-
在众多的开源分布式事务框架中,功能最完善、使用最多的就是阿里巴巴在2019年开源的Seata了。解决分布式事务的思想:找一个统一的事务协调者,与多个分支事务通信,检测每个分支事务的执行状态,保证全局事务下的每一个分支事务同时成功或失败即可。大多数的分布式事务框架都是基于这个理论来实现的。Seata也不例外,在Seata的事务管理中有三个重要的角色:
- TC (Transaction Coordinator) - 事务协调者:维护全局和分支事务的状态,协调全局事务提交或回滚。
- TM (Transaction Manager) - 事务管理器:定义全局事务的范围、开始全局事务、提交或回滚全局事务。
- RM (Resource Manager) - 资源管理器:管理分支事务,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
TM和RM可以理解为Seata的客户端部分,引入到参与事务的微服务依赖中即可。将来TM和RM就会协助微服务,实现本地分支事务与TC之间交互,实现事务的提交或回滚。 而TC服务则是事务协调中心,是一个独立的微服务,需要单独部署。
-
部署TC服务:准备数据库表,准备配置文件,Docker部署
-
微服务集成Seata
- 为了方便各个微服务集成seata,需要把seata配置共享到nacos,因此trade-service模块不仅仅要引入seata依赖,还要引入nacos依赖:
<!--统一配置管理--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency> <!--读取bootstrap文件--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bootstrap</artifactId> </dependency> <!--seata--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-seata</artifactId> </dependency>
- 改造配置:在nacos上添加一个共享的seata配置,命名为shared-seata.yaml
seata: registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址 type: nacos # 注册中心类型 nacos nacos: server-addr: 192.168.150.101:8848 # nacos地址 namespace: "" # namespace,默认为空 group: DEFAULT_GROUP # 分组,默认是DEFAULT_GROUP application: seata-server # seata服务名称 username: nacos password: nacos tx-service-group: hmall # 事务组名称 service: vgroup-mapping: # 事务组与tc集群的映射关系 hmall: "default"
- 改造trade-service模块,添加bootstrap.yaml
spring: application: name: trade-service # 服务名称 profiles: active: dev cloud: nacos: server-addr: 192.168.150.101 # nacos地址 config: file-extension: yaml # 文件后缀名 shared-configs: # 共享配置 - dataId: shared-jdbc.yaml # 共享mybatis配置 - dataId: shared-log.yaml # 共享日志配置 - dataId: shared-swagger.yaml # 共享日志配置 - dataId: shared-seata.yaml # 共享seata配置
- 改造application.yaml文件
server: port: 8085 feign: okhttp: enabled: true # 开启OKHttp连接池支持 sentinel: enabled: true # 开启Feign对Sentinel的整合 hm: swagger: title: 交易服务接口文档 package: com.hmall.trade.controller db: database: hm-trade
-
添加数据库表:seata的客户端在解决分布式事务的时候需要记录一些中间数据,保存在数据库中。
-
将业务类上得@Transactional注解改为Seata提供的@GlobalTransactional:标记事务的起点,将来TM就会基于这个方法判断全局事务范围,初始化全局事务。
-
Seata支持四种不同的分布式事务解决方案:XA,TCC,AT,SAGA,XA模式的介绍
-
XA 规范 是 X/Open 组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准,XA 规范 描述了全局的TM与局部的RM之间的接口,几乎所有主流的数据库都对 XA 规范 提供了支持。
-
RM一阶段的工作:注册分支事务到TC;执行分支业务sql但不提交;报告执行状态到TC
-
TC二阶段的工作:TC检测各分支事务执行状态:如果都成功,通知所有RM提交事务;如果有失败,通知所有RM回滚事务
-
RM二阶段的工作:接收TC指令,提交或回滚事务
-
优点
- 事务的强一致性,满足ACID原则
- 常用数据库都支持,实现简单,并且没有代码侵入
-
缺点
- 因为一阶段需要锁定数据库资源,等待二阶段结束才释放,性能较差
- 依赖关系型数据库实现事务
-
实现步骤
-
在Nacos中的共享shared-seata.yaml配置文件
seata: data-source-proxy-mode: XA
- 利用@GlobalTransactional标记分布式事务的入口方法
-
-
AT模式的介绍:AT模式同样是分阶段提交的事务模型,不过缺弥补了XA模型中资源锁定周期过长的缺陷。
-
阶段一RM的工作:注册分支事务,记录undo-log(数据快照),执行业务sql并提交,报告事务状态
-
阶段二提交时RM的工作:删除undo-log即可
-
阶段二回滚时RM的工作:根据undo-log恢复数据到更新前
-
区别:
- XA模式一阶段不提交事务,锁定资源;AT模式一阶段直接提交,不锁定资源。
- XA模式依赖数据库机制实现回滚;AT模式利用数据快照实现数据回滚。
- XA模式强一致;AT模式最终一致(一阶段提交后,二阶段回滚前,其他业务差数据库,就会出现短暂的数据不一致问题)
-