事物中调用RPC
RPC的设计和使用核心是design for failure,从这个角度介绍一下事物中调用RPC,谈谈我自己的理解。
1.场景
一个事物中可能包含N个RPC调用,可能因为各种原因,RPC会调用失败,而事物又不能一直等待,因此需要做系统上的涉及保证系统能正常使用。
2.服务使用方策略
2.1.超时重试
因为RPC请求肯定都是会设置超时的,如果不设置可能造成链路阻塞,但是超时的原因多种多样,有些可能仅仅因为网络抖动。
因此重试是必不可少的,对于不同的服务可以采取不同的超时策略。
-
不进行重试的
-
业务逻辑错
-
下游系统挂掉
-
-
进行重试的
- 网络抖动
。。。
可以通过业务异常进行区分判断
-
对实时性有要求的
- 请求重要度低的
对于这样的超时可以直接执行failfast,或者降级返回mock数据。
- 请求重要的
基本上是需要根据异常情况进行是否重试,或者事物补偿/人工处理,通常来说对实时性有要求的重试会在固定时间间隔发起。
- 对实时性要求低的
这样的重试可以采用指数退避的方式,即第一次等N 第二次等2N...这样的方式,不会对下游造成太大压力
2.1.1.如何重试
重试是Design for failure的重要部分
常用的方式有:
-
failover
-
failfast(不重试)
-
failBack
2.2.幂等设计
2.2.1.是不是需要幂等
首先要明确的是不是所有请求都需要做幂等操作的,因为幂等是需要代价的(通常都依赖存储层唯一键,和全局唯一id).类似Restful接口中DELETE GET HEAD等都不需要增加幂等操作,同样RPC也一样,对数据状态不影响的请求不应该过多的增加幂等。
2.2.2.如何保证幂等
通常来说RPC需要带一个全局id(可以考虑snowflake),在接收方将id保存到数据库中,如果id冲突说明已经有此id,即消息是重复的。
2.3.对某些服务进行服务降级
降级是针对相对不重要的服务的,如果是重要服务是不能进行降级的。降级是从整体负载考虑的,因此就需要为服务定优先级,确定优先级之后才能合理的进行降级分配。
2.3.1.对非核心链路服务降级
降级一般有两种方式,自动方式和手动方式,这边主要聊一下自动方式。
2.3.1.1.自动降级
通常来说自动降级需要失败记录的配合:
-
超时降级
-
失败次数降级
-
故障降级
-
限流降级
等等。。。
2.3.1.2.降级方式
一般来说的降级方式是进行mock返回
-
force:return策略:直接返回mock数据
-
fail:return策略:调用后触发重试上限之后返回mock数据。
2.3.对不重要的服务进行熔断
降级是从整体资源分配的考虑上来考虑的,大多数是因为为了应对即将到来的大流量或者弹性的hold住大流量。而系统往往还会遇到下游系统故障导致无法正确返回的问题。这样就需要增加熔断机制,来保证不会造成压力传递,导致雪崩。
2.3.1.熔断怎么做
断路器状态有以下几个
-
关闭状态
-
打开状态
-
半开状态
2.4.事物回滚(补偿机制)
这一步通常是最后才考虑的,因为随着链路的深入,事物补偿的代价变得很大,不过如果必须走到这一步也只能做,同样也有不同策略:
因为补偿通常都是有状态有上下文的,因此需要保存上个状态的数据,可以通过以下两种模式保存
2.4.1.本机保存
直接本机保存补偿数据
-
优点:方便,快速
-
缺点:会涉及宕机丢数据,集群处理时数据混乱
2.4.2.持久化保存
持久化保存也是通常的使用方案,(这里不纠结是使用mysql还是redis,存储层的高可用不在这边讨论)
-
优点:全局共享,不会丢数据
-
缺点:可能影响性能,对存储带来压力
2.5.人工处理
如果补偿失败,则会触发人工处理,因为补偿失败意味着这个事物进退两难了,至少这个请求已经脱离系统控制。这时候就需要适合的监控报警方式。
至于如何进行人工处理不在本文讨论内容,各自系统都需要自己解决。无外乎数据修修补补,bug修复。
3.服务提供方策略
3.1.服务限流
光是调用方做处理实际上是不够的,还需要服务自身尽量保证自己不会被压力压垮,因此需要用限流的方式拒绝一些流量,来保证自己服务不会被打挂.
3.1.1.常见的限流策略
-
漏斗桶
-
令牌桶
网友评论