微服务架构之幂等性问题及设计思想

前言

小伙伴们有没有遇到过生产环境经常出现过重复的数据?在排查问题的时候,数据又是正常的。这个是何解呢?怎么会出现这种情况,而且还很难排查问题。今天我给大家分享一下这里的原因,以及解决方案。

罪魁祸首

产生重复数据或数据不一致(假定程序业务代码没问题),绝大部分就是发生了重复的请求,重复请求是指同一个请求因为某些原因被多次提交。导致这个情况会有几种场景:

1)微服务场景,在我们传统应用架构中调用接口,要么成功,要么失败。但是在微服务架构下,会有第三个情况【未知】,也就是超时。如果超时了,微服务框架会进行重试。

2)用户交互的时候多次点击。如:快速点击按钮多次。

3)MQ消息中间件,消息重复消费4)第三方平台的接口(如:支付成功回调接口),因为异常也会导致多次异步回调 5)其他中间件/应用服务根据自身的特性,也有可能进行重试。

我们知道了发生的原因,本质就是多次请求了,那如何解决呢?

幂等性

有些小伙伴们会想到幂等这个词,是的,就是我们在设计某些接口时,要考虑如何保证接口幂等,那什么是接口幂等呢?

网上是这样介绍的【接口的幂等性实际上就是接口可重复调用,在调用方多次调用的情况下,接口最终得到的结果是一致的】

网上的说法定义,有点不是太正确,我们看下怎么不正确

如一个线程请求用户列表接口:select * from user ,返回用户表中的数据,而另一个线程往用户表插入数据。那请求用户列表的线程返回的数据每次都不一样,那按照上面的说法,查询用户列表的接口就不是幂等的,这显然是不正确的。

老顾的理解应该是多次调用对系统的产生的影响是一样的,即对资源的作用是一样的,但是返回值允许不同。

幂等场景

我们来看一下SQL相关业务是否幂等?

一、查询select * from user where xxx ,不会对数据产生任何变化,具备幂等性。

二、新增insert into user(userId,name) values(1,'a')

如userId为唯一主键,即重复操作上面的业务,只会插入一条用户数据,具备幂等性。如userId不是主键,可以重复,那上面业务多次操作,数据都会新增多条,不具备幂等性。

三、修改,区分直接赋值和计算赋值。

1、直接赋值,update user set point = 20 where userId=1,不管执行多少次,point都一样,具备幂等性。2、计算赋值,update user set point = point + 20 where userId=1,每次操作point数据都不一样,不具备幂等性。

四、删除delete from user where userId=1 ,多次操作,结果一样,具备幂等性。

上面场景中,我们发现新增没有唯一主键约束的数据,和修改计算赋值型操作都不具备幂等性

那怎么去解决呢?

网上介绍很多,但介绍的太简单了,且关键点都没有介绍到。老顾这里只介绍常用的方案

token机制

token方式的流程,上一张图,比较清晰

微服务架构之幂等性问题及设计思想,你不得不知的一些幂等方案

上图就是Token+Redis的幂等方案,适用绝大部分场景。主要思想:

1、服务端提供了发送token的接口。我们在分析业务的时候,哪些业务是存在幂等问题的,就必须在执行业务前,先去获取token,服务器会把token保存到redis中。(微服务肯定是分布式了,如果单机就适用jvm缓存)。
2、然后调用业务接口请求时,把token携带过去,一般放在请求头部。
3、服务器判断token是否存在redis中,存在表示第一次请求,可以继续执行业务,执行业务完成后,最后需要把redis中的token删除。
4、如果判断token不存在redis中,就表示是重复操作,直接返回重复标记给client,这样就保证了业务代码,不被重复执行。

这种方案是比较常用的方案,也是网上经常介绍的,但是有一点不同的地方:

网上方案:检验token存在(表示第一次请求)后,就立刻删除token,再进行业务处理上面方案:检验token存在(表示第一次请求)后,先进行业务处理,再删除token

关键点就是 先删除token,还是后删除token。

一、网上方案缺点

我们看下网上方案,先删除token,这是出现系统问题导致业务处理出现异常,业务处理没有成功,接口调用方也没有获取到明确的结果,然后进行重试,但token已经删除掉了,服务端判断token不存在,认为是重复请求,就直接返回了,无法进行业务处理了。

二、上面方案缺点

后删除token也是会存在问题的,如果进行业务处理成功后,删除redis中的token失败了,这样就导致了有可能会发生重复请求,因为token没有被删除

小伙伴们有没有发现,其实上面的问题就是数据库和缓存redis数据不一致的问题。之前老顾分享了一篇文章,里面详细介绍了如何解决数据库和缓存redis数据不一致的问题。小伙伴们可自行查阅。

其实根据这个场景的业务,可以有个简单的处理方式。老顾推荐是网上方案先删除token,先保证不会因为重复请求,业务数据出现问题。顶多再让用户处理一次。

出现业务异常,可以让调用方配合处理一下,重新获取新的token,再次由业务调用方发起重试请求就ok了。

token机制缺点

小伙伴们有没有发现,业务请求每次请求,都会有额外的请求(一次获取token请求、判断token是否存在的业务)。其实真实的生产环境中,1万请求也许只会存在10个左右的请求会发生重试,为了这10个请求,我们让9990个请求都发生了额外的请求。(当然redis性能很好,耗时不会太明显)

乐观锁机制

关于乐观锁老顾之前也讲过,大家可以去查阅。乐观锁这里解决了计算赋值型的修改场景。我们对之前的SQL语句进行修改。

update user 
set point = point + 20, version = version + 1
where
userId=1
and
version=1

加上了版本号后,就让此计算赋值型业务,具备了幂等性。

乐观锁机制缺点

就是在操作业务前,需要先查询出当前的version版本

唯一主键机制

这个机制是利用了数据库的主键唯一约束的特性,解决了在insert场景时幂等问题。但主键的要求不是自增的主键,这样就需要业务生成全局唯一的主键,之前老顾的文章也介绍过分布式唯一主键ID的生成,可自行查阅。如果是分库分表场景下,路由规则要保证相同请求下,落地在同一个数据库和同一表中,要不然数据库主键约束就不起效果了,因为是不同的数据库和表主键不相关。因为对主键有一定的要求,这个方案就跟业务有点耦合了,无法用自增主键了。

去重表机制

这个方案业务中要有唯一主键,这个去重表中只要一个字段就行,设置唯一主键约束,当然根据业务自行添加其他字段。主要流程上图

微服务架构之幂等性问题及设计思想,你不得不知的一些幂等方案

上面的主要流程就是 把唯一主键插入去重表,再进行业务操作,且他们在同一个事务中。这个保证了重复请求时,因为去重表有唯一约束,导致请求失败,避免了幂等问题。

这里要注意的是,去重表和业务表应该在同一库中,这样就保证了在同一个事务,即使业务操作失败了,也会把去重表的数据回滚。这个很好的保证了数据一致性。

这个方案也是比较常用的,去重表是跟业务无关的,很多业务可以共用同一个去重表,只要规划好唯一主键就行了。

总结

上面介绍了一些幂等方案,小伙伴们根据自身的业务进行选择,尽量不要让系统变的复杂,所以推荐唯一主键和乐观锁方式,因为实现比较简单。好了,今天就介绍到这里,谢谢大家!!!

问题 :是不是所有的增改接口都需要考虑幂等问题?

答案并不是,这需要根据业务情况去定的,对于一些非重点的功能,并发度也并不高,所以就没有必要去考虑,多一条少一条并不影响;例如一般的管理系统,用户角色这些,一般很少会去考虑,但如果出现重复数据会对系统模块造成业务问题的话,就需要去做幂等措施,或者对接口进行健壮性处理。

但是对于订单提交、支付、发放优惠券这种就需要考虑,因为出现重复的数据肯定会造成损失。

所以需不需要考虑幂等性这个问题,要先判断解决了幂等性可以带来多大的价值,因为去设计这个也是需要时间跟资源的,价值不高的话,可以考虑将技术延后,等到产生问题时再去做幂等问题。

posted @ 2022-11-07 16:29  paopaoa  阅读(75)  评论(1编辑  收藏  举报