近况
好久没有水博客了,之前本来打算写篇docker部署nacos集群,但碰到问题了,不知道问谁,各种折腾,
结果给搁置下来了,今天来水下sentinel和seata吧,随便记记,不然光看网课啥也不做很没有动力啊。
sentinel主要介绍下客户端的配置,与openfeign的整合,sentinel的持久化
seata就做一个案例,然后介绍下几个实现分布式事务的模型吧,以后忘记也可以过来看看。
Sentinel
分布式系统的流量防卫兵,比hystrix更强大的流控,熔断,降级工具
sentinel分为核心库和dashboard控制台,dashboard控制台允许我们实时配置各种规则,并且实时生效
当我们在dashboard控制台配置好规则后,sentinel dashboard会将规则配置发送到sentinel核心库(客户端)。
注意:sentinel核心库,也被称为sentinel客户端,与springcloud等进行整合后,sentinel客户端
运行在微服务内部,并开启一个端口,监听来自dashboard的配置数据,并且使规则配置实时生效
所以微服务的端口和sentinel客户端的端口不是一样的,另外,sentinel dashboard控制台常常与微服务配置在
同一个服务器,因为我分开配置,可能因为请求延迟过大会导致监听不了微服务,所以最好配置在一起。
下面是sentinel的具体内容介绍
sentinel dashboard配置和sentinel客户端的配置
sentinel dashboard的配置
直接官网下载sentinel dashboard控制台,或者点击此处去下载:sentinel dashboard
然后在有java环境中运行启动命令:java -jar [-Dserver.port=xxx] sentinel-dashboard-1.8.2.jar
指定端口号是可选的,如果不指定端口号,则默认端口号为8080sentinel客户端与cloud的整合
经过上述配置,sentinel dashboard成功可以运行起来,但是想要监控微服务,需要微服务整合
sentinel客户端,这样,当sentinel客户端启动后,sentinel dashboard就能监控到了。整合如下:
1,导入依赖<!--用于sentinel的持久化,将sentinel配置的限流规则持久化到nacos上--> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <!--nacos注册和配置依赖,以后用来做sentinel持久化需要--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-nacos-config</artifactId> </dependency> <!--一般做dashboard都需要actuator的支持,即暴露出微服务的端点数据--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
2,配置application.yaml
server: port: 8401 spring: application: name: MySentinelService cloud: nacos: discovery: server-addr: 47.98.138.53:8849 #nacos的地址 config: server-addr: 47.98.138.53:8849 sentinel: transport: dashboard: localhost:8080 port: 2346 #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直到找到未被占用的端口 clientIp: localhost #port和clientIp指的是sentinel客户端,整合在微服务内部,结合下面可知,三个配置最好在同一台机器。 #sentinel dashboard和sentinel客户端不再同一台机器无法访问 # filter: #如果不配置filter,则sentinel监控controller的url只能监控一层 # url-patterns: /** management: endpoints: web: exposure: include: '*' endpoint: sentinel: enabled: true #开启actuator对sentinel的支持
3,配置好controller接口,然后使用启动微服务
@RestController @Slf4j public class TestController { @RequestMapping("/test1") public String test1(){ System.out.println("test1"); return "test1"; } @RequestMapping("/test2") public String test2(){ System.out.println("test2"); return "test2"; } }
4,当我们启动后,刷新sentinel dashboard发现并没有我们的微服务,是因为sentinel客户端默认懒加载
需要我们先调用一下微服务的接口,然后我们的微服务才能被sentinel dashboard监控到,
如果想关闭懒加载,则在配置文件中进行以下配置:spring: cloud: sentinel: eager: false
对了,sentinel dashboard用户名密码默认都是sentinel
上述便是sentinel客户端与微服务的整合
sentinel dashboard的各个配置介绍
- 实时监控
- 簇点链路
- 流控规则
- 熔断规则
- 热点规则
- 系统规则
- 授权规则
- 实时监控
sentinel中@SentinelResource注解
该注解标注在方法上,被标注的方法被称为资源,可以被sentinel监控到,该注解的value值就是资源名
当然,我们降级方法,异常处理方法也在该注解上配置。@SentinelResource注解有个blockHandler选项,则是指定降级方法,当发生熔断降级时,执行该方法
还有一个blockHandlerClass选项,如果没有指定blockHandlerClass选项,则降级方法配置在业务类中
如果指定该选项则去指定的类中寻找对应的降级方法@RequestMapping("/handle") @SentinelResource(value = "handle", blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handlerException") public String globalExceptionTest(int id){ System.out.println(id); return "正常的处理逻辑"+id; } /* 上述表示,如果该资源被降级熔断,则去CustomerBlockHandler类中执行handlerException方法 注意:handlerException方法返回值和参数列表务必与业务方法一致,参数列表可以多一个 BlockException exception参数 */
blockHandler选项指定的降级方法只能处理资源被熔断的情况,如果资源方法本身发生异常,
blockHandler选项是处理不了的,所以我们需要fallback选项,相应的有一个fallbackClass选项
该注解需要注意的点:
1,SentinelResource注解只能修饰public方法 2,可以根据url限流或者SentinelResource的资源名限流 3,如果SentinelResource注解配置了blockHandler选项,则找本类中找降级方法, 如果同时配置了blockHandlerClass选项,则去指定的类中找降级方法。 4,如果降级方法被放在其他类中,则该降级方法必须是静态的 使用这种方式,在sentinel控制台配置的是根据资源名限流,而不是url路径 4,如果配置了SentinelResource注解,但没有配置blockHandler选项, 则使用sentinel默认的降级方法:返回"Blocked by Sentinel (flow limiting)"字符串 5,需要注意的是,如果方法出现异常,blockHandler选项所指定的降级方法是处理不了的,需要开启fallback选项,指定fallback方法 当blockHandler选项和fallback选项都配置时,会先走fallback指定的处理异常的方法, 如果之后触发了限流熔断规则才会走blockHandler指定的方法。 6,SentinelResource注解有一个exceptionToIgnore选项,该选项的值是一个对象,你可以指定某些异常发生时 不走fallback指定的方法,而是直接页面报错error page 7,如果在openfeign和sentinel一起使用,则需要开启sentinel对feign的支持,在微服务配置文件中配置如下: feign: sentinel: enabled: true 然后处理逻辑还是像之前的方式处理,只不过底层从使用默认的hystrix变成了sentinel.但是使用方式不变
sentinel与feign的整合
导入依赖
<!--openfeign--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!--sentinel--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> <!--如果引入sentinel导致返回格式从json变成了xml,需要添加--> <exclusions> <exclusion> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-xml</artifactId> </exclusion> </exclusions> </dependency>
开启openfeign注解@EnableFeignClients,然后在配置文件中开启sentinel对feign的支持
#设置feign客户端超时时间(OpenFeign默认支持ribbon) ribbon: #指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间 ReadTimeout: 5000 #指的是建立连接后从服务器读取到可用资源所用的时间 ConnectTimeout: 5000 #开启feign对sentinel的支持 feign: sentinel: enabled: true
在@FeignClient注解上指定熔断降级类,该类需实现FallbackFactory接口,重写方法,重写的方法
正是降级方法@Component @FeignClient(value = "nacos-service",fallbackFactory = BaseFeignServiceFallback.class) @RequestMapping(Constant.MAPPING_BASE_PREFIX) public interface BaseFeignService { @GetMapping("要和nacos-service服务同样的请求地址") CommonResult queryArmySysCodeItemList(@PathVariable(value = "setId", required = false) Long setId); }
Component @Slf4j public class BaseFeignServiceFallback implements FallbackFactory<BaseFeignService> { /** * 提高容错率 防止服务雪崩 */ @Override public BaseFeignService create(Throwable throwable) { return new BaseFeignService() { @Override public CommonResult queryArmySysCodeItemList(Long setId) { log.info("远程服务调用出现问题了"); log.error("{}", throwable); return CommonResultUtil.getCommonResult(null); } }; } }
sentinel的持久化
这里有很多资料,以及大佬的实现,我的任务是集中这些资源,方便以后查看
Seata
本来是想做一个seata案例,不过打算留在之后吧,之后会做一个案例结合微服务各个知识点
openfeign,nacos,ribbon,gateway,sentinel,seata,案例就和网课老师的一样
用户下订单,订单增加,库存扣减,账户余额扣减,留坑到以后写吧,我这个人太懒了
这里随便记录下seata吧,毕竟,我的博客也没人看
说到分布式事务,我们知道,之前的事务是本地事务,即我们只有一个数据库,拿锁,修改数据,释放锁,
大概如此,分布式中,不同微服务使用的是各自的数据库,一个请求下来,会调用多个微服务,会产生多个微服务
事务,我们需要把这些事务看成一个整体,这些事务中,任意一个事务失败,则所有事务都需要回滚或者进行数据补偿
保证数据一致性,那么如果保证所有的事务被当成一个整体事务,如何保证这个整体事务同样具备ACID原则,
这就是seata所做的事情,seata中有四种解决方案实现分布式事务。
在介绍上面的分布式事务解决方案之前,来看下seata事务管理中三个重要角色
- TC (Transaction Coordinator) - 事务协调者:维护全局和分支事务的状态,协调全局事务提交或回滚
- TM (Transaction Manager) - 事务管理器:定义全局事务的范围、开始全局事务、提交或回滚全局事务。
- RM (Resource Manager) - 资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

将上述翻译一下,现在的框架设计大体是CS架构,框架有个服务端server,微服务作为客户端,TC就是seata-server
微服务添加seata依赖作为seata的客户端。一个请求可能产生多个微服务事务,TM是全局事务,说的是:
最开始开启事务的方法,A调用B,执行B方法,B方法中调用其他微服务的方法,这些方法都会产生一个事务
这就是RM,可以这么理解吧,而B方法就是全局事务的开始,可以简单认为B方法就是全局事务,只要我们通过
@GlobalTransactional注解标注的方法,我们就可以认为该方法是全局事务,暂且这么认为吧,很多底层东西不懂
Seata中四种分布式事务解决方案
XA模式
简单理解:seata解决方案中通常把操作分成2个阶段,第一个阶段决定本地事务提不提交,第二个阶段决定 如何保证数据一致性,是通过回滚保证还是通过补偿数据方式。
XA模式下,第一阶段,所有分支事务修改完本地数据后,都不提交,等到所有分支事务修改完成后,
在二阶段由TC决定是否提交本地事务,这是种强一致性的实现。
这里的一致性指的是:各个事务数据一致性,当某个事务失败后,其他事务数据回到原来的状态。
一致性分为强一致性和最终一致性,强一致性表示,数据没有其他状态,数据在任何时候都是正确的。
最终一致性指的是,事务提交后,我通过执行反向sql,使得数据还原了,从而保证了数据一致性。
这种数据一致性的实现方式就被称为最终一致性,数据经过最后修正才保持一致。AT模式
AT模式指的是:各个分支事务在第一阶段全部提交,但在提交之前和提交之后对数据做一个保存,或者说镜像 到第二阶段时,TC决定提交还是回滚,当然,第一阶段中已经提交的事务回滚不了,但是通过之前保存的镜像 我们仍然可以保证数据回到过去,实现数据最终一致性。
我们可能会想,既然数据执行之前记录快照,为什么之后还需要对数据做快照,这个快照是干嘛的?
OK,这是个好问题,让我们细致的说下AT模式中前快照的用途- 请求来到TM方法,TM中调用其他微服务方法产生分支事务,分支事务向TC注册信息
分支事务修改数据前记录数据,分支事务执行修改语句,分支事务修改数据后记录数据,分支事务提交 - 二阶段中,TC决定是否回滚,其实分支事务已经提交了,没法回滚,所以如果要回滚数据,就需要分支事务修改之前的数据,拿过来这个数据,直接覆盖即可,因为分支事务已经提交,所以TC二阶段也只是把
前快照,后快照删除即可。
上面操作其实会产生一个脏写问题,问题出在一阶段中,分支事务全部提交,二阶段如果要回滚
则拿到前快照,对现在分支事务的数据覆盖,可是如果一阶段中我们的分支事务提交后,二阶段还没开始的时候
另一个分支事务修改了同样的数据,在二阶段做回滚的时候,我们就回到了最初的数据,那另一个分支事务
修改数据的操作就被覆盖掉了,即造成了脏写问题。那么如何解决AT模式的脏写问题呢?AT模式引入了一个全局锁的概念,来看看全局锁在上述过程是干嘛的
事务在提交事务时,必须拿到全局锁才能提交,而二阶段回滚事务时必须拿到本地锁才能数据恢复 这样会造成事务1,2互相持有锁并等待对方锁,造成死锁,因为本地锁比全局锁时间等待时间长,所以 在事务1提交数据之后,二阶段恢复数据之前期间修改数据是没有效果的,会导致任务超时,修改不了数据 通过全局锁方式隔离两个事务,避免了死锁问题。
还记得最初的问题么?既然有了前镜像,为什么还要有后镜像?
如果事务2是seata管理的事务,我们可以通过全局锁实现写隔离,但是如果事务2不是seata管理的事务, 则没有全局锁怎么办?这个时候我们在修改数据之后做一个快照,这样在二阶段恢复数据的时候, 如果现在的数据和后镜像数据一致,说明事务1修改数据之后,二阶段回滚数据之前,没有其他事务修改数据 我们可以恢复数据,如果现在的数据和后镜像数据不一致,说明数据被动过,这样我们便不能去恢复数据。 因为如果直接恢复数据,则其他事务对数据的修改被覆盖,造成脏写问题,这个时候就需要人工干预了。 所以后镜像是用来判断,非seata管理的事务有没有修改数据,没有可以恢复数据,有则恢复不了。
AT模式需要我们在数据库中创建一个undo_log表,用过临时保存前镜像和后镜像,二阶段中该表中数据会被删除
- 请求来到TM方法,TM中调用其他微服务方法产生分支事务,分支事务向TC注册信息
TCC模式
TCC模式只能处理特定的事务,即资源要可预留,因为没有锁,所以性能很高,但是也有其他问题
- 回滚代码由人工编写,较复杂
- 明明是一个事务,因使用TCC模式,我们需要多建资源预留表,我们在confirm中需要进行多次事务操作
修改业务数据表,修改预留表数据。 - TCC模式有空回滚和业务悬挂的问题
SAGA模式
saga方案个人不太了解,通过补偿业务进行回滚显然是数据最终一致性实现,没有锁表示性能很好 有脏写问题可能数据隔离性不太行,具体细节我太了解,暂且先记录一下。
四种分布式事务解决方案的对比
seata资料
seata服务端就是seata-server,运行它之前,我们需要创建一个seata数据库,并把该数据库信息配置在
seata-server中,另外在AT模式下,我们需要在业务数据库表中创建undo_log表,用来记录前后快照的。
具体的搭建步骤请点击链接,我已经把seata的资料放在github上了,seata资料