分布式锁之Redis实现

在Java中,关于锁我想大家都很熟悉。在并发编程中,我们通过锁,来避免由于竞争而造成的数据不一致问题。通常,我们以synchronized 、Lock来使用它。
但是Java中的锁,只能保证在同一个JVM进程内中执行。如果在分布式集群环境下呢?

一、分布式锁

分布式锁,是一种思想,它的实现方式有很多。比如,我们将沙滩当做分布式锁的组件,那么它看起来应该是这样的:

加锁

在沙滩上踩一脚,留下自己的脚印,就对应了加锁操作。其他进程或者线程,看到沙滩上已经有脚印,证明锁已被别人持有,则等待。

解锁

把脚印从沙滩上抹去,就是解锁的过程。

锁超时

为了避免死锁,我们可以设置一阵风,在单位时间后刮起,将脚印自动抹去。

分布式锁的实现有很多,比如基于数据库、memcached、Redis、系统文件、zookeeper等。它们的核心的理念跟上面的过程大致相同。

二、redis

我们先来看如何通过单节点Redis实现一个简单的分布式锁。

背景:在当应用服务启动的时候,需要进行数据初始化,如果部署至多容器节点的时候,节点在启动的时候会概率触发多次初始化逻辑;所以,需要通过锁来限制初始化操作之后执行一次。

RedisLock工具类

@Component
public class RedisLockUtil {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    private static long DEFALT_TIMEOUT = 1L;

    /**
     * @description 上锁
     * @param  key 锁标识
     * @param  value 线程标识
     * @param  timeout 超时时间,秒
     * @updateTime 2021/9/14
     */
    public boolean lock(String key, String value, long timeout) {
        return stringRedisTemplate.opsForValue().setIfAbsent(key, value, timeout, TimeUnit.SECONDS);
    }

    /**
     * @description 上锁(排队等待)
     * @param  key 锁标识
     * @param  value 线程标识
     * @updateTime 2021/9/14
     */
    public boolean lock(String key, String value) {
        while (true) {
            // 执行set命令
            Boolean absent = stringRedisTemplate.opsForValue().setIfAbsent(key, value, DEFALT_TIMEOUT, TimeUnit.MINUTES);
            // 是否成功获取锁
            if (absent) {
                return true;
            }
        }
    }

    /**
     * @description 解锁
     * @param  key 锁标识
     * @param  value 线程标识
     * @updateTime 2021/9/14
     */
    public boolean unlock(String key, String value) {
        String currentValue = stringRedisTemplate.opsForValue().get(key);
        if(StrUtil.isNotBlank(currentValue) && currentValue.equals(value) ){
            return stringRedisTemplate.opsForValue().getOperations().delete(key);
        }
        return false;
    }
}

代码示例

 @Override
 public void onApplicationEvent(ApplicationStartedEvent event) {
     //上锁
     long time = System.currentTimeMillis();
     boolean result = redisLock.lock(INIT_KEY, String.valueOf(time), TIMEOUT);
     if (log.isInfoEnabled()) {
         log.info("获得锁的结果:" + result + ";获得锁的时间戳:" + String.valueOf(time));
     }
     if(!result){
         throw new IllegalArgumentException("已存在初始化!!!");
     }
     try {
         ...
         初始化逻辑代码
         ...
     } catch (Exception e) {
         log.error("初始化失败!", e);
     } finally {
         //释放锁
         redisLock.unlock(INIT_KEY, String.valueOf(time));
         if (log.isInfoEnabled()) {
             log.info("释放锁的时间戳" + String.valueOf(time));
         }
     }
 }
上一篇:Day1


下一篇:Markdown学习