因为应用程序服务器有两台,所以如果仅仅在每台机器上面同步时不够的,因为获取到的不是同一把锁。
这里使用redis实现分布式锁
使用redis作为分布式锁要注意几点:
原理:在redis上面定义一个锁的key,每台应用程序服务器的下单请求先要去redis尝试创建一个锁的key,如果锁的key已经存在,那么说明其他线程已经进入,这个线程就阻塞。如果锁的key不存在,那么就说明锁是可以获取的状态,建立这个key从而获取锁。
注意事项一:产生异常情况如何保证锁的释放
答:利用try finally,try里面放下单流程,finally里面删除key,保证异常情况下也能释放锁。
注意事项二:服务器断电之后,锁一直存在怎么办?
答:设置过期时间,注意!redis的2.6之前的版本,设置锁的key、设置过期时间这两步操作不是原子性的!但是在2.6之后,可以用一行代码设置key并设置过期时间保证原子性。下面会介绍。
注意事项三:如何保证线程释放的是自己加上的锁?
答:加锁的key的时候,value设置为一个随机的uuid,等到删除key的时候,先判断缓存中value是不是这个uuid,如果是再释放。
注意事项四:程序执行时间过长,超过了key的过期时间怎么办?
开一个守护线程,先休眠,然后不断去判断当前线程是否还有持有锁,如果持有而且超时时间到,就给他“续命”,加上一段时间,直到线程自己删掉key释放锁。
因为我的redis只有一台,如果redis也部署了主从,那么无法完全保证可靠性。因为分布式锁只会去验证主redis的加锁情况。而不会去验证从redis的key的情况。
但是zookeeper是有全部主从分布式锁的验证的,所以zookeeper比较能保证安全,但是相对于redis的实现方式效率较低。
redis实现的方式可以使用redisson框架直接实现
这里以减库存为例,方式超卖现象
@Transactional
public boolean decreaseStock(Integer itemId, Integer amount) throws BusinessException {
//int affectedRow = itemStockDOMapper.decreaseStock(itemId, amount);
//result是代表执行完扣减库存操作之后的结果(还剩多少)
String lockValue = UUID.randomUUID().toString();
Boolean lockSuccess = redisTemplate.opsForValue().setIfAbsent("lockKey", lockValue, 5, TimeUnit.SECONDS);
if(lockSuccess){
try {
long result = redisTemplate.opsForValue().increment("kill_item_stock"+itemId, amount.intValue()*-1);
if(result >= 0){
//更新库存成功
return true;
}else{
//更新库存失败
return false;
}
}
finally {
if(redisTemplate.opsForValue().get("lockKey").equals(lockValue)){
redisTemplate.delete("lockKey");
}
}
}else return false;
}