业务工具杂谈(一)之Redis分布式锁

204 阅读3分钟

这是我参与8月更文挑战的第13天,活动详情查看:8月更文挑战

前言

锁的机制,大家或多或少都接触过,在并发编程中,我们通过锁,来避免由于竞争而造成的数据不一致问题(类似于synchronized等同步机制);数据库也有乐观锁、悲观锁等,但是有一部分是jvm里面的锁,在分布式集群环境下的话,有部分锁就不能胜任了。本篇简单介绍一下平时常用的Redis分布式锁(本人愚见)

Redis分布式锁

首先介绍两个方法

  • setIfAbsent()方法
    这个方法其实就是 get() + set()的结合体。
    如果为空就set值,并返回1;
    如果存在(不为空)不进行操作,并返回0
    对标Redis里面的方法就是setnx()

  • getAndSet()方法
    该方法属并发方法,作用:如果key不存在则返回空并插入value;反之存在key则覆盖value并返回旧value。
    这个方法对标Redis里面的方法就是Getset()

实现

对于这个方法,我一般是作为一个工具类去实现,直接调用上锁、解锁方法即可

逻辑思路

  • key为业务上的标识符,value一般用当前时间+设定的超时时间(例如5分钟什么的的)
  • 加锁:
    • 判断是否存在该key,不存在即直接加锁并返回;
    • 存在则获取已存在的key值得value,判断非空+是否超时(当前时间>设定的超时时间(value=当前时间+超时时间))
    • 未超时则锁未失效,退出方法;若超时则调用getAndSet()方法,覆盖value并获取旧值,并与刚才获取的value比对,若一致则说明上一把锁失效了,覆盖并上新锁(防止死锁)。
@Component
@Slf4j
public class RedisLockUtil {

    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * 加锁
     * @param key
     * @param value 当前时间+超时时间
     * @return
     */
    public boolean lock(String key, String value) {
        if(redisTemplate.opsForValue().setIfAbsent(key, value)) {
            return true;
        }
        //currentValue=A   这两个线程的value都是B  其中一个线程拿到锁
        String currentValue = redisTemplate.opsForValue().get(key);
        //如果锁过期
        if (!StringUtils.isEmpty(currentValue)
                && Long.parseLong(currentValue) < System.currentTimeMillis()) {
            //获取上一个锁的时间
            String oldValue = redisTemplate.opsForValue().getAndSet(key, value);
            if (!StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)) {
                return true;
            }
        }

        return false;
    }

    /**
     * 解锁
     * @param key
     * @param value
     */
    public void unlock(String key, String value) {
        try {
            String currentValue = redisTemplate.opsForValue().get(key);
            if (!StringUtils.isEmpty(currentValue) && currentValue.equals(value)) {
                redisTemplate.opsForValue().getOperations().delete(key);
            }
        }catch (Exception e) {
            log.error("【redis分布式锁】解锁异常, {}", e);
        }
    }

}

总结

简单实现了Redis的分布式锁。为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:

  1. 互斥性。在任意时刻,只有一个客户端能持有锁。
  2. 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
  3. 具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。
  4. 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。