redis分布式锁演变过程
最近研究了下redis分布式锁,分享下演变过程,怎么一步步实现抗高并发的分布式锁**首先看下单线程的代码,未加分布式锁的情况如下:**@Overridepublic String secKill() {String goodsId = "666666";String goodsCountStr = redisTemplate.opsForValue().get(goodsId);int goods
最近研究了下redis分布式锁,分享下演变过程,怎么一步步实现抗高并发的分布式锁
**
首先看下单线程的代码,未加分布式锁的情况如下:
**
@Override
public String secKill() {
String goodsId = "666666";
String goodsCountStr = redisTemplate.opsForValue().get(goodsId);
int goodsCount = Integer.parseInt(goodsCountStr) - 1;
if(goodsCount >= 0){
redisTemplate.opsForValue().set(goodsId, goodsCount + "");
System.out.println("秒杀成功,剩余" + goodsCount + "件");
}else{
System.out.println("秒杀失败,已被抢光");
}
return goodsCount >= 0 ? "恭喜你,抢到商品" : "抱歉,您没抢到";
}
这段代码,单线程情况下,无任何问题,一旦涉及到多线程,就涉及到先后顺序,得先查询redis获取库存,然后减库存,将剩余库存存入redis,这段操作需要原子执行才行
毫无疑问synchronized 和 ReentrantLock在集群环境下是不可行的,这里不做赘述
演变一:使用setnx执行抢锁过程
代码变成了这样
String goodsId = "666666";
String lockKey = "lockKey";
Boolean lock = redisTemplate.opsForValue().setIfAbsent(lockKey, "xxx");
if(lock){
String goodsCountStr = redisTemplate.opsForValue().get(goodsId);
int goodsCount = Integer.parseInt(Objects.requireNonNull(goodsCountStr)) - 1;
if(goodsCount >= 0){
redisTemplate.opsForValue().set(goodsId, goodsCount + "");
System.out.println("秒杀成功,剩余" + goodsCount + "件");
}else{
System.out.println("秒杀失败,已被抢光");
}
redisTemplate.delete(lockKey);
return goodsCount >= 0 ? "恭喜你,抢到商品" : "抱歉,您没抢到";
}else{
return secKill();
}
增加了获取锁和删除锁的过程,如果获取到了锁,执行秒杀操作,获取不到锁,继续执行获取锁的操作;
但是这样写出现了个问题,如果来不及执行删除锁的代码,抛异常或者服务宕机了,那么其他线程永远获取不到锁,变成了死锁了,于是下面开始改进:
演变二
将setnx加上过期时间
Boolean lock = redisTemplate.opsForValue().setIfAbsent(lockKey, “xxx”,30, TimeUnit.SECONDS);
这样就算服务宕机了,也能释放锁,不至于造成死锁。
但是又引发出来了新的问题,有2个线程,线程一需要10S执行完成,线程二需要35秒执行完成,线程二30秒代码没执行完,这时候线程一执行完成删除了锁,这时候会有新的线程获取到锁,线程二执行完成后,又删除了别人的锁,于是开始下面的演变
演变三:
@Override
public String secKill() {
String goodsId = "555";
String lockKey = "lockKey";
String value = UUID.randomUUID().toString();
Boolean lock = redisTemplate.opsForValue().setIfAbsent(lockKey, value,30, TimeUnit.SECONDS);
if(lock){
try{
String goodsCountStr = redisTemplate.opsForValue().get(goodsId);
int goodsCount = Integer.parseInt(Objects.requireNonNull(goodsCountStr)) - 1;
if(goodsCount >= 0){
redisTemplate.opsForValue().set(goodsId, goodsCount + "");
System.out.println("秒杀成功,剩余" + goodsCount + "件");
}else{
System.out.println("秒杀失败,已被抢光");
}
return goodsCount >= 0 ? "恭喜你,抢到商品" : "抱歉,您没抢到";
}finally {
String myLockKey = redisTemplate.opsForValue().get(lockKey);
if (value.equals(myLockKey)){
redisTemplate.delete(lockKey);
}
}
}else{
return secKill();
}
这样还有个问题,执行时间还是个问题,代码执行时间不一致,无法保证所有请求在30秒或者指定时间内执行完成,还是存在删除别人锁的情况,目的就是在代码没有执行完成时不要删除锁,代码执行完成才删除锁,改进方法就是锁续期,专门开启一个线程,每隔10秒,(1/3的时间)检查锁是否还存在,如果存在,就将时间续期为30秒
演变四
Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {
RFuture<Boolean> future = renewExpirationAsync(threadId);
future.addListener(new FutureListener<Boolean>() {
@Override
public void operationComplete(Future<Boolean> future) throws Exception {
expirationRenewalMap.remove(getEntryName());
if (!future.isSuccess()) {
log.error("Can't update lock " + getName() + " expiration", future.cause());
return;
}
if (future.getNow()) {
// reschedule itself
scheduleExpirationRenewal(threadId);
}
}
});
}
}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
if (expirationRenewalMap.putIfAbsent(getEntryName(), new ExpirationEntry(threadId, task)) != null) {
task.cancel();
}
先查询是否是自己的锁,然后再删除锁也要是原子执行
至此,redis分布式锁算比较完整的了,redis官方给我们最好了这一套,可以直接使用redisson
更多推荐
所有评论(0)