使用Redisson实现分布式锁发现的【订阅超时】Subscribe timeout: (7500ms)

news2025/6/4 15:15:11

背景

使用 redisson 实现分布式锁,出现的异常:

org.redisson.client.RedisTimeoutException: Subscribe timeout: (7500ms). Increase ‘subscriptionsPerConnection’ and/or ‘subscriptionConnectionPoolSize’ parameters

在这里插入图片描述

从异常信息读的出来一些东西

  1. 订阅超时?
  2. 和 [subscriptionsPerConnection]、[subscriptionConnectionPoolSize] 似乎要调整配置,名称是这两个?

这是我使用的 redisson 客户端的版本:

       <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-boot-starter</artifactId>
            <version>3.15.6</version>
        </dependency>

正文

查阅一些资料后得出结论:

  1. redisson核心参数:subscriptionsPerConnection和subscriptionConnectionPoolSize较小,当线上出现大量锁竞争时,发布订阅连接池不满足拿去需求
  2. 服务使用的redisson3.15.6版本存在代码漏洞,当出现获取订阅连接失败时缺乏重试,且存在订阅连接无法释放的隐患,有内存泄漏风险。

优化建议:

  • 增大subscriptionsPerConnectionsubscriptionConnectionPoolSize配置大小

该配置为redisson客户端连接池配置,对redis本身性能影响较小,且线上redis资源利用率不高,可酌情调整

  • 升级redisson客户端版本至少到3.17.3

截至到该版本优化了分布式锁的订阅逻辑,并解决了因网络故障、redis集群故障等问题导致的取消订阅失败造成的连接池异常占用问题。

问题复现

服务当前Redisson版本为3.15.6,使用如下用例可稳定复现线上报错:

在这里插入图片描述

代码示例:

package com.example.demo;

import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

@Slf4j
@SpringBootTest
public class RedisTest {



        public static void main(String[] args) throws InterruptedException {
            Config config = new Config();
            config.useSingleServer()
                    .setSubscriptionConnectionPoolSize(2)
                    .setSubscriptionConnectionMinimumIdleSize(2)
                    .setSubscriptionsPerConnection(2)
//                    .setTimeout(5000)
                    .setAddress("redis://127.0.0.1:6379");

            RedissonClient redisson  = Redisson.create(config);
            ExecutorService e = Executors.newFixedThreadPool(32);
            Random random = new Random();
            for (int i = 0; i < 20000; i++) {
                e.submit(() -> {
                    try {
                        String lockKey = "lock-" + random.nextInt(5);
                        RLock lock = redisson.getLock(lockKey);
                        log.info("before lock {}", lockKey);
                        lock.lock();
                        log.info("after  lock {}", lockKey);
                        Thread.sleep(random.nextInt(20));
                        lock.unlock();
                        log.info("after  -unlock {}", lockKey);
                    } catch (Exception exception){
                        log.error("e", exception);
                    }
                });
            }

            e.shutdown();
            e.awaitTermination(10, TimeUnit.MINUTES);
        }
    }

正确使用:

redisson 客户端版本更换:

       <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-boot-starter</artifactId>
            <version>3.17.3</version>
        </dependency>

代码示例:

package com.example.demo;

import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

@Slf4j
@SpringBootTest
public class RedisTest {



        public static void main(String[] args) throws InterruptedException {
            Config config = new Config();
            config.useSingleServer()
                    .setSubscriptionConnectionPoolSize(2)
                    .setSubscriptionConnectionMinimumIdleSize(2)
                    .setSubscriptionsPerConnection(2)
//                    .setTimeout(5000)
                    .setAddress("redis://127.0.0.1:6379");

            RedissonClient redisson  = Redisson.create(config);
            ExecutorService e = Executors.newFixedThreadPool(32);
            Random random = new Random();
            for (int i = 0; i < 20000; i++) {
                e.submit(() -> {
                    try {
                        String lockKey = "lock-" + random.nextInt(5);
                        RLock lock = redisson.getLock(lockKey);
                        log.info("before lock {}", lockKey);
                        lock.lock();
                        log.info("after  lock {}", lockKey);
                        Thread.sleep(random.nextInt(20));
                        lock.unlock();
                        log.info("after  -unlock {}", lockKey);
                    } catch (Exception exception){
                        log.error("e", exception);
                    }
                });
            }

            e.shutdown();
            e.awaitTermination(10, TimeUnit.MINUTES);
        }
    }

原因分析

在这里插入图片描述

关键点:

  • 成功场景: 直接获取锁事不触发订阅流程,仅启动看门狗线程
  • 失败场景: 订阅特定格式的频道(redisson_lock_channel:{key})并阻塞线程
  • 取消订阅‌: 在锁获取成功、线程中断、等待超时三种情况下触发
  • 线程安全‌: 通过Semaphore和线程ID绑定确保订阅/取消订阅的原子性

之所以使用发布订阅模式处理锁竞争时候,为了避免时间轮询获取状态的方式带来的性能吮毫,提高执行效率

发生报错的场景:

在这里插入图片描述
服务使用Redisson3.15.6版本,当分布式锁出现大量竞争触发订阅发布流程,而subscriptionsPerConnection和subscriptionConnectionPoolSize设置较小时,redisson发布订阅的连接池打满出现等待,等待超过设置的连接超时事件就会报错Subscribe timeout

在Redisson3.16.8把版本中对该问题进行了改进,bug号:4064。针对订阅失败增加了重试逻辑,根据默认的重试次数进行重试,大幅度减少该报错的发生几率
https://github.com/redisson/redisson/issues/4064

在这里插入图片描述

 CompletableFuture<RedisPubSubConnection> connectFuture = connect(codec, channelName, msEntry, promise, type, lock, listeners);
                if (attempts.get() == config.getRetryAttempts()) {
                    return;
                }

                connectionManager.newTimeout(t -> {
                    if (connectFuture.cancel(true)) {
                        subscribe(codec, channelName, entry, promise, type, lock, attempts, listeners);
                        attempts.incrementAndGet();
                    }
                }, config.getRetryInterval(), TimeUnit.MILLISECONDS);

版本更新说明:

版本:3.16.8

如何切换到 redisson 3.16.8 这个版本 看源码?

git clone https://github.com/redisson/redisson.git
cd redisson
git checkout tags/版本号 -b 版本名

比如现状切换到 3.16.8版本

 git checkout tags/redisson-3.16.8 -b my-redisson-3.16.8-branch

该版本解决了因高并发下锁竞争超时导致的订阅异常,加入了超时的重试机制,重试多次失败后才进行报错

在这里插入图片描述
并且解决了当出现异常时,订阅不释放的问题
在这里插入图片描述
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何使用 poetry 创建虚拟环境,VSCode 如何激活使用 Poetry 虚拟环境(VSCode如何配置 Poetry 虚拟环境)

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 使用 Poetry 创建和激活虚拟环境 📒🧪 创建项目并初始化 Poetry🔧 配置虚拟环境创建位置✅ 指定Python版本📦 安装依赖并创建虚拟环境🚀 激活虚拟环境📒 在 VSCode 中配置 Poetry 虚拟环境 📒🧭 方法一:使用 V…

牛客小白月赛117

前言&#xff1a;solveABCF相对简单&#xff0c;D题思路简单但是实现麻烦&#xff0c;F题郭老师神力b(&#xffe3;▽&#xffe3;)。 A. 好字符串 题目大意&#xff1a;给定字符串s&#xff0c;里面的字母必须大小写同时出现。 【解题】&#xff1a;没什么好说的&#xff0…

美化显示GDB调试的数据结构

笔者在前面的博文记一次pdf转Word的技术经历中有使用到mupdf库&#xff0c;该库是使用C语言写的一个操作PDF文件的库&#xff0c;同时提供了Python接口&#xff0c;Java接口和JavaScript接口。 在使用该库时&#xff0c;如果想要更高的性能&#xff0c;使用C语言接口是不二的选…

一篇学习CSS的笔记

一、简介 Cascading Style Sheets简称CSS&#xff0c;中文翻译为层叠样式表。当HTML被发明出来初期&#xff0c;不同的浏览器提供了各种各样的样式语言给用户控制网页的效果&#xff0c;HTML包含的显示属性并不是很多。但是随着各种使用者对HTML的需求&#xff0c;HTML添加了大…

StarRocks x Iceberg:云原生湖仓分析技术揭秘与最佳实践

导读&#xff1a; 本文将深入探讨基于 StarRocks 和 Iceberg 构建的云原生湖仓分析技术&#xff0c;详细解析两者结合如何实现高效的查询性能优化。内容涵盖 StarRocks Lakehouse 架构、与 Iceberg 的性能协同、最佳实践应用以及未来的发展规划&#xff0c;为您提供全面的技术解…

笔试笔记(运维)

&#xff08;数据库&#xff0c;SQL&#xff09; limit1 随机返回其中一个聚合函数不可以嵌套使用 【^】这个里面的数据任何形式组合都没有 sql常用语句顺序&#xff1a;from-->where-->group by-->having-->select-->order by-->limit 只要其中一个表存在匹…

使用langchain实现五种分块策略:语义分块、父文档分块、递归分块、特殊格式、固定长度分块

文章目录 分块策略详解1. 固定长度拆分&#xff08;简单粗暴&#xff09;2. 递归字符拆分&#xff08;智能切割&#xff09;3. 特殊格式拆分&#xff08;定向打击&#xff09;Markdown分块 4. 语义分割&#xff08;更智能切割&#xff09;基于Embedding的语义分块基于模型的端到…

【项目记录】登录认证(下)

1 过滤器 Filter 刚才通过浏览器的开发者工具&#xff0c;可以看到在后续的请求当中&#xff0c;都会在请求头中携带JWT令牌到服务端&#xff0c;而服务端需要统一拦截所有的请求&#xff0c;从而判断是否携带的有合法的JWT令牌。 那怎么样来统一拦截到所有的请求校验令牌的有…

linux文件管理(补充)

1、查看文件命令 1.1 cat 用于连接文件并打印到标准输出设备上&#xff0c;它的主要作用是用于查看和连接文件。 用法&#xff1a; cat 参数 文件名 参数&#xff1a; -n&#xff1a;显示行号&#xff0c;会在输出的每一行前加上行号。 -b&#xff1a;显示行号&#xff0c;…

Python训练营---Day42

DAY 42 Grad-CAM与Hook函数 知识点回顾 回调函数lambda函数hook函数的模块钩子和张量钩子Grad-CAM的示例 作业&#xff1a;理解下今天的代码即可 1、回调函数 回调函数&#xff08;Callback Function&#xff09;是一种特殊的函数&#xff0c;它作为参数传递给另一个函数&#…

基于空天地一体化网络的通信系统matlab性能分析

目录 1.引言 2.算法仿真效果演示 3.数据集格式或算法参数简介 4.MATLAB核心程序 5.算法涉及理论知识概要 5.1 QPSK调制原理 5.2 空天地一体化网络信道模型 5.3 空天地一体化网络信道特性 6.参考文献 7.完整算法代码文件获得 1.引言 空天地一体化网络是一种将卫星通信…

c++ opencv 形态学操作腐蚀和膨胀

https://www.jb51.net/article/247894.htm(上图图片来自这个博客) https://codec.wang/docs/opencv/basic/erode-and-dilate&#xff08;上图图片参考博客&#xff09; cv::Mat kernel cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3)); cv::erode(src, dst, kern…

【c++】【数据结构】红黑树

目录 红黑树的定义红黑树的部分模拟实现颜色的向上更新旋转算法单旋算法双旋算法 红黑树与AVL树的对比 红黑树的定义 红黑树是一种自平衡的二叉搜索树&#xff0c;通过特定的规则维持树的平衡。红黑树在每个结点上都增加一个存储位表示结点的颜色&#xff0c;结点的颜色可以是…

基于SpringBoot+Redis实现RabbitMQ幂等性设计,解决MQ重复消费问题

解决MQ重复消费问题 一、实现方案 本方案参考 「RabbitMQ消息可靠性深度解析&#xff5c;从零构建高可靠消息系统的实战指南」&#xff0c;向开源致敬&#xff01; 1、业务层幂等处理&#xff1a; 每个消息携带一个全局唯一ID&#xff0c;在业务处理过程中&#xff0c;首先检查…

使用lighttpd和开发板进行交互

文章目录 &#x1f9e0; 一、Lighttpd 与开发板的交互原理1. 什么是 Lighttpd&#xff1f;2. 与开发板交互的方式&#xff1f; &#x1f9fe; 二、lighttpd.conf 配置文件讲解⚠️ 注意事项&#xff1a; &#x1f4c1; 三、目录结构说明&#x1f4a1; 四、使用 C 编写 CGI 脚本…

DRF的使用

1. DRF概述 DRF即django rest framework&#xff0c;是一个基于Django的Web API框架&#xff0c;专门用于构建RESTful API接口。DRF的核心特点包括&#xff1a; 序列化&#xff1a;通过序列化工具&#xff0c;DRF能够轻松地将Django模型转换为JSON格式&#xff0c;也可以将JS…

2024年09月 C/C++(四级)真题解析#中国电子学会#全国青少年软件编程等级考试

C/C++编程(1~8级)全部真题・点这里 第1题:有几个PAT 字符串 APPAPT 中包含了两个单词 PAT,其中第一个 PAT 是第 2 位,第 4 位(A),第 6 位(T);第二个 PAT 是第 3 位,第 4 位(A),第 6 位(T)。 现给定字符串,问一共可以形成多少个 PAT? 时间限制:1000 内存限制:26214…

免费且好用的PDF水印添加工具

软件介绍 琥珀扫描.zip下载链接&#xff1a;https://pan.quark.cn/s/3a8f432b29aa 今天要给大家推荐一款超实用的PDF添加水印工具&#xff0c;它能够满足用户给PDF文件添加水印的需求&#xff0c;而且完全免费。 这款PDF添加水印的软件有着简洁的界面&#xff0c;操作简便&a…

mqtt协议连接阿里云平台

首先现在的阿里云物联网平台已经不在新购了&#xff0c;如下图所示&#xff1a; 解决办法&#xff1a;在咸鱼上租用一个账号&#xff0c;先用起来。 搭建阿里云平台&#xff0c;参考博客&#xff1a; &#xff08;一&#xff09;MQTT连接阿里云物联网平台&#xff08;小白向&…

一文详谈Linux中的时间管理和定时器编程

&#xff08;目录&#xff09; 先说一些在计算机中需要用到时间的地方&#xff1a;系统日志log、OS调度(时间片、定时器)等等~~ 时间的计量 计时的方式发展&#xff1a;日晷、沙漏 -> 机械钟 -> 石英振荡器、晶振 -> 铯原子钟 -> 氢原子钟 计算机中的计时方式&…