一、幂等定义
在编程中,一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。
二、幂等分类
1.请求层面的幂等
请求层面需要做幂等原因是请求重试,比如:第一次请求不知道结果(比如超时)或者失败的异常情况下,发起多次请求。
还有一个原因就是重复点击,不管什么原因造成多次请求,都不能因多次请求而出现多次的状态变化,即要保证幂等。
从架构层面,哪一层需要做幂等处理呢?
只有数据访问层需要做幂等,因为其他层都不会修改数据。数据访问层对外提供的接口可以按如下分类:
接口类型 | 示例 | 是否天然幂等 |
---|---|---|
insert | insert into user values(12,zs),其中12是业务生成的主键 | 天然幂等 |
insert into user values(zs),使用自增主键 | 不幂等 | |
select | select * from user | 天然幂等 |
update | set age=11 ,使用绝对值 | 天然幂等 |
set age++ ,使用相对值 | 不幂等 | |
delete | delete where id=1 ,使用绝对值 | 天然幂等 |
delete where id in bottom 10 ,使用相对值 | 不幂等 |
- insert 语句在使用数据库自增主键的时候,不是天然幂等,需要我们做幂等处理,处理的方案就是使用业务主键,禁止使用自增主键。
- select 语句不会修改值,天然幂等,无需处理。
3.update 使用绝对值幂等的,使用相对值在并发情况下会有问题。解决方案有两种:
第一种就是先查询一下当前值,比如为10,update set age++相当于update set age=11,即先查询,把相对值语句改为绝对值语句。
第二种方案,也要查询一下当前值,在where条件加上and age=当前值,即update set age++ where age=10,age实际中可以用版本号,或者时间戳替代,可以看出这种方案其实就是乐观锁的做法。
4.delete 也是在使用相对值的时候会有幂等问题,解决方案同update.
2.业务层面的幂等
有些接口要处理幂等不是由于重试引起的,是从业务角度上不允许处理多次,比如下面的案例
1.同一用户只能同类商品下一笔订单
2.商品超卖
3.MQ消费端去重
比如第3个案例中,假如MQ中有三条订单消息,但是订单ID是一样的,也就是说从业务角度讲,这三条消息是同一个订单。消费端处理这三条消息要做幂等性处理,也就是说当成一条处理。伪代码如下:
if(orderId.equals(getProcessedOrderId()){
return;//不执行业务代码
}else{
//业务代码
}
首先判断该订单ID是否处理过,但是有一个问题,在集群环境中,这三条消息可能被不同服务实例处理,解决方案就是使用分布式锁。比如三台机器分别拿到一条消息,并且同一时间。使用了分布式锁之后,只有第一个拿到锁的服务能执行业务代码,其他两个不会重复处理。
总结一下:业务层面幂等的解决方案是使用分布式锁,但是要注意分布式锁本身不解决幂等问题,解决的是并发问题,解决幂等的是代码中的重复判断。
三、分布式事务中的幂等
在没有ACID的情况下解决数据一致性的常见技巧是使用补偿。这种方法与ACID事务不同,你可以具有不一致的中间状态。在业务处理中通常可以忍受这些暂时的不一致,只要能确保最终清理它们并使系统恢复到一致状态。这称为最终一致性,这是分布式系统中的一个重要概念。
最终的一致性通常会产生更好的性能,更简单的操作和更好的可伸缩性,同时帮助程序员理解更复杂的数据模型。
在实现最终一致性时会出现消息重复发送问题。实际应用情况是,在分布式系统中确切地保证消息传递是不可能的。实现一次准确的消息传递完全基于消息生成者从消息使用者那里接收到确认消息。但是,ACK本身是不可靠的,因为它也将通过网络传播。在处理消息后,由于网络问题或消费者崩溃,很可能会丢失ACK。
任何通过网络进行通信,都可能会出现三种故障情形:
- 该请求尚未到达服务提供商
- 请求已到达服务提供商,但在处理期间出现异常
- 服务提供程序处理了请求,但响应丢失了
那么如何才能实现准确的消息传递?它的答案是幂等性!你必须使你的消费者操作具有幂等性。幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。举个最简单的例子,用户购买商品后支付,支付扣款成功,但是返回结果的时候网络异常,此时钱已经扣了,用户再次点击按钮,此时不应该进行第二次扣款。
也就是说用幂等来保证分布式事务。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。