交易系统高并发下的幂等性设计原则

一、介绍

幂等性就是针对同一个请求,不管该请求被提交了多少次,该请求都将被视为同一个请求,服务端不应该将同一个请求进行多次处理,以确认处理逻辑的正确性,针对交易性系统幂等性的设计尤为重要,否则由于网络或服务器处理超时等问题,就会造成交易混乱,最严重的后果就是乱扣用户的钱,造成投诉満天飞。

二、客户端设计原则

系统设计时,一定是要从最坏的角度上去考虑,如网络问题、服务器问题,甚至于包括人为的攻击行为,如果纯粹从服务端的角度去做实现保证,一个是系统的复杂度会加大,二个也会对系统产生更大的并发压力,因而客户端的合理设计也非常重要,举一个示例:

用户在网页端或APP端(统称为客户端)上购买一个商品,通常都是以触发按钮的行为提交该请求,如果客户端没有做请求提交控制,那么由于网络原因或者系统繁忙等原因(这是非常常见的场景),没有在用户期望的时间之内响应,那么用户就会再次点击,那么这个请求又会发往服务端,那么服务端又会再次对这个请求进行处理。

针对这种场景,服务端就会接收处理到多个订单请求,从而产生多条不合理的用户订单。此时在客户端稍微优化一下,当用户的请求提交以后,将订单提交的请求按钮置为不可用的状态,待请求成功响应后跳转到下一步处理逻辑,或者是提单请求超时后再将订单提交按钮置为可用,提示用户上一次的提交可能已经失败,并允许用户再次提交。

当然客户端这样设计并不能完全做到幂等性原则,因为用户同样可以针对相同的订单执行多次提交,服务端如果没有做控制,还是会产生多个订单,只是让错误变得优雅了一点。这个时候需增加令牌策略,在下单之间,针对当前订单从服务端获取一个Token,每次提交的时候都从把该Token带上,针对同一个订单带上相同的值,这样服务端就可以根据该Token来判断是不是一个已经处理的了订单。

说了那么多,看文字太累,还是看图方便,来一个:

 三、服务端设计原则

根据不同的应用场景,服务端可以使用不同的解决方式,如:

1、数据库的乐观锁;
2、防重表;
3、token令牌;
4、分布式锁;
5、异步处理支付;

其中数据库的乐观锁和防重表的使用,都是涉及到数据的参与,在高并发的应用场景中,业务的判断逻辑尽量不要使用数据库参与,特别是RDBMS的参与,因为RDBMS天生具有不易扩展及事务处理属性,吞吐量上都会有相应的瓶颈,因而用数据库做业务逻辑的控制不是我的菜,这里就不会重点说这两种方式。

1、Token令牌+分布式锁的方式

Token是用于确定交易的唯一属性,也是服务端用于检验当前交易是否合法交易的依据,但是在分布式的复杂环境中,如果没有分布式锁的控制,同一笔交易就可能会被处理多次,因而为了确认交易的幂等性,Token令牌和分布式锁必须要一起使用。

实现逻辑步骤如下:

1、服务端根据交易前请求生成对应的Token,保存于服务端的Token库中,通常是缓存集群中,并将生成好的Token库下发给客户端;
2、客户端在每次请求的时候,都带上对应的Token;
3、服务端获取该Token对应的锁,如果获取成功,则继续下面的步骤;
4、判断是否该Token是否合法,如果合法则继续下一步;
5、处理真实的业务逻辑;
6、业务处理成功后,从缓存中删除该Token;
7、删除获取的分布式锁;

实现逻辑参见下图:

其中分布式锁的获取,可以通过Redis和Zookeeper实现,请参看笔者的另外两篇分别介绍通过Redis和Zookeeper实现分布式的文章:

集群环境中使用Redis实现分布式锁两种方式

集群环境中使用Zookeeper实现分布式幂等控制

2、异步处理

异步处理,通常的做法是将认为需要消费的交易,提交到消息队列中,并注册监听事件,待交易被处理完后,再由处理交易的应用回调注册的监听事件反馈处理的结果。交易处理的调度应用,需要负责对交易的处理符合幂等性的原则,将重复请求的交易请求做去重处理。

实现逻辑见下图:

从逻辑处理上可以看到,只要交易处理器足够多,处理速度也不一定会受到多少的影响,交易生产者和交易接收者甚至可以同步返回结果,当交易接收者接收处理结果超时后,再提示用户过一会儿查看交易的处理结果。

猜你喜欢

转载自www.cnblogs.com/fenglibing/p/11026287.html