新年好啊,各位朋友!今天是年后开工的第一天,不知道你们有和我一样今天敬业的在岗吗?美好的时光总是短暂的,不知不觉史上最长春节假期就这样过去了,真是心痛!
唉,多的不谈,回归今天的正题,聊聊和有关幂等方面的东西。
什么是幂等?
幂等其实是一个数学与计算机学的概念,在抽象代数中比较常见。那么在编程中,对于一个幂等操作,它主要表现为多次执行结果与单次执行结果一致,即同样的参数以及条件,无论执行多少次,最终的结果都是一致的。
当然,幂等也可以分为请求幂等和业务幂等两类:
❝
- 请求幂等:相同参数多次请求,结果应当保持一致。
- 业务幂等:同一个业务流程,在推进到终态之后的每次请求,响应结果都应当是一致的。在此之前,每次请求都需要执行相关的业务逻辑,直到进入终态。
❞
对于开发人员来说,保证幂等性是在日常开发过程中是必须要考虑到的。像一些转账、下单支付这类涉及金钱交易的场景,必须要保证其幂等性,否则后果是相当严重的。
如何保证幂等?
讲了上面这些,那么对于开发人员来说,在日常的开发过程中如何来保证幂等性呢?
- 数据库唯一索引
一般我们在进行落库新增操作时,通常会先去查这个数据记录在不在库里,不存在就落库,存在就不执行。但是对于并发较高的场景时,如果存在多个请求同时去查库,并且都没有查到记录。那么后续的话就会去执行落库新增操作,也就会产生相同的重复记录,这显然不符合我们的需求。通过使用数据库唯一索引的方式,当前一个请求插入成功后,后续请求由于索引的唯一性,再次执行就会报冲突,这也能够保证幂等性。(一定要有!) - 状态机
状态机对于很多开发人员来说可能比较陌生,其作用就是为了业务状态正常的流转。例如用户下了一笔订单,此时订单状态为已下单,若成功支付,则流转至已支付待发货;若超时未支付或者手动取消,则流转至超时关闭或者已取消。每次在执行更新操作时,先拿到当前的状态进行对比,符合条件则继续更新流转。 - 加锁(乐观锁、悲观锁或者分布式锁)
先说悲观锁,依靠数据库层面提供的锁机制,保证操作最大程度的独占性。在整个数据处理的过程中,数据一直处于锁定状态,防止其它请求更改该数据。并且悲观锁需要在同一事务中锁住一行数据,如果事务比较长,会造成大量的请求处于等待状态,影响接口性能。再说乐观锁,其主要是通过数据版本记录来实现的,即增加版本号。每次请求之前将版本号先拿出来,后续执行更新操作时,再对该版本号进行增加。携带的版本号若与数据库中记录的版本一致,则执行更新操作,后续请求再执行的话,版本号不一致则不会再去更新数据记录。
再来说说分布式锁,常见于使用Redis或者Zookeeper实现分布式锁,每次请求通过业务唯一ID尝试获取分布式锁,拿到锁了之后,执行后续业务逻辑。如果此次没有拿到锁,丢弃当前请求直接返回。 - token令牌
每次请求操作都会去生成一个唯一的token令牌,服务器端根据该令牌确保相同操作不会被执行多次,具体表现为: 1)每次请求先拿到一个token令牌,下次请求时在请求头带上这个令牌,服务端根据令牌验证判断是否能进行业务操作; 2)验证通过后,删除该令牌,下次请求再次判断token令牌。
要想解决幂等性问题,先要考虑加锁,通过对幂等号(业务唯一ID)进行加锁,之后进行幂等判断,符合条件后再执行业务更新操作。加锁的选择的话,建议选择Redis实现的分布式锁,非阻塞且高效的互斥锁,非常适合幂等控制场景。然后根据操作流水、Token令牌或者状态机判断是否符合幂等条件,最好有操作流水并且能够将幂等号作为唯一性约束。简单代码实例如下:
ini
# 加锁
@RequestLock(keyExpression = "#request.traceId", expire = 2000)
public AjaxResult submit(RequestParams request) {
ObjectResult objectResult = requestService.queryByParams(request);
# 幂等判断
if (objectResult == null) {
# 更新
return requestService.submit(request);
}
return AjaxResult.failure(0, "重复请求");
}
以上即是解决目前在日常开发过程中解决幂等问题的一种方案,大家在使用的过程中根据实际的业务需求,有问题及时发现并解决,选择合适的幂等判断条件进行方案实施,归根结底适合自己的才是最优的。
喜欢本文或者能够给你带来帮助的朋友可以一键三连,谢谢!下篇文章再会~