要介绍分布式锁,首先要提到与分布式锁相对应的是线程锁、进程锁。
线程锁:主要用来给方法、代码块加锁。当某个方法或代码使用锁,在同一时刻仅有一个线程执行该方法或该代码段。线程锁只在同一JVM中有效果,因为线程锁的实现在根本上是依靠线程之间共享内存实现的,比如synchronized是共享对象头,显示锁Lock是共享某个变量(state)。
进程锁:为了控制同一操作系统中多个进程访问某个共享资源,因为进程具有独立性,各个进程无法访问其他进程的资源,因此无法通过synchronized等线程锁实现进程锁。
分布式锁:当多个进程不在同一个系统中,用分布式锁控制多个进程对资源的访问。
- 互斥性,同一时刻,智能有一个客户端持有锁。
- 防止死锁发生,如果持有锁的客户端崩溃没有主动释放锁,也要保证锁可以正常释放及其他客户端可以正常加锁。
- 加锁和释放锁必须是同一个客户端。
- 容错性,只有redis还有节点存活,就可以进行正常的加锁解锁操作。
基本上避免了以上几种错误方式之外,就是正确的方式了。要满足以下几个条件:
命令必须保证互斥
设置的key必须要有过期时间,防止崩溃时锁无法释放
value使用唯一id标志每个客户端,保证只有锁的持有者才可以释放锁
加锁直接使用set命令同时设置唯一id和过期时间;其中解锁稍微复杂些,加锁之后可以返回唯一id,标志此锁是该客户端锁拥有;释放锁时要先判断拥有者是否是自己,然后删除,这个需要redis的lua脚本保证两个命令的原子性执行。
- @Slf4j
- public class RedisDistributedLock {
- private static final String LOCK_SUCCESS = "OK";
- private static final Long RELEASE_SUCCESS = 1L;
- private static final String SET_IF_NOT_EXIST = "NX";
- private static final String SET_WITH_EXPIRE_TIME = "PX";
- // 锁的超时时间
- private static int EXPIRE_TIME = 5 * 1000;
- // 锁等待时间
- private static int WAIT_TIME = 1 * 1000;
- private Jedis jedis;
- private String key;
- public RedisDistributedLock(Jedis jedis, String key) {
- this.jedis = jedis;
- this.key = key;
- }
- // 不断尝试加锁
- public String lock() {
- try {
- // 超过等待时间,加锁失败
- long waitEnd = System.currentTimeMillis() + WAIT_TIME;
- String value = UUID.randomUUID().toString();
- while (System.currentTimeMillis() < waitEnd) {
- String result = jedis.set(key, value, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, EXPIRE_TIME);
- if (LOCK_SUCCESS.equals(result)) {
- return value;
- }
- try {
- Thread.sleep(10);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- }
- } catch (Exception ex) {
- log.error("lock error", ex);
- }
- return null;
- }
- public boolean release(String value) {
- if (value == null) {
- return false;
- }
- // 判断key存在并且删除key必须是一个原子操作
- // 且谁拥有锁,谁释放
- String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
- Object result = new Object();
- try {
- result = jedis.eval(script, Collections.singletonList(key),
- Collections.singletonList(value));
- if (RELEASE_SUCCESS.equals(result)) {
- log.info("release lock success, value:{}", value);
- return true;
- }
- } catch (Exception e) {
- log.error("release lock error", e);
- } finally {
- if (jedis != null) {
- jedis.close();
- }
- }
- log.info("release lock failed, value:{}, result:{}", value, result);
- return false;
- }