首页 > 数据库 >Redis 实现分布式锁

Redis 实现分布式锁

时间:2024-08-13 12:51:46浏览次数:15  
标签:续约 实现 Redis 过期 线程 stringRedisTemplate 分布式

目录

Redis 实现分布式锁及续约机制详解

1、Redis 分布式锁的基本实现

2、引入守护线程[看门狗机制】


Redis 实现分布式锁及续约机制详解
  • 在分布式系统中,多个节点对同一资源的访问可能会产生竞争,为了解决这个问题,我们需要一种机制来保证同一时间内只有一个节点可以访问资源,分布式锁便是其中的一种解决方案。Redis 作为一种高性能的分布式缓存系统,其自带的 SETNX 命令和过期时间机制非常适合用来实现分布式锁。本文将详细介绍如何利用 Redis 实现分布式锁,并引入一个守护线程为锁自动续约,确保在业务长时间执行的过程中锁不会被意外释放。
1、Redis 分布式锁的基本实现
  • 第一版
  • 在这个简单的实现中,我们通过 setIfAbsent 方法(对应 Redis 的 SETNX 命令)来尝试获取锁。如果获取成功,就执行相应的业务逻辑,处理完后再检查当前锁是否仍属于自己(通过比对 lockId),如果是,则删除锁以释放资源。

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Test
    void RedisDistributedLock2() {
        int i = 1;
        // 线程获得唯一标识的锁ID
        String lockId = UUID.randomUUID().toString();
        // 使用 Redis 的 setNx 命令,同时设置过期时间,保证原子性
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("key", lockId, 15, TimeUnit.SECONDS);

        if (Boolean.TRUE.equals(lock)) {
            try {
                i += 10;
                // 处理业务逻辑
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                // 释放锁
                if (lockId.equals(stringRedisTemplate.opsForValue().get("key"))) {
                    stringRedisTemplate.delete("key");
                }else{
                    System.out.println("当前线程持有锁已过期!");
                }
            }
        }else {
            // 获取锁失败,快速返回
            System.out.println("获取锁失败,当前线程无法执行任务。");
            return; // 直接返回,终止方法执行
        }
    }
 

  •  虽然上述代码能够实现分布式锁的基本功能,但在某些场景下,锁的过期时间可能会过短,导致在业务逻辑尚未执行完毕时锁被自动释放,从而可能导致其他线程错误地获取到锁。为了解决这个问题,我们需要引入一种机制来定期延长锁的过期时间。

2、引入守护线程[看门狗机制】
  • 为了保证在业务逻辑长时间执行时锁不会过期,我们可以通过守护线程来为锁续约。下面是具体的实现

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    private static final int LOCK_EXPIRE_TIME = 15; // 锁的过期时间,单位为秒
    private static final int RENEWAL_INTERVAL = 10; // 续约的间隔时间,单位为秒

    @Test
    void RedisDistributedLock(){
        int i=1;
        //某线程获取到锁唯一标识
        String lockId = UUID.randomUUID().toString();
        //使用Redis的setNx命令,同时过期时间一起,保证原子性
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("key", lockId, LOCK_EXPIRE_TIME, TimeUnit.SECONDS);
        if (Boolean.TRUE.equals(lock)){
            // 创建续约标志,确保可以终止续约线程
            AtomicBoolean continueRenewal = new AtomicBoolean(true);

            // 守护线程进行锁续约
            Thread renewalThread = new Thread(() -> {
                try {
                    while (continueRenewal.get()) {
                        Thread.sleep(TimeUnit.SECONDS.toMillis(RENEWAL_INTERVAL));
                        // 如果锁ID匹配,则续约
                        if (lockId.equals(stringRedisTemplate.opsForValue().get("key"))) {
                            stringRedisTemplate.expire("key", LOCK_EXPIRE_TIME, TimeUnit.SECONDS);
                        } else {
                            break; // 锁已不再属于当前线程,退出续约线程
                        }
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            });

            renewalThread.setDaemon(true); // 设置为守护线程
            renewalThread.start();


            try{
                i+=10;
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                //如果是当前线程的锁,操作完成释放锁
                if (lockId.equals(stringRedisTemplate.opsForValue().get("key"))){
                    continueRenewal.set(false); // 停止续约线程
                    stringRedisTemplate.delete("key");
                }else{
                    System.out.println("当前线程持有锁已过期!");
                }
            }
        }else {
            // 获取锁失败,快速返回
            System.out.println("获取锁失败,当前线程无法执行任务。");
            return; // 直接返回,终止方法执行
        }
    }
  • 续约线程的引入:在成功获取锁后,我们创建并启动一个守护线程,该线程会每隔一段时间检查当前锁的状态,如果锁仍属于当前线程,则通过 expire 方法延长锁的过期时间。 
  • 续约逻辑:续约线程的执行周期由 RENEWAL_INTERVAL 控制,每次延长的过期时间为 LOCK_EXPIRE_TIME。这样即使业务逻辑执行时间较长,锁也不会因为过期而被释放。
  • 守护线程的终止:在释放锁之前,我们首先将 continueRenewal 置为 false,使守护线程停止续约操作,然后再删除锁。这种设计避免了锁被释放后续约线程仍在运行的风险。

总结:以上便是 Redis 实现分布式锁及其续约机制的详细介绍,希望对你有所帮助。如果你在实际开发中遇到问题或有其他更好的方案,欢迎交流讨论。

标签:续约,实现,Redis,过期,线程,stringRedisTemplate,分布式
From: https://blog.csdn.net/m0_64022678/article/details/141159485

相关文章

  • 【Java毕设选题推荐】基于SpringBoot的springbootstone音乐播放器的设计与实现
    前言:我是IT源码社,从事计算机开发行业数年,专注Java领域,专业提供程序设计开发、源码分享、技术指导讲解、定制和毕业设计服务......
  • H5 html单页面实现对接接口,获取接口数据
    一、AJAX的一种实现方式,XMLHttpRequestvarxhr=newXMLHttpRequest();xhr.open("POST","你的接口URL",true);xhr.setRequestHeader("Content-Type","application/json;charset=UTF-8");//准备发送的数据vardata=JSON.stringif......
  • 在Vue3中实现自定义指令
    一、前言我们需要明白为什么需要自定义一个指令,其实就是想更加简洁地重复使用操作DOM的逻辑,这就和组件化和组合式函数差不多。不管是Vue内置的指令还是自定义的指令,都有类似于组件的生命周期,我们可以在不同的生命周期完成不同的逻辑操作,并绑定到组件元素上,这样就产生了自定义指......
  • vue2实现轮播图
    1.在components路径下新建文件Carousel.vue,在Carousel.vue文件中创建一个Vue组件实现轮播图的功能<button@click="prev"class="carousel-controlprev">‹<button@click="next"class="carousel-controlnext">›<spanv-for=&qu......
  • 程序员失业在家,我是如何用AI绘画搞钱?掌握这几个方法实现经济自由!
    大部分的设计师除了主业以外,都会利用空余时间去接单做副业。单子包括但不限于产品/品牌LOGO、电商产品图设计、海报、室内设计图等等,单价在几十到上千不等。那么普通人也能像他们一样去接单赚钱吗?在AI绘画还没出现前,没有任何设计经验的人,可以说是完全没有机会!!但是,时代......
  • 分布式知识总结(一致性Hash算法)
    文章收录在网站:http://hardyfish.top/文章收录在网站:http://hardyfish.top/文章收录在网站:http://hardyfish.top/文章收录在网站:http://hardyfish.top/一致性Hash算法假如有三台服务器编号node0、node1、node2,现在有3000万个key,希望可以将这些个key均匀的缓存到三台机......
  • 如何在 Flask 中实现用户登录
    在Flask中实现用户登录功能通常涉及以下几个步骤:设置Flask应用、创建用户模型、处理用户注册、实现登录逻辑以及保护受限路由。下面就是我总结得一些经验,可以一起聊一聊。1、问题背景在使用Flask框架构建Web应用程序时,通常需要实现用户登录功能。常见的需求是将......
  • redis scan 优雅的批量删除
    参考:https://ops-coffee.cn/s/x48wmx_k55hmPfZL0tyBYQ.htmlRedis删除特定前缀key的优雅实现还在用keys命令模糊匹配删除数据吗?这就是一颗随时爆炸的炸弹!Redis中没有批量删除特定前缀key的指令,但我们往往需要根据前缀来删除,那么究竟该怎么做呢?可能你一通搜索后会得到下边的答......
  • 使用 navigateTo 实现灵活的路由导航
    title:使用navigateTo实现灵活的路由导航date:2024/8/13updated:2024/8/13author:cmdragonexcerpt:摘要:本文详细介绍Nuxt.js中的navigateTo函数,包括基本用法、在路由中间件中使用、导航到外部URL和新标签页打开链接的方法,以及参数详解和注意事项,展示了该函数......
  • vue3的defineAsyncComponent是如何实现异步组件的呢?
    前言在上一篇给我5分钟,保证教会你在vue3中动态加载远程组件文章中,我们通过defineAsyncComponent实现了动态加载远程组件。这篇文章我们将通过debug源码的方式来带你搞清楚defineAsyncComponent是如何实现异步组件的。注:本文使用的vue版本为3.4.19欧阳写了一本开源电子书vue3编......