使用Redis实现分布式锁

  Redis能实现分布式锁,是因为Redis的单进程单线程模式,它采用队列将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系。本篇记录一下如何使用Redis的Java客户端Jedis和Redisson实现分布式锁。

使用Jedis实现分布式锁

一、加锁的条件

1、加锁必须有时间控制

防止某个线程出现问题,长时间或永久不释放锁,其他线程无法获得锁

2、加锁的线程和解锁的线程是同一个

A在加锁期间内没有执行完,Redis使锁过期,A会继续往下执行,通常在finally块内会释放锁,但此时B已抢到锁了,那么A释放的就是B的锁。
同理B线程继续释放C线程的锁。。。

二、代码实现

1、代码示例

String script="if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
String key = "mylock";
String requestId = UUID.randomUUID().toString();
String lock = null;
Jedis jedis = jedisPool.getResource();
try{
  while (lock == null) {
    lock = jedis.set(key,requestId,"NX","EX",120);//key在120秒后自动删除,如果执行set时key存在则返回null,否则返回OK
    if("OK".equals(lock)){
      break; //获得锁,跳出循环,执行业务代码
    }
    Thread.sleep(1000);//没有获得锁,休眠1s,继续尝试获得锁
  }
  //业务代码
 ......
}catch (Exception e){ e.printStackTrace(); }finally { jedis.eval(script,Collections.singletonList(key),Collections.singletonList(requestId)); jedis.close(); }

2、代码说明

加锁时使用的jedis.set(key,requestId,"NX","EX",120)是一条语句,满足原子性操作,下面的写法不可取:
jedis.setnx(key,requestId);
jedis.expire(key,120);

解锁时使用了Lua脚本,先通过key获取value,如果value与参数requestId相等就删除key。
之所以使用Lua脚本是因为将两个语句合为一个脚本,满足原子性操作,下面的写法不可取:
String value = jedis.get(key);
if("requestId".equals(value)){
    jedis.del(key);
}

三、存在的问题 

  上面的实现方式在某些业务场景中无法满足,比如业务场景要求我们必须等到执行完方法才能释放锁。这就需要加锁的时间随着方法的执行时间而延长,如果自己实现的话就需要写一个监控线程,监控方法的执行时间,从而延长Redis的加锁时间。其实这些在Redisson中已经实现,推荐使用Redisson实现分布式锁。

使用Redisson实现分布式锁

一、加锁的条件

Redisson已经帮我们实现

二、代码实现

String key = "mylock";
RLock lock = null;
try{
  lock = redissonClient.getLock(key);
  while (true) {
    if(lock.tryLock()){
      break;
    }
    Thread.sleep(1000);
  }
  //业务代码
......
}catch (Exception e){ e.printStackTrace(); }finally { if(null != lock && lock.isHeldByCurrentThread()) { lock.unlock(); } }

三、存在问题

主从复制时可能出现问题,如何解决下一篇再讲

posted @ 2020-10-11 16:58  雷雨客  阅读(228)  评论(0编辑  收藏  举报