Skip to content

一、使用场景

  • 集群情况下的定时任务
  • 抢单
  • 库存扣减
  • 幂等性场景
  • 原逻辑
java
/**
 * 抢购优惠券
 * @throws InterruptedException
 */
public void rushToPurchase() throws InterruptedException {
  // 获取优惠券数量
  Integer num=(Integer)redisTemplate.opsForValue().get("num");
  // 判断是否抢完
  if(null==num || num<=0){
    throw new RuntimeException("优惠券已抢完");
  }
  // 优惠券数量减一,说明抢到了优惠券
  num = snum-1;
  // 重新设罡凳惠券的数最
  redisTemplate.opsForValue().set("num",num);
}
  • 并发产生的后果: 导致库存超卖
  • Synchronized 互斥锁
shell
public void rushToPurchase() throws InterruptedException {
 
 Synchronized(this) {
    // 获取优惠券数量
    Integer num=(Integer)redisTemplate.opsForValue().get("num");
    // 判断是否抢完
    if(null == num || num<=0){
      throw new RuntimeException("优惠券已抢完");
    }
    // 优惠券数量减一,说明抢到了优惠券
    num = snum-1;
    // 重新设罡凳惠券的数最
    redisTemplate.opsForValue().set("num",num);
 }
 
}
  • 流程图如下:

单体项目可以支持,但多台确有问题

二、SETNX

  • Redis实现分布式锁主要利用Redis的 setnx命令
  • setnx: 是SET if not exists(如果不存在,则 SET)的简写

shell
# 添加锁, NX 是互斥, EX设置超时时间
$ SET lock value NX EX 10

shell
# 释放锁
$ DEL key

四、Redisson 红锁

  • 当前案例就存在2个服务,2个线程同时持有相同的锁无法保证一致性问题
  • RedLock(红锁): 不能只在一个redis实例上创建锁,应该是在多个redis实例创建锁(n/2+1),避免在一个redis实例上加锁

例如: 3台Redis, 创建锁(3/2+1)约等于 2个节点(超过Redis 一半)。

缺点

注意: redis 官方不建议使用Redisson 红锁的使用。

  • 实现复杂
  • 性能差
  • 运维繁琐

五、面试问题

回答:

  • 我们在业务中使用【分布式锁使用的场景】是 抢券
  • 我们当使用的redisson实现的分布式锁,底层是setnxlua脚本(主要作用是保证命令原子性)

回答:

  • 在redisson的分布式锁中,提供了一个WatchDog(看门狗),一个线程获取锁成功以后。
  • WatchDog会给持有锁的线程 => 续期(默认是每隔10秒续期一次)

回答:

  • 可以重入多个锁重入需要判断 => 是否是当前线程
  • 在redis中进行存储的时候, 通过的hash结构来存储 => 线程信息(唯一标识)重入的次数.

回答:

  • 不能解决,但是可以使用redisson提供的红锁来解决,但是这样的话,性能就太低了
  • 如果业务中非要保证数据的强一致性,建议采用zookeeper实现的分布式锁