接口的幂等设计

接口幂等

什么是接口幂等?就是一个接口,被重复调用屡次,却可以保证对系统内部产生的影响是一致的,也就是调用屡次和调用一次,数据的变化是同样的,是相同的,不会由于调用屡次而出现任何数据问题。分布式系统中,接口幂等性是系统可行性论证的第一个步骤。不少地方须要把接口设计成幂等。sql

思路基本上是3种:数据库

1 当第N(N>1)次请求过来时,系统要能知道,这个业务咱们已经处理过了,相同的请求咱们忽略掉就行了
2 当第N(N>1)次请求过来时,无论三七二十一,执行执行之,底层的数据接口层面保证其幂等就行了
3 从源头上避免请求重复提交。固然,这个有必定的限制。对于用户重复点击,那么容易避免,代理端能够进行各类过滤,去重。但对于mq的状况等,可能没法避免。缓存

虽然概念上很接近,咱们也很容易混为一谈,但服务接口的幂等和数据接口的幂等 ,细分开来仍是有所不一样的。 服务接口(咱们的service层)可能包括了 对数据的操做,对文件的操做,对网络的操做,对cpu、内存的计算,还有对其余服务的操做; 而数据接口(咱们的dao层)经常限于对数据库表的CRUD(这里不讨论广义的“数据”的定义,而是内存、缓存、文件、数据库分开讨论),及其复合操做服务器

数据接口层面的幂等设计

全部的数据接口均可以归结为增删改查四大类;固然,下面咱们对这四大类接口进行分析;网络

查询和删除
查询和删除业务,自然的具备幂等的特性;架构

1. 查询
在数据不变的状况下,查询一次和查询屡次,查询结果是同样的;并发

2. 删除
删除一次和屡次删除的结果都是把数据删除;(若是第二次删除返回0 rows affected之类的,那么忽略便可)分布式

3. 新增
咱们能够把关键的 业务id 设置成惟一索引,这样,第二次会失败,惟一索引约束错误的话,调用方忽略便可。不然就出现了多条数据,一条正确的,其他的是脏数据。高并发

主要就是经过业务的相关的字段组成的一个 惟一性的约束。执行消息处理以前能够先根据这个惟一约束是否存在,若是存在,说明已经执行过了,忽略便可,不然把这个惟一约束以某种方式 保存起来。大部分状况 都会存在 至少一个 业务的惟一性的约束, 好比用户的 邮箱不能重复吧。 测试

4. 更新
若是是幂等的更新操做,好比update table1 set f1 = v1,咱们能够不用管。由于这些操做自己是幂等的。 不然咱们可能须要对update 语句进行稍稍的改写,增长where 条件,也就是乐观锁的方式。好比 update table1 set f1 = v1 where f1 = v0 (v0 是初始值)。 这样第一次update会成功,后面的 就会失败。咱们须要尽可能的把那些非幂等的update sql改写。 可是这样,有必定限制就是咱们须要 更多的参数,好比这个初始值。 并且,咱们不能排除某些操做是 不能改写的, 好比给用户增长积分等等, 原始积分就是限制的参数, 若是不能提供,那么没法改写。

服务接口层面的幂等设计

查询业务

咱们能够首先把数据加载到缓存,而后尽可能保证一个高可用的缓存,同时在 新增、更新、删除的时候维护缓存。

新增业务
新增业务类接口,咱们要解决以下两个问题
1. 同一个用户用一样的数据屡次请求同一个接口(不论是什么缘由屡次提交,他应该只请求一次)
2. 不一样用户的提交一样的数据请求同一个接口;
第一个问题能够经过防重复提交来解决;业务数据连同Token,一块儿提交给接口,同一个Token,只能被处理一次(这里要注意,只能被处理一次,应该改为只能被正确的处理一次,也就是说,咱们应该缓存某次新增业务处理的结果,若是上一次请求时出现某些异常,好比数据库链接失败,用户再次提交的时候,咱们应该放行用户的此次请求,固然有些异常就不须要放行了,好比提交的业务数据不对等);
第二个问题是没法解决的,一个开放的系统,不能杜绝两个不一样的客户端(用户)同时请求;可是能够交给数据的最后防线,存储层;经过惟一索引或惟一组合索引能够防止新增数据存在脏数据 (当表存在惟一索引,并发时新增报错时,再查询一次就能够了,数据应该已经存在了,返回结果便可) ;

注意:
Token防重复提交,只须要网关这层控制便可;Token的处理机制,还须要缓存调用的处理结果,以判断是否须要放行后续的重试请求;

更新业务

系统中的大部分业务均可以归属到更新业务,好比禁用用户、电商秒杀等等,只要是有更新操做的,不论是不是还有其余的操做,都归结到更新业务;
更新业务接口,不只须要有表单防重复提交的验证,还须要有下面这些更精细的控制,以防止高并发环境中出现脏读,幻读等引发错误的数据更新结果;
更新业务接口幂等性解决方案通常是经过各类层面的锁和CAS机制;

悲观锁
悲观锁,select for update,整个执行过程当中锁定要操做的记录;

乐观锁
更新业务的接口,好比订单付款等,须要综合使用尽量多的信息来逐步验证逐步减小直至杜绝重复消息重复处理的几率;基本思路是CAS(Compare And Set);
能够参考下面的两篇文章体会一下:
1. 《架构师之路-库存扣多了,到底怎么整》
2. 订单操做,利用订单编号和订单的状态机(序列号)

测试用例
经过下面的方法能够初步验证接口幂等性的健壮性:
1. 同一个请求,屡次提交到同一台节点,屡次提交到不一样的节点
2. 同一个请求,同时到达同一个节点,同时到达到不一样的节点
3. 有逻辑前后顺序的消息、请求乱序的处理,好比建立订单的请求和支付订单的请求,不能保证第一个请求先于第二个请求到达服务器;
--------------------- 摘抄至 https://blog.csdn.net/xichenguan/article/details/78085801-------------

 

消息消费流程的异常点

消息的消费确认流程中,任何一个环节均可能会出问题!

  • 方法:对于未确认的消息,采用按规则从新投递的方式进行处理。
  • 问题:消息的重复发送会致使业务处理接口出现重复调用的问题。

消息重复发送的缘由

被动方应用接收到消息,业务处理完成后应用出问题,消息中间件不知道消息处理结果,会从新投递消息。
被动方应用接收到消息,业务处理完成后网络出问题,消息中间件收不到消息处理结果,会从新投递消息。
被动方应用接收到消息,业务处理时间过长,消息中间件因消息超时未确认,会再次投递消息。
被动方应用接收到消息,业务处理完成,消息中间件问题致使收不到消息处理结果,消息会从新投递。
被动方应用接收到消息,业务处理完成,消息中间件收到了消息处理结果,但因为消息存储故障致使消息没能成功确认,消息会再次投递。

 

怎么实现消费方的消息幂等

1 根据业务来实现幂等,前面已经说过。

2 增长消息表来实现幂等,主要就是说,若是没法经过业务信息判断是否已经消费过,那么咱们经过mq的消息的id,存放到消息表,而后消费的时候先判断是否已经存在。经过消息ID,或生成一个惟一ID标记每一条消息,将消息处理成功和去重日志(也就是“消息表”)经过事物的形式写入去重表, 若是以前没有处理成功,那么去重日志确定是没有记录的,那么就消费,不然就不消费。 若是mq 会删除 肯定消费完了的数据,咱们能够先经过消息ID 去mq peek一下是否存在,存在则表示还未消费,而后消费方进行处理,不然就忽略。

其实这两种方式道理都差很少,能够按照具体状况来作

 

有时候,咱们还能够 经过mq自己的消息超时机制,好比根据业务需求给消息 ID 设置一个 TTL, 或者是直接用 Redis 等缓存机制来保证在合理的时间范围内不会重复消费

实现这些幂等是有成本的,我能够考虑业务状况,若是都每个消费方的操做都作幂等设计, 有时候可能成本过高,得不偿失。咱们须要权衡去重所花的代价决定是否须要实现幂等性,如:购物会员卡成功,向用户发送通知短信,发送一次或者屡次影响不大。不作幂等性能够省掉写去重日志的操做。

 

 

参考:

https://blog.csdn.net/xichenguan/article/details/78085801 

https://blog.csdn.net/qq_27384769/article/details/79307340

相关文章
相关标签/搜索