什么是幂等?
在编程中,一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。即不用担心重复执行幂等方法不会影响系统状态。比如setTrue()方法就具有幂等性。
在高并发、分布式系统中,对幂等的控制非常重要。
严格幂等:同一笔交易,无论请求方发送多少次请求,服务方只处理一次,且后续的返回信息都和第一次返回的信息相同。
弱化幂等:同一笔交易,无论请求方发送多少次请求,服务方只处理第一次,第二次之后的请求全部拒绝。这样也可以保证只处理一次,但不保证每次返回的结果相同。
幂等的实现
数据库实现
在IT系统中,最常见的幂等实现方式,是利用交易流水在数据库表里面设置的唯一约束来实现,这种方法实现成本低,效果好。
优点:实现简单,可以在很长的时间范围内实现幂等控制
缺点:占用存储空间,占用一定的数据库资源
缓存实现
利用分布式缓存的原子事务操作来实现幂等
优点:内存实现,速度快,不占用存储空间
缺点:容量有限,不能在一个很长的时间范围内保持幂等。缓存一旦失效,幂等也随之失效
幂等实现案例
比如有一张数据表:
Create table business (
Id varchar2(128) primary key, //主键
Status varchar2(16) not null default ‘U’, //状态,默认为U
Amount number(13,2) not null, //账户金额
Order_no varchar(32) not null //业务主流水
);
错误方法:
很容易想到,如果并发情况下多个线程同时处理这块代码,会出现问题。
//1 先查询流水号123的数据存不存在
Select * from business where order_no = ‘123’;
//2 不存在,插入数据
Insert into business(id, status, order_no) values(seq_business.nextval, ‘P’,’123’);
//3 处理业务逻辑
...
错误改进:
使用Synchronized关键字。但这种方案也是错误的。因为在分布式系统中,同样的应用会部署若干台机器,synchronized同步块只能同步同一个jvm中的不同线程。对于不同的jvm中的线程,完全没有控制力。
Synchronized(lock) {
//1 先查询流水号123的数据存不存在
Select * from business where order_no = ‘123’;
//2 不存在,插入数据
Insert into business(id, status, order_no) values(seq_business.nextval, ‘P’,’123’);
//3 处理业务逻辑
...
}
正确方法:
在流水号上增加唯一性约束:
Alter table business add constraint business unique(order_no);
这时候,再执行上面的代码,就不会存在问题了。因为多个线程去执行插入操作,只会有一个线程成功,其他线程都会失败。即使在分布式情况下也能保证。
正确改进:
可以想到第一步select查询操作是多余的,因为数据库的唯一性约束会帮我们保证只有一个线程插入成功,所以可以去掉第一条语句。
实现幂等的方式有很多种,应用的场景也有很多,其要点在于利用共享资源的锁机制。利用锁的机制来达到执行权竞争的目的,即实现了幂等。