spring-data-redis自定义实现看门狗机制

文章目录

  • 前言
    • redission分布式锁看门狗机制简单流程图
  • spring-data-redis实现看门狗机制指南开始
    • 引入依赖
    • 配置redis连接以及基础配置
    • 实现redis分布式锁工具类
    • 直接失败和锁重试机制实现
  • 效果图展示

前言

项目中使用redis分布式锁解决了点赞和楼层排序得问题,所以这里就对这个redis得分布式锁进行了学习,一般使用得是redission提供得分布式锁解决得这个问题,但是知其然更要知其所以然,所以自己就去找了一些资料以及也实践了一下就此记录分享一下。

redission分布式锁看门狗机制简单流程图

在这里插入图片描述

spring-data-redis实现看门狗机制指南开始

引入依赖

        <!--redis的依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--json工具包-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.60</version>
        </dependency>      

配置redis连接以及基础配置

spring:
  redis:
    host: localhost
    port: 6379
    lettuce:
      timeout: 200000 
    database: 1

在Spring Boot的配置类中创建一个RedisTemplate的Bean:

@Configuration
public class RedisConfig {


    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        // 我们为了自己开发方便,一般直接使用 <String, Object>
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(connectionFactory);
        // Json序列化配置
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        // String 的序列化
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式采用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式采用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
}

实现redis分布式锁工具类

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component
public class RedisLockUtils {
    @Resource
    private RedisTemplate redisTemplate;
    private volatile static Map<String, LockInfo> lockInfoMap = new ConcurrentHashMap<>();
    private static final Long SUCCESS = 1L;
    public static class LockInfo {
        private String key;
        private String value;
        private int expireTime;
        //更新时间
        private long renewalTime;
        //更新间隔
        private long renewalInterval;
        public static LockInfo getLockInfo(String key, String value, int expireTime) {
            LockInfo lockInfo = new LockInfo();
            lockInfo.setKey(key);
            lockInfo.setValue(value);
            lockInfo.setExpireTime(expireTime);
            lockInfo.setRenewalTime(System.currentTimeMillis());
            lockInfo.setRenewalInterval(expireTime*1000 *2 / 3);
            return lockInfo;
        }

        public String getKey() {
            return key;
        }
        public void setKey(String key) {
            this.key = key;
        }
        public String getValue() {
            return value;
        }
        public void setValue(String value) {
            this.value = value;
        }
        public int getExpireTime() {
            return expireTime;
        }
        public void setExpireTime(int expireTime) {
            this.expireTime = expireTime;
        }
        public long getRenewalTime() {
            return renewalTime;
        }
        public void setRenewalTime(long renewalTime) {
            this.renewalTime = renewalTime;
        }
        public long getRenewalInterval() {
            return renewalInterval;
        }
        public void setRenewalInterval(long renewalInterval) {
            this.renewalInterval = renewalInterval;
        }
    }

    /**
     * 使用lua脚本更新redis锁的过期时间
     * @param lockKey
     * @param value
     * @return 成功返回true, 失败返回false
     */
    public boolean renewal(String lockKey, String value, int expireTime) {
        String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('expire', KEYS[1], ARGV[2]) else return 0 end";
        DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>();
        redisScript.setResultType(Boolean.class);
        redisScript.setScriptText(luaScript);
        List<String> keys = new ArrayList<>();
        keys.add(lockKey);

        Object result = redisTemplate.execute(redisScript, keys, value, expireTime);
        log.info("更新redis锁的过期时间:{}", result);
        return (boolean) result;
    }

    /**
     * @param lockKey    锁
     * @param value      身份标识(保证锁不会被其他人释放)
     * @param expireTime 锁的过期时间(单位:秒)
     * @return 成功返回true, 失败返回false
     */
    public boolean lock(String lockKey, String value, int expireTime) {
        Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(lockKey, value, expireTime, TimeUnit.SECONDS);
        if(aBoolean){
            lockInfoMap.put(String.valueOf(Thread.currentThread().getId()),LockInfo.getLockInfo(lockKey,value,expireTime));
        }
        return aBoolean;
    }

    /**
     * redisTemplate解锁
     * @param key
     * @param value
     * @return 成功返回true, 失败返回false
     */
    public boolean unlock2(String key, String value) {
        Object currentValue = redisTemplate.opsForValue().get(key);
        boolean result = false;
        if (StringUtils.isNotEmpty(String.valueOf(currentValue)) && currentValue.equals(value)) {
            lockInfoMap.remove(String.valueOf(Thread.currentThread().getId()));
            result = redisTemplate.opsForValue().getOperations().delete(key);
        }
        return result;
    }

    /**
     * 定时去检查redis锁的过期时间
     */
    @Scheduled(fixedRate = 1000)
    @Async("redisExecutor")
    public void renewal() {
        long now = System.currentTimeMillis();
        for (Map.Entry<String, LockInfo> lockInfoEntry : lockInfoMap.entrySet()) {
            LockInfo lockInfo = lockInfoEntry.getValue();
            System.out.println("++"+lockInfo.key);
            if (lockInfo.getRenewalTime() + lockInfo.getRenewalInterval() < now) {
                renewal(lockInfo.getKey(), lockInfo.getValue(), lockInfo.getExpireTime());
                lockInfo.setRenewalTime(now);
                log.info("lockInfo {}", JSON.toJSONString(lockInfo));
            }
        }
    }

    /**
     * 分布式锁设置单独线程池
     * @return
     */
    @Bean("redisExecutor")
    public ThreadPoolTaskExecutor redisExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(1);
        executor.setMaxPoolSize(1);
        executor.setQueueCapacity(1);
        executor.setKeepAliveSeconds(60);
        executor.setThreadNamePrefix("redis-renewal-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());
        executor.initialize();
        return executor;
    }
}

这个类是一个用于实现分布式锁的工具类,主要提供了以下功能:

  1. renewal() 方法:定时检查并更新 Redis 锁的过期时间。该方法使用 @Scheduled 注解进行定时执行,通过遍历 lockInfoMap 中保存的锁信息,判断是否需要更新锁的过期时间,并调用 renewal() 方法进行更新。

  2. renewal(String lockKey, String value, int expireTime) 方法:使用 Lua 脚本更新 Redis 锁的过期时间。该方法首先定义了一个 Lua 脚本,然后使用 redisTemplate.execute() 方法执行该脚本,并传入相应的参数。如果执行成功,则返回 true,否则返回 false。

  3. lock(String lockKey, String value, int expireTime) 方法:获取分布式锁。该方法使用 Redis 的 setIfAbsent() 方法尝试将锁的键值对存储到 Redis 中,并设置相应的过期时间。如果存储成功,则返回 true,表示获取锁成功;否则返回 false,表示获取锁失败。

  4. unlock2(String key, String value) 方法:释放分布式锁。该方法首先获取 Redis 中当前的锁值,然后判断锁值是否和传入的 value 相等。如果相等,则从 lockInfoMap 中移除锁信息,并调用 Redis 的 delete() 方法删除锁的键值对。最后返回删除结果,表示是否成功释放锁。

  5. redisExecutor() 方法:配置一个单独的线程池用于执行 renewal() 方法。该方法创建一个 ThreadPoolTaskExecutor 对象,并设置相关的属性,如核心线程数、最大线程数、队列容量等。

直接失败和锁重试机制实现

直接失败的方式,就是调用获取锁的方法判断是否加锁成功,失败则直接中断方法执行返回

    @GetMapping("/test2")
    public Object test2(){
        boolean a = redisLockUtils.lock("A", "111", 5);
        if(!a){
            return "获取锁失败";
        }
        try{
            System.out.println("执行开始-------------------test2");
            TimeUnit.SECONDS.sleep(12);
            System.out.println("执行结束-------------------test2");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {

            redisLockUtils.unlock2("A","111");
            System.out.println("释放锁-----------------------test2");
        }
        return null;
   }

锁重试,这里是封装了获取锁的方法,加入了一个重试次数的限制,通过使用while循环去尝试获取锁。

private Boolean getLock(int tryNum, String key, String value, int exp) throws InterruptedException {
    int i = 0; // 初始化计数器,记录尝试获取锁的次数
    boolean flag = false; // 初始化标志变量,表示获取锁的结果

    while (true) { // 循环进行尝试获取锁的操作
        if (i == tryNum) { // 判断是否达到尝试获取锁的最大次数
            return flag; // 返回当前的获取锁结果
        }

        flag = redisLockUtils.lock(key, value, exp); // 尝试获取锁,返回是否成功获取到锁的结果

        if (flag) { // 如果成功获取到锁
            return flag; // 直接返回获取锁结果为 true
        } else { // 如果未能成功获取到锁
            i++; // 计数器加一,表示已经尝试了一次获取锁的操作
            TimeUnit.SECONDS.sleep(1); // 暂停一秒钟,等待一段时间后再进行下一次获取锁的尝试
        }
    }
}

效果图展示

在这里插入图片描述
在这里插入图片描述
这里设置的锁过期是5秒每隔2/3的时间也就是4秒进行一次续期一共续了3次,因为我中间让线程睡了12秒。可以看到锁被正常续费了,确保了业务的正常执行不会抢占资源。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/364786.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java的Mysql使用

Java的Mysql使用 说明 通过Java的方式连接Mysql中的数据库&#xff0c;并对数据库中的数据进行增加 查询操作 ​ 使用Mysql所提供的第三方库中的类(Mysql的API) 对其进行操作 ​ 将Mysql Jar包添加到lib目录后&#xff0c;就可以使用其中的类对其Mysql数据库进行操作 Mysq…

语义分割:从早期探索到深度学习的突破

语义分割&#xff1a;从早期探索到深度学习的突破 语义分割的端倪&#xff1a;从早期探索到深度学习的突破引言早期技术&#xff1a;图像处理与模式识别边缘检测区域生长图割(Graph Cut)聚类方法 深度学习的兴起&#xff1a;CNN革命2012年 AlexNet的突破全卷积网络&#xff08;…

网络编程之ip地址

目录 1、概念&#xff1a; 2、所属的tcp/ip协议的层 3、IP地址查询 &#xff08;1&#xff09;Linux &#xff08;2&#xff09;windows 4、IP地址结构组成及分类 &#xff08;1&#xff09;组成&#xff08;网络位主机位&#xff09; &#xff08;2&#xff09;分类 1、…

文心一言 VS 讯飞星火 VS chatgpt (192)-- 算法导论14.2 2题

二、用go语言&#xff0c;能否在不影响红黑树任何操作的渐近性能的前提下&#xff0c;将结点的黑高作为树中结点的一个属性来维护&#xff1f;说明如何做&#xff0c;如果不能&#xff0c;请说明理由。如何维护结点的深度&#xff1f; 文心一言&#xff1a; 在Go语言中&#…

Oracle闪回日志管理(flashbackup log)

Oracle闪回日志管理&#xff08;flashbackup log&#xff09; 1.开启闪回日志 shutdown immediate startup mount; alter database archivelog; alter database flashback on; alter database open; 2、查看闪回日志保留期限 show parameter flash 默认是1440分钟&#xff0c…

宋仕强论道之华强北蓝牙耳机(三十七)

华强北最近几年最火爆的产品就是TWS蓝牙耳机了&#xff0c;蓝牙耳机是指通过蓝牙无线技术连接智能手机等设备&#xff0c;实现无线通讯的一种设备。因为苹果蓝牙应用技术的突破&#xff0c;而呈现爆发之势&#xff0c;目前蓝牙耳机的市场存量是非常之大&#xff0c;全世界智能手…

gitlab ci cd 不完全指南

gitlab 可能大家很常用&#xff0c;CI、CD 也应该早有耳闻&#xff0c;但是可能还没有去真正地了解过&#xff0c;这篇文章就是我对 gitlab CI、CD 的一些理解&#xff0c;以及踩过的一些坑&#xff0c;希望能帮助到大家。 什么是 CI、CD CI&#xff08;Continuous Integrati…

【计算机毕业设计】063德云社票务系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

情人节最全送礼指南!实用性强礼物推荐

眼看七夕节就要到了&#xff0c;如果还没有想到要送什么礼物给对象的&#xff0c;抓紧来看小编总结的这几款实用性比较强的礼物&#xff0c;送礼一下子就送到了心坎上了&#xff0c;少整点一些花里胡哨的礼物&#xff0c;多送点实用性比较强的&#xff01;下面给大家整理了一份…

FW如何区别 PAW3212DB-TJDT 和 PAW3220DB-TJDL/TJDR/TJDS

PAW3212DB-TJDT 和 PAW3220DB-TJDL/TJDR/TJDS 的引脚功能定义是一样的&#xff0c;只是封装有一点不一样。PAW3212DB-TJDT是圆形火山口&#xff0c;配的是圆孔透镜&#xff0c;PAW3220DB-TJDL/TJDR/TJDS是方形火山口&#xff0c;配的是方孔透镜。 PAW3212DB-TJDT 和 PAW3220DB-…

Gaussian_Splatting 项目脚本指令

准备好一个稀疏重建的目录&#xff08;如Colmap的sparse文件&#xff09;&#xff0c;高斯泼溅需要稀疏重建的点云结果来作为输入&#xff0c;进行进一步训练和渲染。 可以参考&#xff1a;gaussian-splatting原理 有一点需要注意&#xff1a;Gaussian_Splatting 需要稀疏重建…

Flask框架开发学习笔记《5》简易服务器代码

Flask框架开发学习笔记《5》 Flask是使用python的后端&#xff0c;由于小程序需要后端开发&#xff0c;遂学习一下后端开发。 简易服务器代码 接口解析那一块很关键&#xff0c;学后端服务器这一块&#xff0c;感觉主要就是学习相应地址的接口怎么处理。 然后写清楚每个地址…

深度揭秘:代理IP的工作原理及其在网络安全中的关键角色

代理IP的工作原理及其在网络安全中的关键角色是一个相对复杂但非常重要的主题。以下是对这一内容的深度揭秘&#xff1a; 代理IP的工作原理 1. 请求转发 当一个客户端&#xff08;如浏览器或爬虫程序&#xff09;使用代理IP时&#xff0c;它不是直接与目标网站通信&#xff0c…

SpringBoot使用Rabbit详解含完整代码

1. 摘要 本文将详细介绍如何在Spring Boot应用程序中集成和使用RabbitMQ消息队列。RabbitMQ是一个开源的消息代理和队列服务器&#xff0c;用于通过轻量级和可靠的消息在应用程序或系统之间进行异步通信。本文将通过步骤说明、代码示例和详细注释&#xff0c;指导读者在Spring…

有趣的css - 动态的毛玻璃背景

页面效果 此效果主要使用 backdrop-filter 属性&#xff0c;以及配合 animation 属性来实现毛玻璃模糊和一些动效。 此效果可适用于登录窗口&#xff0c;网站背景或者一些卡片列表中&#xff0c;使网页更具科技感和空间感。 核心代码部分&#xff0c;简要说明了写法思路&#x…

四大组件 - ContentProvider

参考&#xff1a;Android 这 13 道 ContentProvider 面试题&#xff0c;你都会了吗&#xff1f; 参考&#xff1a;《Android 开发艺术探索》 第 9.5 节&#xff08;ContentProvider 的工作过程&#xff09; 参考&#xff1a;内容提供者程序 参考&#xff1a;<provider>&g…

Maya------显示隐藏提取复制刺破面

alth<--->ctrlshifth 补洞后刺破面&#xff0c;防止多边面的产生&#xff01;

数据探索与可视化:可视化分析数据关系-中

目录 一、前言 二、介绍 Ⅰ.一个分类变量和一个连续变量 Ⅱ.两个分类变量的一个连续变量 Ⅲ.两个分类变量和两个连续变量 Ⅳ.一个分类变量和多个连续变量 ①.平行坐标轴 ②.矩阵散点图 三、结语 一、前言 在做数据分析的时候&#xff0c;很少会遇到连续变量和分类变量…

(每日持续更新)jdk api之NotActiveException基础、应用、实战

博主18年的互联网软件开发经验&#xff0c;从一名程序员小白逐步成为了一名架构师&#xff0c;我想通过平台将经验分享给大家&#xff0c;因此博主每天会在各个大牛网站点赞量超高的博客等寻找该技术栈的资料结合自己的经验&#xff0c;晚上进行用心精简、整理、总结、定稿&…

重写Sylar基于协程的服务器(2、配置模块的设计)

重写Sylar基于协程的服务器&#xff08;2、配置模块的设计&#xff09; 重写Sylar基于协程的服务器系列&#xff1a; 重写Sylar基于协程的服务器&#xff08;0、搭建开发环境以及项目框架 || 下载编译简化版Sylar&#xff09; 重写Sylar基于协程的服务器&#xff08;1、日志模…