一、微服务保护
1、雪崩
雪崩问题:微服务调用链中的某个服务故障,引起整个链路中的所有微服务都不可用,这就是雪崩。
原因:
- 微服务相互调用,服务提供者出现故障或阻塞
- 服务调用者没有做好异常处理,导致自身故障。
- 调用链中的所有服务级联调失败,导致整个集群故障。
2、服务保护方案
1)请求限流:限制访问微服务的请求的并发量,避免服务因流量激增出现故障。
2)线程隔离:也叫做舱壁模式,模拟船舱隔板的防水原理。通过限定每个业务能使用的线程数量而将故障业务隔离,避免故障扩散。
3)服务熔断:由断路器统计请求的异常比例或慢调用比例,如果超出阈值则会熔断该业务,则拦截该接口的请求。熔断期间,所有请求快速失败,全都走fallback逻辑。
服务保护技术
3、Sentinel
①初识Sentinel
是阿里巴巴开源的一款微服务流量控制组件。提供了一个核心库,(可以理解为jar包),包含限流等功能,微服务引入其即可使用,只需要配置规则,用编码实现(比较麻烦)或者使用控制台。
Sentinel 的使用可以分为两个部分:
核心库(Jar包):不依赖任何框架/库,能够运行于 Java 8 及以上的版本的运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。在项目中引入依赖即可实现服务限流、隔离、熔断等功能。
控制台(Dashboard):Dashboard 主要负责管理推送规则、监控、管理机器信息等。
使用:
1)下载
将课程资料的jar包放在任意非中文、不包含特殊字符的目录下,重命名为sentinel-dashboard.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)微服务整合
我们在cart-service
模块中整合sentinel,连接sentinel-dashboard
控制台,步骤如下: 1)引入sentinel依赖:
<!--sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
修改application.yaml文件,添加下面内容:
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8090
之后在购物车就可以看到了:
簇点链路
所谓簇点链路,就是单机调用链路,是一次请求进入服务后经过的每一个被Sentinel
监控的资源。默认情况下,Sentinel
会监控SpringMVC
的每一个Endpoint
(http接口,就是controller)。
因此,我们看到/carts
这个接口路径就是其中一个簇点,我们可以对其进行限流、熔断、隔离等保护措施。
不过,需要注意的是,我们的SpringMVC接口是按照Restful风格设计,因此购物车的查询、删除、修改等接口全部都是/carts
路径。默认情况下Sentinel会把路径作为簇点资源的名称,无法区分路径相同但请求方式不同的接口,查询、删除、修改等都被识别为一个簇点资源,这显然是不合适的。
所以我们可以选择打开Sentinel的请求方式前缀,把请求方式 + 请求路径
作为簇点资源名:
首先,在cart-service
的application.yml
中添加下面的配置:
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8090
http-method-specify: true # 开启请求方式前缀
然后,重启服务,通过页面访问购物车的相关接口,可以看到sentinel控制台的簇点链路发生了变化:
②请求限流
点击流控,选择QPS即每秒钟请求的数量,在单机阈值填写数量
测试:
下载Jemeter,资料中提供了压缩包,解压缩即可,在bin目录下 双击其中的jmeter.bat执行程序。命令框不可以关闭。
设置语言:
添加测试
输入一些数据 ,配置线程
在线程组右键添加取样器,http请求
添加数据进行配置
添加监听器可以添加汇总报告以及查看结果树
注:资料中提供了测试,直接拖进来即可。
选中想要测试的,右键启动即可 ,启动之后就可以点击查看结果树以及汇总报告来查看测试
③线程隔离
同样是点击流控
打开后选择并发线程数,输入线程数,这里的5是可以用5个线程,比如响应时间是500毫秒,那么一秒就可以处理两个请求,即一个线程每秒可以处理两个请求。
进行测试首先要①对商品服务进行修改进行模拟线程延迟
@ApiOperation("根据id查询商品")
@GetMapping("{id}")
public ItemDTO queryItemById(@PathVariable("id") Long id) {
//模拟业务延迟
ThreadUtil.sleep(500);
return BeanUtils.copyBean(itemService.getById(id), ItemDTO.class);
}
②对购物车服务进行修改
需要注意的是,默认情况下SpringBoot项目的tomcat最大线程数是200,允许的最大连接是8492,单机测试很难打满。所以我们需要配置一下cart-service模块的application.yml文件,修改tomcat连接:
server:
port: 8082
tomcat:
threads:
max: 50 # 允许的最大线程数
accept-count: 50 # 最大排队等待数量
max-connections: 100 # 允许的最大连接
如果没有线程隔离,查询购物车将服务器的资源耗尽,修改购物车也会受到影响,变得很慢。如果添加了线程隔离,查询购物车很慢但不会影响到修改购物车了。
④Fallback
1、将FeignClient作为Sentinel的簇点资源,修改cart-service模块的application.yml文件:
feign:
sentinel:
enabled: true # 开启feign对sentinel的支持
FeignClient的Fallback有两种配置方式:
方式一:FallbackClass,无法对远程调用的异常做处理。
方式二:FallbackFactory,可以对远程调用的异常做处理,通常都会选择这种。
给feignClient编写Fallback逻辑
1)自定义类,实现FallbackFactory,编写对某个FeignClient的fallback逻辑:
@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);
}
};
}
}
2)将刚刚定义的ItemClientFallbackFactory注册为一个bean:
@Bean
public ItemClientFallbackFactory itemClientFallbackFactory() {
return new ItemClientFallbackFactory();
}
3)在ItemClient中的@FeignClient注解上添加上新建的类
@FeignClient(value = "item-service",fallbackFactory = ItemClientFallbackFactory.class)
⑤服务熔断
熔断是解决雪崩问题的重要手段。思路是由断路器统计服务调用的异常比例、慢请求比例,如果超出阈值则会熔断该服务。即拦截访问该服务的一切请求;而当服务恢复时,断路器会放行访问该服务的请求。
在sentinel控制台中点击熔断
填写相关的规则
二、分布式事务
在分布式系统中,如果一个业务需要多个服务合作完成,而且每个服务都有事务,多个事务必须同时成功或失败,这样的事务就是分布式事务,其中每个服务的事务就是一个分支事务,整个业务称为全局事务。
解决思路:添加一个事务协调者,让它跟每个每个事务都建立联系。
1、认识Seata
在Seata的事务管理中有三个重要的角色:
TC (Transaction Coordinator) - 事务协调者:维护全局和分支事务的状态,协调全局事务提交或回滚。
TM (Transaction Manager) - 事务管理器:定义全局事务的范围、开始全局事务、提交或回滚全局事务。
RM (Resource Manager) - 资源管理器:管理分支事务,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
TC作为事务协调者是独立于微服务之外的,所以应搭建TC服务,在微服务中引入Seata依赖,会提供TM,RM的功能。
Seata架构
2、部署TC服务
1、准备数据库表
Seata支持多种存储模式,但考虑到持久化的需要,我们一般选择基于数据库存储。执行课前资料提供的《seata-tc.sql》
,导入数据库表。
2、准备配置文件
课前资料准备了一个seata目录,其中包含了seata运行时所需要的配置文件:
将整个seata文件夹拷贝到虚拟机的/root
目录,将tar包也传进去(使用docker load -i seata.tar 将tar包转为镜像)
3、Docker部署
需要注意,要确保nacos、mysql都在hm-net网络中。如果某个容器不再hm-net网络,可以参考下面的命令将某容器加入指定网络:
docker network connect [网络名] [容器名]
在虚拟机的/root
目录执行下面的命令:
docker run --name seata \
-p 8099:8099 \
-p 7099:7099 \
-e SEATA_IP=192.168.100.128 \
-v ./seata:/seata-server/resources \
--privileged=true \
--network hm-net \
-d \
seataio/seata-server:1.5.2
3、微服务集成Seata
首先,在项目中引入Seata依赖:
<!--统一配置管理-->
<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.100.128: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"
4、XA模式
XA规范是x/Open组织定义的分布式事务处理标准,XA规范描述了全局的TM与局部的RM之间的接口,几乎所有主流的关系型数据库都对XA规范提供了支持。Seata的XA模式如下:
一阶段的工作:
RM注册分支事务到
TC
RM执行分支业务sql但不提交
RM报告执行状态到
TC
二阶段的工作:
TC
检测各分支事务执行状态如果都成功,通知所有RM提交事务
如果有失败,通知所有RM回滚事务
2. RM接收TC
指令,提交或回滚事务
XA
模式的优点是什么?
事务的强一致性,满足ACID原则
常用数据库都支持,实现简单,并且没有代码侵入
XA
模式的缺点是什么?
因为一阶段需要锁定数据库资源,等待二阶段结束才释放,性能较差
依赖关系型数据库实现事务
实现步骤:
首先,我们要在配置文件中指定要采用的分布式事务模式。我们可以在Nacos中的共享shared-seata.yaml配置文件中设置:
seata:
data-source-proxy-mode: XA
其次,给发起全局事务的入口方法添加@GlobalTransactional
注解。
5、AT模式
AT
模式同样是分阶段提交的事务模型,不过缺弥补了XA
模型中资源锁定周期过长的缺陷。
阶段一RM
的工作:
注册分支事务
记录undo-log(数据快照)
执行业务sql并提交
报告事务状态
阶段二提交时RM
的工作:
删除undo-log即可
阶段二回滚时RM
的工作:
根据undo-log恢复数据到更新前
实现AT模式
首先,添加资料中的seata-at.sql到微服务对应的数据库中(每个微服务都要有自己的表)
然后修改,application.yml文件,将事务模式修改为AT模式:
seata:
data-source-proxy-mode: AT
简述AT
模式与XA
模式最大的区别是什么?
XA
模式一阶段不提交事务,锁定资源;AT
模式一阶段直接提交,不锁定资源。XA
模式依赖数据库机制实现回滚;AT
模式利用数据快照实现数据回滚。XA
模式强一致;AT
模式最终一致
可见,AT模式使用起来更加简单,无业务侵入,性能更好。因此企业90%的分布式事务都可以用AT模式来解决。