Redis 的分布式锁
分布式锁
分布式锁一般有三种实现方式:
- 数据库乐观锁
- redis分布式锁
- zookeeper分布式锁
本文主要研究redis分布式锁的实现过程。
实现要点
- 互斥性,即同一时刻,只能有一个客户端持有锁。
- 防止死锁发生,如果持有锁的客户端因崩溃而没有主动释放锁,也要保证锁可以释放并且其他客户端可以正常加锁。
- 加锁和释放锁必须是同一个客户端。
- 容错性,只要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; } }
source:
Comments:
Email questions, comments, and corrections to hi@smartisan.dev.
Submissions may appear publicly on this website, unless requested otherwise in your email.