为什么要用分布式锁
分布式锁的目的其实很简单,就是为了保证多台服务器在执行某一段代码时保证只有一台服务器执行。
为了保证分布式锁的可用性,至少要确保锁的实现要同时满足以下几点:
-
互斥性。在任何时刻,保证只有一个客户端持有锁。
-
不能出现死锁。如果在一个客户端持有锁的期间,这个客户端崩溃了,也要保证后续的其他客户端可以上锁。
-
保证上锁和解锁都是同一个客户端。
redis实现分布式锁
加锁:使用set扩展命令,key:锁标识,value:持有当前锁线程标识,NX是if not exists(如果不存在)的简写,PX:超时时间(毫秒)。
解锁:只有当前锁的持有者才可以执行删除操作,通过lua脚本保证了get和del命令执行的原子性操作。
# 加锁命令
set key value NX PX milliseconds
# setnx lock value1
# 解锁命令
if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end
代码实现
// 加锁
public boolean lockByLua(String lockKey, String requestId, int expireTime) {
RedisCache cache = redisFactory.getRedisCacheInstance(name);
String result = cache.set(lockKey,requestId, "NX", "PX", expireTime);
if ("OK".equals(result)) {
return true;
}
return false;
}
// 解锁
public boolean unLockByLua(String lockKey, String requestId) {
Long success = 1L;
RedisCache cache = redisFactory.getRedisCacheInstance(name);
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = cache.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
if (success.equals(result)) {
return true;
}
return false;
}
缺点:
-
未实现可阻塞,可重入性,公平性。
-
未能解决锁超时并发执行,集群容错。
setnx
手动实现,坑很多、代码较为复杂
redisson实现分布式锁
setnx
虽好,但是实现起来毕竟太过麻烦,一不小心就可能陷入并发编程的陷阱中,那么有没有更加简单的实现方式呢?答案就是redisson
。
redisson
提供了一系列较为完善的工具类,其中就包含了分布式锁。用redisson
实现分布式锁的流程极为简单。
引入依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.14.0</version>
</dependency>
创建Redission实例
@Bean
public RedissonClient redisson(){
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
return Redisson.create(config);
}
编写分布式锁代码
Redisson分布式锁的主要原理非常简单,利用了lua脚本的原子性。 在分布式环境下产生并发问题的主要原因是三个操作并不是原子操作:
- 获取库存
- 扣减库存
- 写入库存 那么如果我们把三个操作合并为一个操作,在默认单线程的Redis中运行,是不会产生并发问题的。
@Autowired
private RedissonClient redissonClient;
@RequestMapping("/reduct_stock")
public void reductStock(){
RLock lock = redissonClient.getLock("redisson:stockLock");
try{
//加锁
lock.lock();
String stock = stringRedisTemplate.opsForValue().get("stock");
int stockNum = Integer.parseInt(stock);
if(stockNum > 0){
//设置库存减1
int realStock = stockNum - 1;
stringRedisTemplate.opsForValue().set("stock",realStock + "");
System.out.println("设置库存" + realStock);
}else{
System.out.println("库存不足");
}
}catch(Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
redisson
实现分布式锁,能够保证多实例下线程安全,代码简单可靠