背景
在开发中很容易忽略接口防重的场景,比如交易中支付防重问题、营销系统中C端领取优惠券接口防重等等处理不当很容易引起资损。
方案
主流方式有三种策略:唯一索引机制、分布式锁和缓存计数器。
策略一:唯一索引机制
新建一个表用于存储锁的记录,
CREATE TABLE `Lock` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`type` int(11) NOT NULL COMMENT '锁类型',
`businessId` int(11) NOT NULL DEFAULT '0' COMMENT '业务ID',
`status` int(11) NOT NULL DEFAULT '0' COMMENT '锁状态',
PRIMARY KEY (`id`),
UNIQUE KEY (`businessId`, `type`)
);
整体处理流程如下:
- 线程一插入一条Lock记录数据,成功,相当于获取锁
- 线程二尝试插入一条Lock记录数据,由于唯一索引的限制,失败
- 线程一处理完业务,删除此条记录,相当于释放锁
该种方式中若业务代码中包含唯一索引的记录插入操作,也可以直接采用插入记录的唯一索引
策略二: 分布式锁
分布式锁策略采用的是同一个业务ID的不同线程对锁的争用情况来达到防重的目的,示例代码如下:
public void test() {
if(!redis.setnx(key, value, time)){
// 尝试获取锁,获取失败直接返回
return;
}
try {
//处理业务中.....
} finally {
// 处理完业务,释放锁
redis.del(key);
}
}
- 线程一设置key成功,相当于获取锁,开始处理业务
- 线程二来请求,key已经设置过了,设置失败,返回
- 线程一处理完业务,释放锁
策略三:缓存计数器
缓存计数器采用的策略类似于分布式锁的实现,缓存计数器把同一个业务ID每次请求的次数放在缓存(如redis)中,示例代码如下:
public void test() {
if(redis.incr(key) > 1){
// 尝试获取锁,获取失败直接返回
return;
}
try {
//处理业务中.....
} finally {
// 处理完业务,释放锁
redis.del(key);
}
}
- 线程一对业务ID进行加1后发现等于1,处理业务。
- 线程二对业务ID进行加1后发现大于1,说明有操作在进行返回。
- 线程一处理完业务,删除key,即释放锁
其他方案欢迎讨论!