springboot2 redis 及 redission 分布式锁(分布式事务)

参考:https://www.fengyunxiao.cn/

springboot 在单机模式下很好解决抢票,秒杀等需要一个一个执行的业务,可以使用jvm自带的synchronized进行上锁,避免抢了同一个。分布式场景下(比如在多个服务器同时部署了多个项目)使用synchronized关键字不能保证业务不出问题,需要使用中心化的解决方法,如redis。

方法1:单纯使用 redis 作为分布式锁,比较简单,但是存在几个很难完美解决的问题。

// 获取原子锁(setnx),同时设置超时时间,该方法是原子性的。
public boolean getLock(String key, String value) {
    Boolean result = stringRedisTemplate.boundValueOps(key).setIfAbsent(value, 30, TimeUnit.SECONDS);
    return result == null ? false : result;
}

// 删除原子锁,根据key和value同时删除,非原子性。最好使用lua,保证原子性,因为我没有使用过lua,所以先用非原子性的。后期更新。
public void deleteLock(String key, String value) {
    String s = stringRedisTemplate.boundValueOps(key).get();
    if (value!= null && value.equals(s)) {
        stringRedisTemplate.delete(key);
    }
}

单纯使用 redis 作为分布式锁需要注意以下事项,现在存在 3 和 5 两个问题很难解决:

  1. 设置锁的超时过期时间:防止中途出现错误导致代码中断,没有执行释放锁,其他线程再也不会抢到锁。
  2. try…finally { 释放锁 }:防止中途出现异常,没有执行到释放锁。
  3. 若代码执行时间比锁的超时时间长,会造成严重问题:A线程未执行完,但是超时锁解了,B线程进入了同时上锁,此时A执行完,释放了B线程的锁,导致C线程又进入。(临时解决方案:锁的时间尽可能长,同时用4的方案)
  4. 比较key的value相同再解锁,如果线程1超时解锁了,线程2获得新的锁进入了,此时1线程执行完因为线程1的锁的value和线程2锁的value不相同,所以不能解开线程2的锁,所以线程3无法进入。
  5. A和B同时访问,B防止抢不到锁就结束(体验不好),最好B线程能持续尝试几秒,超时再结束。如果使用 while 循环持续抢锁,可能性能消耗很大。如果使用 while 循环抢锁 n 次后结束,设计代码又很复杂冗长。
  6. 主从redids中,主节点挂掉后进行主从切换后,从节点没法获取已经上锁的进程的锁id进行线程的重新上锁。(只能使用单机redis解决)

方法2:使用 redisson 作为分布式锁

  1. 首先导入依赖

    org.redisson
    redisson-spring-boot-starter
    3.12.0

  1. 代码
// 导入依赖
@Autowired private RedissonClient redissonClient;

// 获取锁
RLock lock = redissonClient.getLock("ddddd");
// 上锁
lock.lock();
// 解锁
lock.unlock();

(解决 redis 的3的问题):redisson有看门狗机制,自动续期当前锁的超时时间(默认20秒检查一次,锁的超时时间默认30秒),保证锁的时间>代码执行时间。如果手动设置了超时时间<30秒,同时也要修改看门狗的检查时间间隔。redissonClient.getConfig().setLockWatchdogTimeout();
(解决 redis 的5的问题):使用 lock() 方法,如果没有抢到锁会持续进行抢锁,直到抢到再执行操作代码。避免了n个线程同时访问,n-1个线程没有抢到锁,就直接返回给用户无效的信息,体验很差。也可以使用 lock.tryLock(5, TimeUnit.SECONDS),只在 5 秒内尝试抢锁,如果 5 秒内抢不到锁,返回 false,继续执行代码。


// 获取锁
RLock lock = redissonClient.getLock("ddddd");
// 尝试上锁,5秒内出结果
boolean islock = lock.tryLock(5, TimeUnit.SECONDS); 
if ( islock ) {
	// 抢到锁的代码
	// 获取到锁,需要解锁
    lock.unlock();
} else {
    // 没有抢到锁的代码,不需要解锁
}

你可能感兴趣的:(springboot2)