当前位置: 首页 > news >正文

redisson 使用与分析

一、前言

        上一期小永哥简单介绍了基于redis实现自定义分布式锁,通过三步优化将漏洞完善。本期要分享的是一个基于redis的分布式锁的成熟框架-redisson,该框架简单易用,功能强大,对于上一期我们担心的问题,redisson框架内部也都已实现,下面简单介绍一下redisson的使用

二、redisson配置

package com.relation.web.core.config;

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author huhy
 * @version 1.0
 * @Description:
 * @ClassName Date:2025/3/9 22:31
 */
@Configuration
public class RedissonConfig {

    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379")
                .setRetryInterval(5000)
                .setTimeout(10000)
                .setConnectTimeout(10000);
        return Redisson.create(config);
    }
}

三、redisson基本使用

        本次我们使用redisson来改造一下上一期测试代码,用redisson分布式锁解决方案来解决并发问题。

    @Test
    public void redissonTest() throws Exception{
        CountDownLatch countDownLatch = new CountDownLatch(10);
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                String uuid = UUID.randomUUID().toString().replaceAll("-","");
                String key = "test:createCode";
                //执行业务逻辑之前加锁
                RLock lock = redissonClient.getLock(key);
                try {
                    //假设业务需要执行7秒,已经超过了设置的超时时间
                    TimeUnit.SECONDS.sleep(8);
                }catch (Exception e){
                }
                try {
                    lock.lock();
                    createCodeTest(1L);
                }finally {
                    //判断当前要释放的锁是否是当前线程加的锁
                    if(lock.isHeldByCurrentThread()){
                        //业务逻辑执行后解锁
                        lock.unlock();
                    }
                }
                countDownLatch.countDown();
            },"测试线程"+i).start();
        }
        countDownLatch.await();
    }

    /**
     * 功能描述:测试生成编码
     * @param: 编码规则id
     * @Author:huhy
     * @Date: 2025/3/11 23:03
     */
    private void createCodeTest(Long id){
        //通过编码id获取编码规则
        TSCodeRule tsCodeRule = codeRuleService.selectTSCodeRuleById(id);
        if(ObjectUtil.isEmpty(tsCodeRule)){
            System.out.println(id+":编码规则不存在");
        }
        //获取最新的流水码值
        String serialCode = "";
        List<TSSerialCode> tsSerialCodes = serialCodeService.selectTSSerialCodeList(null);
        if(tsSerialCodes.isEmpty()){
            serialCode = String.valueOf(1);
        }else {
            tsSerialCodes.sort((t1,t2)->{
                Integer code1 = Integer.parseInt(t1.getSerialCode());
                Integer code2 = Integer.parseInt(t2.getSerialCode());
                return code2.compareTo(code1);
            });
            serialCode = tsSerialCodes.get(0).getSerialCode();
        }
        //获取当前线程名称
        String threadName = Thread.currentThread().getName();
        //计算出本次需要的流水码
        Integer newSerialCode = Integer.parseInt(serialCode)+tsCodeRule.getStepLength();
        //生成业务编码
        String codeResult = tsCodeRule.getCodePrefix()+newSerialCode;
        System.out.println(threadName+" 获取编码:"+codeResult);
        //将最新的流水码保存
        TSSerialCode tsSerialCode = new TSSerialCode();
        tsSerialCode.setSerialCode(newSerialCode+"");
        tsSerialCode.setCreateTime(new Date());
        serialCodeService.insertTSSerialCode(tsSerialCode);
    }

        运行测试代码,在并发场景下编码生成正确,说明redisson分布式锁框架简单、易用、可靠。

四、redisson核心实现简介

        4.1、我们在锁创建、加锁、解锁处打上断点,通过断点来窥探一下redisson的实现原理。

                进入redissonClient的getLock方法,该方法调用了RedissonLock的构造方法,构造方法中对RedissonLock的相关信息进行一些初始化操作,比如说commandExecutor(执行器)等信息的初始化,此步骤只是做一些初始化的事情,并没有真正开始加锁。

        

        4.2、通过断点一步一步往下走,我们主要是找核心的加锁逻辑,就是最后截图中的tryLockInnerAsync方法,该方法的核心逻辑其实也是通过redis命令进行加锁,只不过它用的是lua脚本进行的加锁。

        我们把lua脚本复制出来看一下。大概意思就是先判断是否存在,如果不存在就进行创建,并且设置过期时间,如果已存在,判断是否是当前线程自己的锁,如果是的话,则进行一个累加操作,类似于锁的重入。

//如果key不存在
if (redis.call('exists', KEYS[1]) == 0) 
    then 
        //创建元素
        redis.call('hset', KEYS[1], ARGV[2], 1); 
        //设置过期时间
        redis.call('pexpire', KEYS[1], ARGV[1]);
        return nil; 
        end; 
//如果key存在并且是自己加的锁(也就是锁重入)
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) 
    then 
        //则给原来的可以进行累加
        redis.call('hincrby', KEYS[1], ARGV[2], 1); 
        redis.call('pexpire', KEYS[1], ARGV[1]); 
        return nil; 
        end; 
return redis.call('pttl', KEYS[1]);

             如果加锁成功,那么另起线程进行锁的续期操作,具体是每隔过期时间的三分之一进行续期,就是说如果设置的过期时间的三分之一时间内没有执行完业务,那么就再进行续期操作。续期的核心操作也是通过lua脚本来执行的。

//如果key存在
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) 
    then 
        //将key再设置一遍过期时间
        redis.call('pexpire', KEYS[1], ARGV[1]); 
        //返回成功标识
        return 1;
        //结束
        end; 
//如果key不存在,则返回失败标识,则续期线程退出
return 0;"

        4.3、解锁时,我们还是一步一步去找核心方法unlockInnerAsync,在这个方法内,核心的其实还是lua脚本,通过lua脚本对锁进行释放。释放完锁以后,最后再关闭续期线程,这样完成了一个完整的流程。

"if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) 
    then 
        return nil;
        end; 
        local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); 
if (counter > 0) 
    then 
        redis.call('pexpire', KEYS[1], ARGV[2]); return 0; 
    else 
        redis.call('del', KEYS[1]); redis.call('publish', KEYS[2], ARGV[1]); 
        return 1; 
        end; 
return nil;"

五、结语

        这次分享源码对小永哥来说挑战非常大,我也不确定我是否说清楚了这个事情,大家感兴趣可以也尝试着打断点去跟一下源码,由于能力有限,有不完美的地方还望大家多多指教,希望可以和小伙伴们共同进步

相关文章:

  • C语言入门教程100讲(34)结构体初始化
  • RISC-V: 固件与操作系统引导 | eg OpenSBI | 借助AI注释项目代码
  • Java架构师成长之路
  • llama源码学习·model.py[3]ROPE旋转位置编码(2)旋转角度生成代码
  • vue-cli如何正确关闭prefetch和preload
  • 让S7-1200与DeepSeek联动(转)
  • MCU vs SoC
  • vue3 UnwrapRef 与 unref的区别
  • [极客大挑战 2019]BuyFlag-3.23BUUCTF练习day5(3)
  • LeetCode HOT100系列题解之岛屿数量(10/100)
  • 【Keil5-开发技巧】
  • VSCode 生成HTML 基本骨架
  • 【CICD】Ansible知识库
  • 【MySQL数据库】触发器与事件
  • 从失衡到平衡:手撕 AVL 树的插入旋转操作
  • Cursor 一键自动无限续杯(3月24日)亲测有效
  • 黑马点评-UV统计
  • 2025前端面试题记录
  • 23种设计模式-创建型模式-工厂方法
  • 【USTC 计算机网络】第三章:传输层 - 传输层概述及其服务、多路复用与解复用、无连接传输:UDP
  • 上海市第二十届青少年科技节启动:为期半年,推出百余项活动
  • 专访|《内沙》导演杨弋枢:挽留终将失去的美好
  • 百色一女子称家委会强制排班被迫抱婴儿校门口站岗?区教育局:自愿参与
  • 陈吉宁龚正黄莉新胡文容等在警示教育基地参观学习,出席深入贯彻中央八项规定精神学习教育交流会
  • 网约车座椅靠背张贴“差评者得癌症”,如祺出行:未收到投诉无法处理
  • 日本前卫艺术先驱群展上海:当具体派相遇古树古宅