一、介绍
幂等性就是针对同一个请求,不管该请求被提交了多少次,该请求都将被视为同一个请求,服务端不应该将同一个请求进行多次处理,以确认处理逻辑的正确性,针对交易性系统幂等性的设计尤为重要,否则由于网络或服务器处理超时等问题,就会造成交易混乱,最严重的后果就是乱扣用户的钱,造成投诉満天飞。
二、客户端设计原则
系统设计时,一定是要从最坏的角度上去考虑,如网络问题、服务器问题,甚至于包括人为的攻击行为,如果纯粹从服务端的角度去做实现保证,一个是系统的复杂度会加大,二个也会对系统产生更大的并发压力,因而客户端的合理设计也非常重要,举一个示例:
用户在网页端或APP端(统称为客户端)上购买一个商品,通常都是以触发按钮的行为提交该请求,如果客户端没有做请求提交控制,那么由于网络原因或者系统繁忙等原因(这是非常常见的场景),没有在用户期望的时间之内响应,那么用户就会再次点击,那么这个请求又会发往服务端,那么服务端又会再次对这个请求进行处理。 |
针对这种场景,服务端就会接收处理到多个订单请求,从而产生多条不合理的用户订单。此时在客户端稍微优化一下,当用户的请求提交以后,将订单提交的请求按钮置为不可用的状态,待请求成功响应后跳转到下一步处理逻辑,或者是提单请求超时后再将订单提交按钮置为可用,提示用户上一次的提交可能已经失败,并允许用户再次提交。
当然客户端这样设计并不能完全做到幂等性原则,因为用户同样可以针对相同的订单执行多次提交,服务端如果没有做控制,还是会产生多个订单,只是让错误变得优雅了一点。这个时候需增加令牌策略,在下单之间,针对当前订单从服务端获取一个Token,每次提交的时候都从把该Token带上,针对同一个订单带上相同的值,这样服务端就可以根据该Token来判断是不是一个已经处理的了订单。
说了那么多,看文字太累,还是看图方便,来一个:
三、服务端设计原则
根据不同的应用场景,服务端可以使用不同的解决方式,如:
其中数据库的乐观锁和防重表的使用,都是涉及到数据的参与,在高并发的应用场景中,业务的判断逻辑尽量不要使用数据库参与,特别是RDBMS的参与,因为RDBMS天生具有不易扩展及事务处理属性,吞吐量上都会有相应的瓶颈,因而用数据库做业务逻辑的控制不是我的菜,这里就不会重点说这两种方式。
1、Token令牌+分布式锁的方式
Token是用于确定交易的唯一属性,也是服务端用于检验当前交易是否合法交易的依据,但是在分布式的复杂环境中,如果没有分布式锁的控制,同一笔交易就可能会被处理多次,因而为了确认交易的幂等性,Token令牌和分布式锁必须要一起使用。
实现逻辑步骤如下:
实现逻辑参见下图:
其中分布式锁的获取,可以通过Redis和Zookeeper实现,请参看笔者的另外两篇分别介绍通过Redis和Zookeeper实现分布式的文章:
2、异步处理
异步处理,通常的做法是将认为需要消费的交易,提交到消息队列中,并注册监听事件,待交易被处理完后,再由处理交易的应用回调注册的监听事件反馈处理的结果。交易处理的调度应用,需要负责对交易的处理符合幂等性的原则,将重复请求的交易请求做去重处理。
实现逻辑见下图:
从逻辑处理上可以看到,只要交易处理器足够多,处理速度也不一定会受到多少的影响,交易生产者和交易接收者甚至可以同步返回结果,当交易接收者接收处理结果超时后,再提示用户过一会儿查看交易的处理结果。