Redis 分布式锁
本博客使用第三方开源组件Jedis实现Redis客户端,且只考虑Redis服务端单机部署的场景。
什么是分布式锁
为了防止分布式系统中的多个进程之间相互干扰,我们需要一种分布式协调技术来对这些进程进行调度。而这个分布式协调技术的核心就是来实现这个分布式锁。
分布式锁的实现方式
数据库锁(包括数据库唯一约束/乐观锁),Redis 锁,ZK 锁
如何设计一把可靠的分布式锁
- 互斥性。在任意时刻,只有一个客户端能持有锁。
- 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
- 具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。
- 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了
jedis实现
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
如下代码即为redis 加锁代码,使用jedis.set 方法将redis 的setNx 命令和 expire 命令绑定在一个原子操作中,避免在执行setNx 后系统宕机导致死锁。
clientId 意义为线程的标识,避免在解锁的过程中A 线程解了B 线程的锁具体过程在下文会有解释
public class RedisLock {
/**
* redis setNx 命令
*/
private final String REDIS_COMMAND_NX = "NX";
private final String REDIS_COMMAND_EXPIRE = "PX";
/**
* 获取redis 分布式锁
*
* @param [jedis, lockKey, clientId, expireTime]
* @return boolean
* @author longcheng
* @date 2020/10/21 23:13
*/
public boolean tryLock(Jedis jedis, String lockKey, String clientId, int expireTime) {
String result = jedis.set(lockKey, clientId, REDIS_COMMAND_NX, REDIS_COMMAND_EXPIRE, expireTime);
if(result.equals("OK")){
return true;
}else {
return false;
}
}
}
不可靠示例1
如下代码假如执行完整个减库存方法需要15s(在系统压力较大的情况下,正常情况只需要8秒) ,现有三个线程 A,B,C, 当线程A 执行到减库存方法时花费了10秒,此时锁被释放。B 进入加锁,A执行释放锁。此时 是不是就把B 的锁释放掉了? 所以上述加锁代码中的clientId 是否就好理解了
/**
* 减库存方法
*
* @return
*/
public Integer reduceStock(Integer num) {
String goodsId = "001";
Integer row = 0;
try {
//加锁
Long res = jedis.setnx("goodsId", "ok");
jedis.expire("goodsId", 10);
if (res == 0) {
//代表已经被锁住
throw new Exception("已被锁住");
}
//减库存
row = goodsDAO.reduceStock(num);
} finally {
//释放锁
jedis.del(goodsId);
}
return row;
}
正确示例
//注入redislock ,上文的redisLock 工具类
private RedisLock redisLock;
/**
* 减库存方法
*
* @return
*/
public Integer reduceStock(Integer goodsId) {
Integer row = 0;
try {
//加锁 Jedis jedis, String lockKey, String clientId, int expireTime
boolean res = redisLock.tryLock(jedis,"lock",goodsId,10);
if (!res) {
//代表已经被锁住
throw new Exception("已被锁住");
}
//减库存
row = goodsDAO.reduceStock(goodsId);
} finally {
//释放锁
String lockGoodId = jedis.get("lock");
if (lockGoodId = goodsId) {
jedis.del(goodsId);
}
}
return row;
}