Redis实现分布式锁用图说话

2022-08-01,,

上一篇博客简单了介绍redisTemplate文章地址的基本使用,本篇博客介绍如何使用redis实现分布式锁。

目录

        • 一、进程级别单机锁
        • 二、分布式锁
        • 三、如何使用redis实现分布式锁
          • 3.1 关于redis
          • 3.2加锁
          • 3.3 解锁
        • 四、分布式锁框架 redission

一、进程级别单机锁

在传统的单机应用中,java内部提供的锁十分强大能够很好的解决并发问题,如synchronized,ReentrantLock,及juc包下的类。synchronized是进程级别的锁,也就是说只能在一个jvm进程中有效的控制多线程的并发问题。如下图:

二、分布式锁

随着现在架构的升级,单点已经不能满足我们的需要。我们的web应用通常是部署多个,然后通过nginx负载均衡。示意图如下:

在这种多点部署点情况下,如果采用单应用的锁如下图:

就无法保证数据的一致性了。当减库存的时候虽然每个jvm都加上锁的控制,但是数据还是会出现不一致。如当减库存时候,还是剩最后一件商品。有三个用户在同一个时刻发起请求。正好nginx把每一个请求单独发送到一个web应用上,然后他们都发现还有一件商品,然后能够进行购买,进行库存减1的操作。那么就会出现超卖的现象。此时就需要一个每个web应用都能访问的地方来请求锁。有一个全局的公共区域来管理着锁,这是强大的redis就该上场了
当我们首次请求的时候都向存放一个k-v,然后还有请求过来我们会再次尝试加k-v,如果已经存在,那么就相当于加锁失败。当执行完成后会释放锁,也就把redis里的这个 k-v删除。如下图:

注:加锁 = 存一个k-v;释放锁 = 删除k-v

三、如何使用redis实现分布式锁

3.1 关于redis

redis适合做这个分布式锁有一个很大的原因,他是天然单线程的。当有指令过去时会一排着一个阻塞执行。

了解了整体的实现原理,那么一把简单的分布式锁就容易实现了,下面使用RedisTemplate来对redis进行操作。代码如下:

3.2加锁
  /**
     * 加锁, 加锁成功返回true, 失败返回false
     *
     * @param redisTemplate
     * @param key
     * @return
     * @Author liuzihao
     */
    public static boolean tryLock(RedisTemplate redisTemplate, String key) {
        boolean result = false;
        String status = (String) redisTemplate.execute((RedisCallback<String>) redisConnection -> {
            Jedis jedis = (Jedis) redisConnection.getNativeConnection();
            // 相当于setnx方法,不存在才设置, 
            // key为传入的key
            String res = jedis.set(key, "1", "nx");
            return res;
        });
        if ("OK".equals(status)) {//抢到锁
            result = true;
        }
        return result;
    }
3.3 解锁

在解锁过程中我们需要确认是否有存在这把锁,然后存在就把它操作。
但是这其实是两个步骤:1⃣️判断是否存在 2⃣️:存在就删除
(是不是会有疑问,直接删除就可以了,为什么还要判断是否存在,因为有时候我们会需要根据释放锁的结果进行业务操作)。我们需要步骤1⃣️和步骤2⃣️ 两个合二为一 以原子的方式执行,这个时候就可以使用lua脚本来保证执行的原子性。可以将我们将两个步骤以一条指令发送给redis进行处理。

 /**
     * 释放锁, 通过lua脚本保证原子性
     *
     * @param redisTemplate
     * @param key
     * @return
     * liuzihao
     */
    public static void unLock(RedisTemplate redisTemplate, String key) {
    	// 这里其实就是构造一个不可变的list 使用了 谷歌的gava
        ImmutableList<String> keys = ImmutableList.of(StringUtils.join(Collections.singleton(key), ""));
        // lua脚本
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        redisTemplate.execute(new DefaultRedisScript<>(script, String.class), keys, 1);
    }

上面这种方式只是加锁解锁,并为考虑分布式redis主机宕机 ,然后重新选举后,导致锁不存在或失效的问题;或者单机redis模式下 突然宕机的异常情况。

四、分布式锁框架 redission

基于Redis的Java内存数据网格,最艺术的Redis Java客户端。能很好的解决分布式锁的问题

使用它实现分布式加锁解锁很简单,只要进行简单的配置后。调用对应的api即可。此处就不过多介绍

本文地址:https://blog.csdn.net/weixin_43732955/article/details/107513733

《Redis实现分布式锁用图说话.doc》

下载本文的Word格式文档,以方便收藏与打印。