这次分析实现一个接口的幂等性框架
首先是幂等框架的需求分析
我们将限流框架应用场景规定为了一个公共的服务平台,公司内部其他的金融产品的后台系统,会调用公共平台的服务,从而避免了从零开发,公共平台提供的Restful接口,为了简化开发,一般使用feign框架来访问公共服务平台的接口
调用方访问公共服务平台的接口,常见的结果会出现,成功,失败,超时,如果是失败或者成功,那么结果会很明确,调用方可以自己决定收到的结果如何处理,结果为成功,就OK了,结果为失败,就需要将调用失败的结果,返回给用户
但是当接口请求超时了,处理起来就没那么容易,很可能业务逻辑已经执行了,但是公共平台的响应超时了,导致没有成功,或者数据库存在集中写入,但是部分数据写入超时了,导致超时的原因很多,调用方发生调用超时的问题,如何解决呢?
对于操作,需要注意一些诸如ABA问题,如何进行处理
比如更新操作 update, update x = x + delta 操作并非幂等,而update x = y,看起来幂等,但是实际上也并非幂等,同样存在着ABA问题
对于修改操作,多次执行会导致业务上的错误
所以可以利用插入的数据中包含的数据库唯一键来保证,不会重复插入数据,我们会建议调用放按照这样几种方式来处理
1.调用方访问公共服务平台发现超时,将返回给用户超时,让用户自己判断是否重试,不过,可能用户看到了超时,于是发起了重新操作,转账,充值等操作,这样的操作都是用户自己发起的,无法去控制用户第二次的操作是否是新的新的操作,还是基于上一次操作的重试机制
2.调用方通过调用其他的接口,来查询超时操作的结果,明确超时操作对应的业务,到底是执行成功了,还是失败了,在基于明确的结果来处理,这样会有着两倍的接口编写
3.调用方在遇到了接口超时之后,自己发起了重试操作,这就需要接口支持幂等,在业务代码中自己触发重试,还是将重试的操作放在feign中完成,都可以,但是放在业务逻辑中实现,会多出一些冗余代码,当然,如果数量不多,可以处理,但是多了,最好是将超时重试的非业务相关的逻辑,放在框架层面解决
对于响应时间敏感的调用方来说,服务的是移动端的用户,过长的等待时间,不如直接返回超时给用户,在这种情况下,第一种处理方式比较推荐的,对于响应时间不敏感的调用方,后两个可以,因为可以提高处理效率,第三种保证接口幂等性的处理方式,是比较通用的开发手段,我们要针对这种方式,去抽象出一套统一的幂等框架,简化幂等接口的开发
需求分析
我们进行框架的简要分析之前,先说一下,幂等性
什么是幂等性,超时重试需要接口的幂等的支持,我们要对需求进行一些更加详细的分析和整理
不过,我们先搞清一个概念,幂等号
就是针对同一个接口,多次发起同一个业务请求,只会执行一次,那么如何保证多次调用是一次业务请求的呢?如何判断两次的接口之间的关系呢?
这样,就需要给同一个业务添加一个唯一表示,也就是幂等的标识,如果多个接口请求,去携带着相同的幂等号,判断是重试关系,是用一个业务请求,不要重复执行
幂等号需要具有全局的唯一性,可以具有业务含义,比如手机号是唯一的,所以可以作为幂等号,不过,这样就和业务想耦合了,无法脱离具体的业务,所以,可以使用某种算法来随机生成没有业务含义的幂等号
接下来进行一些框架的功能性需求分析
我们先进行借助用户示例和测试驱动开发的思想,去思考,最终框架被开发出来后,如何被使用的,Demo如下
///////// 使用方式一: 在业务代码中处理幂等 ////////////
// 接口调用方 Idempotence idempotence = new Idempotence(); String idempotenceId = idempotence.createId(); Order order = createOrderWithIdempotence(…, idempotenceId); // 接口实现方 public class OrderController { private Idempotence idempontence; // 依赖注入 public Order createOrderWithIdempotence(…, String idempotenceId) { // 前置操作 boolean existed = idempotence.check(idempotenceId); if (existed) { // 两种处理方式: // 1. 查询order,并且返回; // 2. 返回duplication operation Exception } idempotence.record(idempotenceId); //…执行正常业务逻辑 } public Order createOrder(…) { //… } } ///////// 使用方式二:在框架层面处理幂等 ////////////// // 接口调用方 Idempotence idempotence = new Idempotence(); String idempotenceId = idempotence.createId(); //…通过feign框架将幂等号添加到http header中… // 接口实现方 public class OrderController { @IdempotenceRequired public Order createOrder(…) { //… } } // 在AOP切面中处理幂等 @Aspect public class IdempotenceSupportAdvice { @Autowired private Idempotence idempotence; @Pointcut(“@annotation(com.xzg.cd.idempotence.annotation.IdempotenceRequired)”) public void controllerPointcut() { } @Around(value = “controllerPointcut()”) public Object around(ProceedingJoinPoint joinPoint) throws Throwable { // 从HTTP header中获取幂等号idempotenceId // 前置操作 boolean existed = idempotence.check(idempotenceId); if (existed) { // 两种处理方式: // 1. 查询order,并且返回; // 2. 返回duplication operation Exception } idempotence.record(idempotenceId) Object result = joinPoint.proceed(); return result; } } |
主要的流程在于,接口的调用方生成幂等性,并且跟随着接口请求,将幂等号传递给接口实现方,接口实现方将接口请求拿到了,按照约定,从Http Header或者参数中,拿到幂等号,然后查询幂等框架,如果幂等号已经存在,说明正在执行或者已经执行,如果不存在,说明不存在,业务没有执行过,记录幂等号,继续执行
对于幂等框架的非功能性需求
首先是易用性,需要框架接入简单方便,学习成本低,只需要简单的配置和少许的代码就可以完成接入,在一些不和业务耦合地方接入,而不是耦合在业务代码中,在性能方面,针对每个幂等接口,在正式的处理之前,都需要保证幂等的处理逻辑能走通,这就需要让幂等框架尽可能的低延迟,减少对接口请求本身的响应影响
在容错的方面,不能因为框架本身的异常,而导致接口响应异常,影响服务本身的可用性,于是框架要具有高度的容错性,比如,存储幂等性的外部存储器挂了,也不能因此导致业务不能执行
本章重点:
我们总结了一下幂等框架,而且对于幂等框架存在的接口超时重试进行了分析,现在即使开源的东西很多,但是幂等框架很少见,因为幂等的保证是和业务强相关的,大部分保证幂等性的方式都是针对具体的业务数据中ID唯一性来处理插入操作的幂等性,针对需要幂等的业务逻辑,单独别写代码
于是,我们希望可以简化幂等接口的开发,开发一套统一的幂等框架,脱离具体的业务,让程序要通过简单的配置和少量代码,进行幂等性的改造
课后思考:
1.重试无处不在,比如nginx,dubbo,feign,还有什么重试机制呢?
2.超时重试是接口幂等性框架的一个需求,还有什么场景需要幂等设计?
1.TCP维护的重试机制
2.对于支付接口的幂等性需求,一般支付型接口都是有着调用其他接口查询结果的方式