微服务架构下的系统不可以使用 synchronized 加锁,可以使用 redis 的 setnx 指令来加锁。
Redis 加锁简单示例
@RestController
public class IndexTestController {
@Autowired
private StringRedisTemplate stringRedisTemplate;
//redis普通加锁
@RequestMapping("/test_lock")
public String testLock(){
String lock_key = "lock:test";
//唯一id,不能用线程id,因为分布式有多台服务器,不同服务器的线程id可能相同
String clientId = UUID.randomUUID().toString();
//实现上锁和设置超时时间的原子性
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lock_key, clientId, 10, TimeUnit.SECONDS);
if(!result){
System.out.println("error_code");
return "error_code";
}
try{
//扣减库存
int count = Integer.parseInt(stringRedisTemplate.opsForValue().get("count"));
count = count - 1;
if(count > 0){
stringRedisTemplate.opsForValue().set("count", count + "");
System.out.println("库存剩余:" + count + "个");
}else{
System.out.println("库存剩余不足");
}
}finally {
//clientId 判断用来防止被其他线程误删锁
if(clientId.equals(stringRedisTemplate.opsForValue().get(lock_key))){
//注意:clientId 的相等判断和删除锁的操作仍不满足原子性
stringRedisTemplate.delete(lock_key);
}
}
return "end";
}
}
setnx 加锁可能产生的问题
如果finally 不进行 clientId.equals() 判断,直接删除 lock_key,可能删除了另外一个线程的锁。
如果 finally 进行 clientId.equals() 判断仍仍然可能删除了另外一个线程的锁
因为 clientId 的相等判断和删除锁的操作不满足原子性。
- 线程 1 执行了 clientId.equals() 判断结果为 true;
- 这时线程 1 还未来得及执行 delete 操作,线程 2 获得了锁;
- 线程 1 重新获得资源,执行 delete 操作,这时 delete 的是线程 2 的锁。
Redisson 分布式锁
利用 Redisson 分布式锁可以避免上面的问题,Redisson 使用了 Lua 脚本来保证 redis 语句执行的原子性及支持事务,同时可以减少网络开销。
@RestController
public class IndexTestController {
@Autowired
private StringRedisTemplate stringRedisTemplate;
//redisson
@RequestMapping("/test_lock")
public String testLock(){
String lockKey = "lock:product_101";
//获取锁对象
RLock redissonLock = redisson.getLock(lockKey);
//加分布式锁
redissonLock.lock();
try {
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
if (stock > 0) {
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock", realStock + ""); /
System.out.println("扣减成功,剩余库存:" + realStock);
} else {
System.out.println("扣减失败,库存不足");
}
} finally {
//解锁
redissonLock.unlock();
}
return "end";
}
}
Redisson 分布式锁原理
Redisson 还支持锁续命功能,后台线程发现线程还持有锁,会重置这个锁的过期时间,这种方式避免了锁过期了但是线程还没有结束的情况。
Redisson Redlock 分布式锁
Redis 主从架构锁失效问题
- 线程 1 加锁成功,主节点保存了加锁信息,但是还没有同步到从节点。
- 这时主节点宕机,从节点重新被选举为新的主节点,新的主节点中没有线程 1 加锁的记录。
- 线程 2 申请加锁成功,线程 1 加的锁失效。
解决办法
1、使用 Zookeeper,但是性能不如 Redis。
2、Redisson Redlock 可以解决这个问题,但仍存在一些问题。
Redis Redlock 分布式锁原理与存在的问题分析
Redis Redlock 分布式锁原理
存在的问题
1、每个 redis 节点关联一个从节点,仍然可能发生主从架构锁失效的问题。
2、每个 redis 节点不关联从节点,没有主从架构,这时如果超过半数 redis 节点宕机会导致 redis 加锁失败;为了解决这个问题增加 redis 节点数量,每次加锁需要更多的节点返回加锁成功信息,影响性能。