Loading [MathJax]/jax/input/TeX/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Redis 分布式锁(14)

Redis 分布式锁(14)

作者头像
兜兜毛毛
发布于 2021-05-18 02:56:10
发布于 2021-05-18 02:56:10
53400
代码可运行
举报
文章被收录于专栏:兜兜毛毛兜兜毛毛
运行总次数:0
代码可运行

什么是分布式锁

在分布式系统中,有些业务场景会用到分布式锁,实现分布式锁的方式有很多,本篇主要讲根据Redis如何来实现。

首先我们要知道分布式锁的一些基本特点:

  1. 互斥性:只有一个客户端可以持有锁
  2. 不会产生死锁:即使持有锁的客户端崩溃,也能保证后续其他客户端可以获得锁
  3. 只有持有这把锁的客户端才能解锁

下边我们通过几个例子来说明分布式锁为什么需要以上3个特点。

不加客户端校验解锁
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 使用jedis客户端实现分布式锁
 * @Author: maomao
 * @Date: 2021-04-27 08:42
 */
public class DistLock {

    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "PX";
    private static final Long RELEASE_SUCCESS = 1L;

    /**
     * 尝试获取分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param value 值
     * @param expireTime 超期时间
     * @return 是否获取成功
     */
    public static boolean tryGetDistributedLock(Jedis jedis, String value, String requestId, int expireTime) {
        // set支持多个参数 NX(not exist) XX(exist) EX(seconds) PX(million seconds)
        String result = jedis.set(lockKey, value, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
        if (LOCK_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }

    /**
     * 直接删除解锁,未判断客户端ID,会导致其他客户端把锁释放
     * @param jedis
     * @param lockKey
     */
    public static void releaseLock1(Jedis jedis, String lockKey) {
        jedis.del(lockKey);
    }
}

上边代码是一个不验证客户端的例子,加锁是没有问题的,但在解锁时会有很大的问题。

通过上图可以看到,因为没有校验客户端逻辑,Thread B可以直接解锁,而Thread A程序还未执行完,但已被解锁,造成锁失效。如果此时有其他客户端加锁是可以加锁成功的。

那我们可以在代码中增加一个客户端校验不就可以了?

加客户端校验解锁
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
   /**
     * 尝试获取分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param requestId 请求标识-修改此处为客户端唯一标致
     * @param expireTime 超期时间
     * @return 是否获取成功
     */
    public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
        // set支持多个参数 NX(not exist) XX(exist) EX(seconds) PX(million seconds)
        String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
        if (LOCK_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }

   /**
     * 增加锁判断,但因判断与删除不是原子操作,在并发场景时,会导致错误删除
     * @param jedis
     * @param lockKey
     * @param requestId
     */
    public static void releaseLock2(Jedis jedis, String lockKey, String requestId) {
        // 判断加锁与解锁是不是同一个客户端
        if (requestId.equals(jedis.get(lockKey))) {
            // 若在此时,这把锁突然不是这个客户端的,则会误解锁
            // 两个操作不能保证原子性
            jedis.del(lockKey);
        }
    }

在解锁代码中可以看到,我们也增加了客户端标志校验应该可以解决客户端校验问题了吧?其实并没有,我们要知道对redis来说,每个命令都是原子的,你的get与del方法是两个命令,无法保证原子操作。也就是我们多线程中常见的i++;操作,其实他是由3个操作执行。

那我们如何确保get与del的原子操作呢?我们可以使用lua脚本来实现。上述代码我们可以调整为一个lua脚本。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
   /**
     * 释放分布式锁,使用lua脚本删除,可确保判断与删除的原子操作
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param requestId 请求标识
     * @return 是否释放成功
     */
    public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
        if (RELEASE_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }

通过增加客户端校验与解锁的原子性就可以实现安全的解锁。

有了上边的方式是不是就可以确保分布式锁的全部问题了?并不是,还有一种场景没有考虑到。

程序执行时间超出锁的过期时间

如果我们的加锁程序执行时间超出锁过期时间时,就会导致分布式锁失效。此时其他客户端是可以获得到锁的。如下图:

那么这种问题如何解决呢?

Redission

Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid),提供了分布式和可扩展的Java数据结构,比如分布式对象,分布式集合(Map、List、Queue、Set),分布式锁等等功能,不需要自己去运行一个服务实现。

Redisson官网

Redisson Git地址

Redission是由一个中国人与俄罗斯人共同发起的,所以中文文档比较详细。

使用Redission实现分布式锁

使用Redission可以很简单的实现分布式锁,代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static void main(String[] args) throws InterruptedException {
        //设定锁标志
        //会在redis中创建一个Hash,Key是客户端UUID,value是锁重入次数
        RLock rLock = redissonClient.getLock("lockKey");
        // 最多等待100秒、上锁10s以后自动解锁
        if(rLock.tryLock(100,10, TimeUnit.SECONDS)){
            System.out.println("获取锁成功,此时可以查看redis中的数据!");
        }
        //线程等待后可在redis中查到
        Thread.sleep(20000);
        rLock.unlock();
}

Redission不只可以实现独占锁,还可以实现如:可重入锁、公平锁、联锁、红锁、读写锁等等。

redission实现分布式锁的逻辑基本与上边我们讲的原理差不多,它还解决了我们最后一个问题,程序执行时间超出锁过期时间的问题。

他使用了一个《看门狗》的概念来实现自动续期。默认最大续期时间30s,也就是说如果业务超出30秒还未执行会自动解锁。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Redis分布式锁的正确实现方式(Java版)
分布式锁一般有三种实现方式:1. 数据库乐观锁;2. 基于Redis的分布式锁;3. 基于ZooKeeper的分布式锁。本篇博客将介绍第二种方式,基于Redis实现分布式锁。虽然网上已经有各种介绍Redis分布式锁实现的博客,然而他们的实现却有着各种各样的问题,为了避免误人子弟,本篇博客将详细介绍如何正确地实现Redis分布式锁。
wuweixiang
2018/10/11
1.5K0
如何用redis正确实现分布式锁?
先把结论抛出来:redis无法正确实现分布式锁!即使是redis单节点也不行!redis的所谓分布式锁无法用在对锁要求严格的场景下,比如:同一个时间点只能有一个客户端获取锁。
yaphetsfang
2020/07/30
4580
大厂-分布式专栏 23 分布式系统下分布式锁的实现
锁是开发过程中十分常见的工具,你一定不陌生,悲观锁,乐观锁,排它锁,公平锁,非公平锁等等,很多概念,如果你对java里的锁还不了解,可以参考这一篇:不可不说的Java“锁”事,这一篇写的很全面了,但是对于初学者,知道这些锁的概念,由于缺乏实际工作经验,可能并不了解锁的实际使用场景,Java中可以通过Volatile、Synchronized、ReentrantLock 三个关键字来实现线程的安全,这部分知识在第一轮基础面试里一定会问(要熟练掌握哦)。
botkenni
2022/08/25
4210
分布式锁原来实现起来这么简单
阿粉最近迷上了 Redis,为什么呢?感觉 Redis 确实功能很强大呀,一个基于内存的 Key-Value 存储的数据库,竟然有这么多的功能,而阿粉也要实实在在的把 Redis 来弄一下,毕竟面试的时候,Redis 可以说是一个非常不错的加分项。
Java极客技术
2022/12/02
1890
分布式锁原来实现起来这么简单
Redis分布式锁加锁案例
首先我们要通过Maven引入Jedis开源组件,在pom.xml文件加入下面的代码:
FHAdmin
2021/12/17
2670
如何优雅的使用Redis实现分布式锁
我们在多线程开发过程中,肯定没避免不了使用锁,jdk中也提供了大量的锁功能,但是我们为什么还要手动开发一个分布式锁呢,原因在于我们在传统项目中使用的锁是在同一个进程中的,他们能够相互访问到彼此的资源信息,但是在分布式中,每个项目都是跑在不同的进程中的,他们无法共享资源信息,所以就需要一个能够在不同进程之间进行“通信”的第三方来实现这个功能,那么redis其实就具备这种功能的。
AI码师
2020/11/19
9230
如何优雅的使用Redis实现分布式锁
面试官:怎么实现Redis分布式锁?
在单机环境下,当存在多个线程可以同时改变某个变量(可变共享变量)时,就会出现线程安全问题。这个问题可以通过 JAVA 提供的 volatile、ReentrantLock、synchronized 以及 concurrent 并发包下一些线程安全的类等来避免。
程序员大彬
2022/07/08
3630
Redis分布式锁的正确实现方式(Java版)
https://wudashan.cn/2017/10/23/Redis-Distributed-Lock-Implement/
全栈程序员站长
2022/09/05
1.4K0
redis实现分布式锁的原理_Redis作为分布式锁原理
现在面试,一般都会聊聊分布式系统这块的东西。通常面试官都会从服务框架(Spring Cloud、Dubbo)聊起,一路聊到分布式事务、分布式锁、ZooKeeper等知识。
全栈程序员站长
2022/11/17
1K0
redis实现分布式锁的原理_Redis作为分布式锁原理
分布式锁
基于zookeeper临时有序节点可以实现的分布式锁。大致思想即为:每个客户端对某个方法加锁时,在zookeeper上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。 判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。 当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。
张伦聪zhangluncong
2022/10/26
3640
如何优雅的实现分布式锁?(文末赠书)
随着业务发展的需要,原单体单机部署的系统被演化成分布式集群系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的JavaAPI并不能提供分布式锁的能力。为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题!
范蠡
2020/06/15
5090
谈谈几种分布式锁实现
在JVM中,可以使用同步锁或Lock锁,在多线程并发的情况下保证同一时间只有一个线程修改共享变量或执行代码块。然而,随着现代应用程序基本上都基于分布式集群来实现的趋势,传统Java锁在分布式环境中使用时就显得无能为力。此时,我们需要实现分布式锁来保证共享资源的原子性。分布式锁还可以用于避免不同节点执行重复的任务,例如在分布式集群中只需要保证一个服务节点发送短信,以避免多个节点重复发送短信给同一个用户,从而避免资源的浪费。
架构狂人
2023/08/16
2340
谈谈几种分布式锁实现
Redis实现分布式锁的正确方式
封面为好友拍摄的照片,想查看更多微信公众号搜索:JavaBoy王皓或csdn博客搜索:TenaciousD
胖虎
2019/06/26
8680
Redis实现分布式锁的正确方式
关于分布式锁的面试题都在这里了
最简单的理由就是作为一个社招程序员,面试的时候一定被面啦,你看怎么多公众号都翻来覆去的发分布式锁的主题,可见它很重要啦,在高考里这就是送分题,不要怪可惜的。
王炸
2020/04/26
3.2K0
Redis 分布式锁应用
Redis 最常使用的场景是作为缓存,缓存用户信息,会话信息,还有一些热点信息。
王小明_HIT
2019/08/12
9360
使用Redis单实例实现分布式锁
在同一个jvm进程中时,可以使用JUC提供的一些锁来解决多个线程竞争同一个共享资源时候的线程安全问题,但是当多个不同机器上的不同jvm进程共同竞争同一个共享资源时候,juc包的锁就无能无力了,这时候就需要分布式锁了。常见的有使用zk的最小版本,redis的set函数,数据库锁来实现,本节我们谈谈Redis单实例情况下使用set函数来实现分布式锁。
加多
2018/09/06
5410
Redis分布式锁的实现方式及底层原理
分布式锁在分布式环境中起着非常重要的作用,它可以协调多个节点的操作,保证数据的一致性。Redis作为一个高性能、高可用的缓存系统,提供了基于Redis的分布式锁的实现方案。
青山师
2023/05/05
2.1K0
剖析分布式锁
前不久,阿里大牛虾总再次抛出了分布式锁的讨论,对照之前项目中实现的redis分布式锁总结一下
码农戏码
2021/03/23
4050
分布式锁实现大型连续剧之(一):Redis
单机环境下我们可以通过JAVA的Synchronized和Lock来实现进程内部的锁,但是随着分布式应用和集群环境的出现,系统资源的竞争从单进程多线程的竞争变成了多进程的竞争,这时候就需要分布式锁来保证。
java架构师
2018/09/26
1.2K0
分布式锁系列--02Redis实现分布式锁
有一个redis服务实例,在分布式系统中,所有需要获取锁的客户端,都需要访问这个redis实例:
IT云清
2019/01/22
5840
相关推荐
Redis分布式锁的正确实现方式(Java版)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档