1、业界有哪些主流的分布式锁实现方案?
目前主流的有三种,如下:
- 基于数据库实现
基于数据库来做分布式锁的话,通常是采用数据库的乐观锁或悲观锁来实现。
- 基于ZooKeeper实现
基于ZooKeeper,是采用它的临时有序节点来实现的分布式锁。
- 基于Redis实现
基于Redis实现的锁机制,主要是依赖redis自身的原子来实现
以上三种方式都可以实现分布式锁,如果并发量不大的话,直接采用数据库就可以,如果高并发的话,就要考虑zookeeper或redis,但从高并发高性能角度考虑,基于 Redis 实现性能会更好;所以如何选择,还是取决于业务需求。
2、下面介绍基于redis的普通分布式锁
SET key value [NX] [XX] [EX <seconds>] [PX [millseconds]]
以上set 代替了 setnx +expire 需要分2次执行命令操作的方式,保证了原子性
127.0.0.1:6379> set lock true NX px 600000
OK
127.0.0.1:6379> set lock true NX px 600000
(nil)
如果setnx 返回ok 说明拿到了锁
如果setnx 返回 nil,说明拿锁失败,被其他线程占用。
3、案例实战:基于redis的分布式锁实现下订单防止重复提交
@PostMapping(value = "/createOrder", produces = APPLICATION_JSON_UTF8_VALUE, consumes = APPLICATION_JSON_UTF8_VALUE)
public String createOrder(@RequestBody OrderDTO obj) {
//步骤1:先转换为唯一MD5
String json=JsonUtil.object2Json(obj);
String md5 = DigestUtils.md5DigestAsHex(json.getBytes()).toUpperCase();
//步骤2:把md5设置为分布式锁的key
/**
* setIfAbsent 的作用就相当于 SET key value [NX] [XX] [EX <seconds>] [PX [millseconds]]
* 设置过期
*/
Boolean bo=stringRedisTemplate.opsForValue().setIfAbsent(md5,"1",60, TimeUnit.SECONDS);
if(bo){
// 加锁成功
log.debug("{}拿锁成功,开始处理业务",md5);
try {
//模拟10秒 业务处理
Thread.sleep(1000*10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//不要删除,万一业务处理时间很短,页面上点了两次,点第二次的时候,第一次已经处理完了,这样就同一张单子处理了两次,还是等他过期比较好
//stringRedisTemplate.delete(md5);
log.debug("{}拿锁成功,结束处理业务",md5);
return "ok";
}else{
log.debug("{}拿锁失败",md5);
//拿不锁,直接退出
return "请不要重复点击!";
}
}