Niko's blog

用幂等支持重试

2022-04-24

公司的银行核心系统是一个购买的系统,由外包公司负责,该公司是一家专做银行核心系统的上市公司。经过几次跟他们对接后发现,虽然是上市公司,但实际上并没有外面看去那么光鲜,一个重试的支持都没有。

为什么要重试

网络是不稳定的,会出现抖动、延迟等,这些可能做过后台系统开发的人都有共识的。网络一旦出现不稳定,在你发出去的请求还有收到响应就已经超时了,对于调用方来说其实能做的事情就不多了,要么去重试、要么去查询刚才的请求是否成功。
在现在的微服务架构下,很有一种orchestration 的编排方式。业务系统中往往会有一个biz层或者contoller 层,它会中间调度很多个各种各种各样的服务来完成一个业务的,因为要调用的服务会很多中间有一个服务超时,那么对于用户来说就是超时了。
体验上让用户重试一下也许会更好。对于调用方来说,如果出现超时的情况,直接重试一下,那么它在处理网络请求的时候就会简单多了。

重试需要什么前提

一个操作能否支持重试就要看它多次操作是否会产生side effects。举个例子来说,比如银行转账,在发起转账时因为网络的原因第一次提交失败了,然后进行了一次重试,最后发现生成了两笔转账,那么这就产生了side effect。
一个操作可以重试,它最终的结果最终对资源产生 at most once 的操作,最多一个次。

刚才转账的例子来说,一旦出现超时,客户端很难决定接下来要怎么做的,简单重试那么就会造成资金的损失。当然可以留给对账去处理,用最终的一致性去保证,这里就会要引入很多的补偿逻辑了。

幂等

一个操作支持幂等,那么对于同一个请求进行多次重试也不会产生side effect。

幂等key

要支持幂等,那么要识别出哪些请求可能是潜在的重试请求,并把它拦截下来。一个常规的做法就是根据请求的入参,按照一定的规则生成一个hash,只要hash 是一致的那么就认为它是重试的请求,这个过程完全由服务端去处理。这个方法在某些情况下,
虽然是一样的入参但是对于调用方来说可能就是对资源的两次操作,那么入参可能需要加一些随机的入参或者时间戳等,来保证hash 的冲突减少。
另外一种就是由调用方主动提供一个token 当作幂等key,服务提供者会记录这个token,只要是有重复的token 存在就可以认为是重试的请求。从业务系统的角度来讲,这个token 就可以用业务的唯一id了。

响应

继续刚才转装的例子,在重试时,服务端识别到是一个重复的请求,那么它改怎么返回呢?一个类似AreadyExist 的错误码?还是按照接口正常的调用时,把结果返回给重试的请求?
从客户端的角度来讲,在它重试后拿到一个AreadyExist 的code,它也无法进行它下一步的操作。正常来说只有在正常操作拿到结果后才能进行下一步的操作。这么看来按照正常调用的结果返回,对于使用方说就比较友好了,更符合对于重试的理解。

在这个重试的过程中,还是需要区分请求客户端的错误还是服务端的错误。如果请求的入参本来就是错误的,即便再怎么重试也不可能最终获得成功的。

幂等key 的保存时长

常规的业务系统来说,一般都直接把key 就持久化,也不一定会物理删除。在某些系统下,在重试的过程中,有可能前面的请求早就成功了,但是在收到重试请求时,之前请求的资源却被删除了,那么怎么返回呢?能识别它是重试的请求吗?
这种情况下需要根据不同的系统来做决定了,也许可以返回报错,也可以把它识别为重试请求,并把正常结果返回。如果是不会永久保存记录的情况下,可能要对幂等key 的保存设计一个时长。

如果调用方改掉了幂等key 呢

既然是由调用方决定的key,如果它在重试时改表了幂等用的token,把它当作一个新的请求也是合理的。