SpringCloud -- 服务保护和分布式事务
目录
前言
一、微服务保护
1. 微服务保护方案
1.1 请求限流
1.2 线程隔离
1.3 服务熔断
2. Sentinel
2.1 安装
2.2 微服务整合
3. 请求限流
4. 线程隔离
4.1 OpenFeign整合Sentinel
4.2 配置线程隔离
5. 服务熔断
二、分布式事务
1. 认识Seata
2. XA模式
2.1 模型架构
2.2 优缺点
2.3 实现步骤
3. AT模式
3.1 模型架构
3.2 使用步骤
3.3 AT与XA的区别
前言
在微服务远程调用的过程中,还存在一些问题需要解决。
首先是业务健壮性问题:
例如现在有这样的一个项目,当查询购物车列表的时候,需要同时查询最新的商品信息,与当前购物车中的数据做对比。如果商品微服务模块此时出现了故障,那么查询购物车列表也会出现异常。但从业务角度来看,即使查询商品出现了问题,购物车列表也应该显示出来,哪怕不包含最新的商品信息。
还有级联失效问题:
还是查询购物车的业务,如果此时对商品查询业务的并发较高,占用过多的Tomcat连接。可能会导致商品服务中所有接口响应时间增加。
此时查询购物车业务需要查询并等待商品的查询结果,因为商品查询并发较高,因此也就导致查询购物车业务响应时间变长。因为请求是源源不断的,如果前面的请求接收不到响应,就会一直占用Tomcat中的线程,后面的请求就会占用新的线程,最终可能会导致Tomcat中的资源耗尽,服务的性能就会很差。
同理,如果有其他服务需要调用购物车模块中的查询购物车功能,该服务也会受到影响,从而影响整个项目。
这就是雪崩问题。
总结:当某个服务因资源耗尽、响应延迟或代码错误而失效时,依赖它的上游服务会因等待响应而阻塞线程。若未及时释放资源,阻塞会持续蔓延,直至整个系统不可用。这一过程类似雪崩,由局部故障演变为全局瘫痪。
一、微服务保护
1. 微服务保护方案
微服务保护方案有很多,常用的有以下三种:请求限流、线程隔离、服务熔断。
1.1 请求限流
服务故障的一个重要原因就是并发太高。因此请求限流就是限制或控制接口访问的并发流量,避免服务因流量激增而出现故障。
请求限流往往会有一个限流器,数量高低起伏的并发请求曲线,经过限流器就变的非常平稳。这就像是水电站的大坝,起到蓄水的作用,可以通过开关控制水流出的大小,让下游水流始终维持在一个平稳的量。
1.2 线程隔离
当一个业务接口响应时间过长,而且并发高时,就可能耗尽服务器的线程资源,导致服务内的其他接口受到影响。就需要用到线程隔离。
线程隔离可以限定每个接口可以用到的资源范围,也就是将其“隔离”起来。
如图所示,给查询购物车的接口只分配了20个线程,这时就算查询购物车业务发生了异常,也不会影响到购物车服务中的其他接口正常运行。
1.3 服务熔断
线程隔离虽然避免了雪崩问题,但故障服务(商品服务)依然会拖慢购物车服务(服务调用方的接口响应速度)。而且商品查询的故障依然会导致查询购物车的功能出现故障,购物车的业务也就不可用了。
所以,要做两件事:
编写服务降级逻辑:就是服务调用失败后的处理逻辑,根据业务场景,可以抛出异常,也可以返回友好提示或默认数据
异常统计和熔断:统计服务提供方的异常比例,当比例过高表明该接口会影响到其他业务,此时就拒绝调用该接口,直接走降级逻辑。
2. Sentinel
2.1 安装
Sentinel是阿里巴巴开源的一款服务保护框架,目前已经加入SpringCloudAlibaba中。
首先先去GitHub中下载响应的jar包https://github.com/alibaba/Sentinel/releases。
然后将jar包放在任意非中文、不包含特殊字符的目录下,重命名为sentinel-dashboard.jar
运行下面的命令启动控制台:
java -Dserver.port=8090 -Dcsp.sentinel.dashboard.server=localhost:8090 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar
访问http://localhost:8090页面,就可以看到sentinel的控制台了:
需要输入账号和密码,默认都是:sentinel。登录后,即可看到控制台,默认会监控sentinel-dashboard服务本身:
2.2 微服务整合
若要在微服务模块中整合Sentinel,连接控制台,步骤如下:
1)引入依赖
<!--sentinel-->
<dependency><groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
2)配置yaml文件
添加如下内容(换成你自己设置的地址和ip端口):
spring:cloud: sentinel:transport:dashboard: localhost:8090
3)访问微服务模块的任意接口,以cart-service为例
查询购物车接口,sentinel的客户端就会将服务访问的信息提交到sentinel-dashboard
控制台。并展示出统计信息:
点击簇点链路菜单,会看到下面的页面:
簇点链路就是一次请求在微服务内部走过的“完整路径”,例如在电商网站点“购买”按钮,请求会经过:下单接口
→ 支付服务
→ 库存服务
→ 数据库。简单来说,就是当对一个业务进行请求时,完成该业务所需要的所有服务组成的链路。
我们看到/carts
这个接口路径就是其中一个簇点,我们可以对其进行限流、熔断、隔离等保护措施。
不过可能在一个服务中多个接口请求的地址是相同的
所以我们可以选择打开Sentinel的请求方式前缀,把请求方式 + 请求路径
作为簇点资源名:
首先,在cart-service
的application.yml
中添加下面的配置:
spring:cloud:sentinel:transport:dashboard: localhost:8090http-method-specify: true # 开启请求方式前缀
重启服务,通过页面访问购物车的相关接口,可以看到sentinel控制台的簇点链路发生了变化:
3. 请求限流
在簇点链路后面点击流控按钮,即可对其做限流配置:
弹出的菜单如下:
上述信息表明把查询购物车的这个簇点资源的流量限制在了每秒6个请求。如果我们每秒发送10个请求,通过的只有6个。
4. 线程隔离
4.1 OpenFeign整合Sentinel
我们要对查询购物车接口做线程隔离。
首先,修改购物车微服务模块的application.yml文件,开启Feign的sentinel功能
feign:sentinel:enabled: true # 开启feign对sentinel的支持
需要注意的是,默认情况下SpringBoot项目的tomcat最大线程数是200,允许的最大连接是8492,单机测试很难打满。
所以我们需要配置一下购物车模块的application.yml文件,修改tomcat连接,这样在测试的时候才可以看到效果。
server:port: 8082tomcat:threads:max: 50 # 允许的最大线程数accept-count: 50 # 最大排队等待数量max-connections: 100 # 允许的最大连接
然后重启购物车服务,可以看到查询商品的FeignClient自动变成了一个簇点资源:
4.2 配置线程隔离
点击查询商品的FeignClient对应的簇点资源后面的流控按钮:
这里勾选的是并发线程数限制,也就是说这个查询功能最多使用5个线程,而不是5QPS。如果查询商品的接口每秒处理2个请求,则5个线程的实际QPS在10左右,而超出的请求自然会被拒绝。
此时就实现了线程隔离的需求。
5. 服务熔断
线程隔离保护了购物车业务的其它接口,但是超出的QPS上限的请求就只能抛出异常,从而导致购物车的查询失败。但从业务角度来说,即便没有查询到最新的商品信息,购物车也应该展示给用户,用户体验更好。也就是给查询失败设置一个降级处理逻辑。
同时,如果查询商品的延迟比较高,从而导致查询购物车的响应时间也变的很长。这样不仅拖慢了购物车服务,消耗了购物车服务的更多资源,而且用户体验也很差。对于商品服务这种不太健康的接口,我们应该直接停止调用,直接走降级逻辑,避免影响到当前服务。也就是将商品查询接口熔断。当商品服务接口恢复正常后,再允许调用。这其实就是断路器的工作模式了。
Sentinel中的断路器不仅可以统计某个接口的慢请求比例,还可以统计异常请求比例。当这些比例超出阈值时,就会熔断该接口,即拦截访问该接口的一切请求,降级处理;当该接口恢复正常时,再放行对于该接口的请求。
断路器的工作状态切换有一个状态机来控制:
状态机包括三个状态:
-
closed:关闭状态,断路器放行所有请求,并开始统计异常比例、慢请求比例。超过阈值则切换到open状态
-
open:打开状态,服务调用被熔断,访问被熔断服务的请求会被拒绝,快速失败,直接走降级逻辑。Open状态持续一段时间后会进入half-open状态
-
half-open:半开状态,放行一次请求,根据执行结果来判断接下来的操作。
-
请求成功:则切换到closed状态
-
请求失败:则切换到open状态
-
我们可以在控制台通过点击簇点后的熔断
按钮来配置熔断策略:
二、分布式事务
项目中下单业务流程如下:
由于订单、购物车、商品分别在三个不同的微服务,而每个微服务都有自己独立的数据库,因此下单过程中就会跨多个数据库完成业务。而每个微服务都会执行自己的本地事务。
整个业务中,各个本地事务是有关联的。因此每个微服务的本地事务,也可以称为分支事务。多个有关联的分支事务一起就组成了全局事务。我们必须保证整个全局事务同时成功或失败。
每一个分支事务就是传统的单体事务,是满足ACID特性的。但是在全局事务中,ACID特性就不一定会满足了。因为它们跨越了不同的数据库,相互之间没有感知,可能已经下单成功,扣减库存了,但是购物车缺清理失败,就导致最终的结果无法统一。
1. 认识Seata
解决分布式事务的方案有很多,但实现起来都比较复杂,因此我们一般会使用开源的框架来解决分布式事务问题。在众多的开源分布式事务框架中,功能最完善、使用最多的就是阿里巴巴在2019年开源的Seata了。
在Seata的事务管理中有三个重要的角色:
-
TC (Transaction Coordinator) - 事务协调者:维护全局和分支事务的状态,协调全局事务提交或回滚。
-
TM (Transaction Manager) - 事务管理器:定义全局事务的范围、开始全局事务、提交或回滚全局事务。
-
RM (Resource Manager) - 资源管理器:管理分支事务,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
Seata的工作架构如图所示:
其中,TM和RM可以理解为Seata的客户端部分,引入到参与事务的微服务依赖中即可。将来TM和RM就会协助微服务,实现本地分支事务与TC之间交互,实现事务的提交或回滚。
而TC服务则是事务协调中心,是一个独立的微服务,需要单独部署(可以部署到docker容器当中)。
2. XA模式
2.1 模型架构
Seata对原始的XA模式做了简单的封装和改造,以适应自己的事务模型,基本架构如图:
首先TM会告知TC全局事务已开启-->调用分支事务-->注册分支事务-->执行业务sql(执行完后不对事务进行提交,而是等待)--> 报告事务状态 --> 提交全局事务 --> 提交/回滚(如果所有分支事务状态都正常就提交,若存在异常就对所有分支事务进行回滚)。
2.2 优缺点
XA
模式的优点:
-
事务的强一致性,满足ACID原则
-
常用数据库都支持,实现简单,并且没有代码侵入
XA
模式的缺点:
-
因为一阶段需要锁定数据库资源,等待二阶段结束才释放,性能较差
-
依赖关系型数据库实现事务
2.3 实现步骤
在配置文件中指定要采用的分布式事务模式
seata:data-source-proxy-mode: XA
其次,我们要利用@GlobalTransactional
标记分布式事务的入口方法:
3. AT模式
3.1 模型架构
与XA模式不同的是分支事务在执行sql之后会直接进行提交,但是会在提交事务之前保存一个快照文件,如果存在异常,就可以直接使用快照文件来恢复数据。如果都成功,快照文件就会直接删除。
3.2 使用步骤
因为Seata默认就是AT模式,所以我们只需要在分布式事务的入口方法上面加上@GlobalTransactional注解即可。
3.3 AT与XA的区别
-
XA
模式一阶段不提交事务,锁定资源;AT
模式一阶段直接提交,不锁定资源。 -
XA
模式依赖数据库机制实现回滚;AT
模式利用数据快照实现数据回滚。 -
XA
模式强一致;AT
模式最终一致