redis 哨兵连接不释放问题

转载文章:https://www.cnblogs.com/devilwind/p/6865750.html

问题说明:生产环境redis(哨兵模式 一主两从)使用过程中发现有一台从机sentinel端口的连接持续增长,每天增加44条established状态的连接(共四台应用服务器,每台服务器每天增加11条,每130分钟左右增加一条,相当规律)

疑问:另一台从机和主机没有这样的问题,同时开发环境、dat测试环境、uat测试环境、准生产、压测环境等同样的redis运用,所有配置都一样,却没有这样的问题。

redis使用场景:使用commons-pool2-2.4.2.jar中的JedisSentinelPool初始化连接池,jedis-2.9.0.jar进行操作。

问题处理1:一开始遇到此问题,网上查找资料,介绍说是jedis包的版本低导致(系统一开始使用的是jedis-2.4.2.jar),将jar升级至2.9.0后发布生产进行验证,结果问题没有解决(没办法啊,其余环境不能复现,其余环境升级jar后观测两周后没有问题才升级的生产环境)。  【继续努力中…………………………】

 

问题处理2:终于在上面转载地址中找到了和我遇见的问题相似度达95%的解决方法,以下全为转载内容,格式就不调整了(具体待验证…………)

一、问题现象

  • redis服务端的sentinel模块存在大量的established状态的连接,并且这些连接一直不被释放,而客户端的连接数正常。

二、问题排查过程

1、根据连接状态进行推断

  • 服务端存在大量的连接的状态为established,而客户端连接数正常,这说明客户端连接是被非正常的方式关闭的,因为根据TCP关闭连接的四次握手协议来看,只要客户端正常发起了关闭动作,服务端的状态只可能为CLOSE_WAIT/LAST_ACK/CLOSED中的一种。
  • 非正常关闭连接的情况有断电、断网后服务器重启等等,但这些场景出现的次数较少,不会造成这么多连接,究竟是怎样被正常关闭,到这一步还无法判断,只能先从其它方面入手。

2、检查业务系统的redis配置和程序逻辑

  • 检查redis配置,看是否有不规范的配置和不规范的使用redis的代码,经过排查,确定openapi有直接使用JedisSentinelPool类,但并未在spring中配置destroy-method=”destroy”,即在停止应用时释放连接。
  • 排查其它系统,未发现有不正常使用redis的情况,JedisSentinelPool造成的连接数增长也是有限的,即只在启动时才创建sentinel连接,此处暂时排查是业务代码使用redis不规范的问题。

3、从网络寻找线索

  • 通过百度搜索,找不到关于sentinel的该问题的文章。
  • 查看redis、jedis、spring-data-redis官方网站的issues及releaseNotes,均未发现有提及该问题。

4、重新思考产生连接的根源

  • 通过阅读jedis源码,发现连接sentinel的代码只有1处,即系统启动时会先建立一个短连接查询sentinel的主节点,然后开启3(业务系统配置了3个地址)个线程创建3个长连接与3个sentinel相连。短连接会立即释放,而长连接的创建放在循环中,问题可能出现在该处,以下是源码:

public void run() {

      running.set(true);

      while (running.get()) {

        j = new Jedis(host, port);

        try {

          j.subscribe(new JedisPubSub() {

            @Override

            public void onMessage(String channel, String message) {

              log.fine("Sentinel " + host + ":" + port + " published: " + message + ".");

              String[] switchMasterMsg = message.split(" ");

              if (switchMasterMsg.length > 3) {

                if (masterName.equals(switchMasterMsg[0])) {

                  initPool(toHostAndPort(Arrays.asList(switchMasterMsg[3], switchMasterMsg[4])));

                } else {

                  log.fine("Ignoring message on +switch-master for master name "

                      + switchMasterMsg[0] + ", our master name is " + masterName);

                }

              } else {

                log.severe("Invalid message received on Sentinel " + host + ":" + port

                    + " on channel +switch-master: " + message);

              }

            }

          }, "+switch-master");

        } catch (JedisConnectionException e) {

          if (running.get()) {

            log.severe("Lost connection to Sentinel at " + host + ":" + port

                + ". Sleeping 5000ms and retrying.");

            try {

              Thread.sleep(subscribeRetryWaitTimeMillis);

            } catch (InterruptedException e1) {

              e1.printStackTrace();

            }

          } else {

            log.fine("Unsubscribing from Sentinel at " + host + ":" + port);

          }

        }

      }

    }

正常情况下 j.subscribe会产生阻塞,而出现异常时会重新创建连接并且打印日志“Lost connection to Sentinel at……”

  • 在业务系统搜索“Lost connection to Sentinel at”日志,把所有业务日志都搜索一遍,没有找到该日志,最后终于在nohub日志中找到了(nohub日志在weblogic启动时会被清理,也导致找了几次才找到该日志),通过UltraEdit提取所有该内容的日志,发现了28条日志,将这些日志导出到excel比较,发现日志出现的时间间隔均为2小时11分15秒到2小时11分20秒左右,该日志出现的时间间隔非常有规律,这种规律一定与该问题存在某种联系。

一般在系统中设置时间间隔都会是个整数,2小时11分15秒显得有点怪异,然后将时间换算成秒,看是否为一个常用的或有规律的整数,2小时=7200秒,突然想到操作系统的keepalive设置中有一个7200秒,查一下操作系统默认的设置,参数如下:

net.ipv4.tcp_keepalive_time = 7200   默认 tcp空闲时间

net.ipv4.tcp_keepalive_intvl =75        默认心跳检测时间间隔

net.ipv4.tcp_keepalive_probes = 9     默认检测次数

如果完成9次心跳,仍然发现连接无效的时间为:7200+9*75=2小时11分15秒,由此,基本上可以判断,客户端的连接是被操作系统回收的,结合前面的分析,连接被回收时并未向服务端发送关闭的报文。

  •  
  • 通过在网络上搜索,长连接被阻断的原因,看到了防火墙的字样,进一步了解,发现防火墙基本上都有该项功能,即给长连接设置默认的超时时间。

5、与运维进行沟通防火墙问题

  • 刚开始问运行同事A时,同事A说防火墙用的是juniper,但绝对没有为长连接设置默认的超时时间。
  • 进一步搜索juniper的资料,确定是有默认30分钟的超时时间。
  • 与运维同事B沟通,确认有默认的超时时间。防火墙的策略是,当建立连接时,会在防火墙保存一条会话信息,记录了源IP、源端口、目的IP、目的端口。当在超时时间内,该会话代表的连接未有任何数据包时,就会清除该会话,此后若再有数据包过来,由于没有了会话,防火墙会直接丢弃该会话。
  • 要同事B将sentinel的端口26379配置一条永不超时的策略。

6、持续观察测试环境的日志

  • 发现在更改了防火墙超时策略后,仍然出现了一条“Lost connection to Sentinel at”日志,此后一直未出现,由此可见,防火墙确实对连接造成了影响。

7、初步结论

  • 防火墙每30分钟将该连接的会话信息清除,从而导致客户端操作系统检测到心跳失败,随后操作系统清除了客户端连接,使得客户端的连接数能正常释放,由于报文被防火墙丢弃,close信息也不会到达服务端,导致服务端的连接未被及时关闭。

8、继续思考服务断连接不被释放的问题

  • 按道理连接被防火墙阻断后,既然客户端的操作系统能正常回收连接,服务端应该也可以,但服务端的keepalive机制为什么没有发生作用?通过进一步阅读redis客户端-jedis的源码,发现jedis默认开启了keepalive,继续阅读redis的源码,发现redis默认关闭了keepalive,到此所有疑问已经解决。

以上因素使得防火墙每30分钟将该连接的会话信息清除,从而导致客户端操作系统检测到心跳失败,随后操作系统清除了客户端连接,使得客户端的连接数能正常释放,随后客户端的jedis收到异常后重新创建连接,而服务端的keepalive并未执行,以上过程不断循环,导致服务端established状态的连接不断增加并得不到释放。

 

 

 

 

 

 

 

 

 

 

问题处理3:参见上述处理办法2中的处理方式与运维沟通了服务器上的配置,很遗憾和处理办法2中的不一致,怎么办、怎么办,快疯了……革命尚未成功,同志们还得努力啊。最终,在团队的共同努力下给解决了:前面有提到,用的是一主两从的redis哨兵模式,但是出问题的只有一台从机,另一台从机却没有问题,很奇怪对不对,的确也是哈。线上用的服务器都是虚拟机,那好,就将没问题的那台从机克隆下来,去替换有问题的那一台从机,再来观测连接数是否增长(事实证明,OK了),具体步骤:

1、联系运维,沟通此方案,并协助处理。

2、克隆没问题的从机,停掉有问题的从机(不会影响正产的服务)

3、将克隆的从机的ip改为原有问题从机的ip,并将原redis的配置文件拷贝克隆的虚拟机上(如果原从机上有别的服务还得一一还原并启动,幸好我们这台机子上就redis和一个zookeeper的配置,好还原)

4、启动克隆的虚拟机,启动redis-server、redis-sentinel。

5、观测连接数(哈哈哈哈哈,连接数没有增涨了,问题解决了,也解答了先前的疑问,至于原虚拟机为啥有问题呢,好吧,没去深究了,涉及网络、操作系统,太深奥了……………………)。

 

 

 

 

 

 

 

<think>好的,我现在需要帮助用户解决如何用JedisPoolConfig配置Redis哨兵模式的问题。首先,我应该回忆一下Redis哨兵模式的基本概念。哨兵模式用于高可用性,当主节点故障时,自动进行故障转移。用户想通过Java的Jedis库来连接哨兵,所以需要配置连接池参数,并正确初始化连接。 接下来,我得查看用户提供的引用内容,特别是引用[2]和[3],里面提到了Java连接哨兵和集群的示例。引用[4]展示了一个简单的主从复制代码,可能对理解基础连接有帮助。用户需要的是具体的配置步骤和代码示例,所以我要整合这些信息。 首先,JedisPoolConfig的配置参数,比如最大连接数、最大空闲数等,这些是连接池的常规设置,用户可能需要根据实际需求调整。然后,连接哨兵需要用到JedisSentinelPool,这个类需要主节点名称、哨兵节点集合、连接池配置以及密码(如果有的话)。 我需要确保示例代码正确创建了JedisSentinelPool实例,并通过它获取Jedis连接。同时,处理异常和正确关闭资源也很重要。用户可能还关心如何在Spring Boot中配置,所以需要提到RedisTemplate的配置方式,但主要焦点应该在原生的Jedis示例上。 另外,要注意哨兵节点的地址和端口是否正确,主节点名称是否与哨兵配置中的一致。可能还需要处理密码的情况,所以在代码中加入setPassword的调用,但也要说明如果需要密码可以省略。 最后,生成相关问题,帮助用户进一步了解哨兵模式的其他方面,比如故障转移机制、生产环境中的最佳实践等。确保引用标记正确,参考用户提供的引用资料中的相关内容。</think>### 如何配置 JedisPoolConfig 支持 Redis 哨兵模式? 以下是基于 Jedis 连接 Redis 哨兵模式的完整配置步骤和示例代码: --- #### **1. 配置 JedisPoolConfig 参数** ```java JedisPoolConfig poolConfig = new JedisPoolConfig(); poolConfig.setMaxTotal(200); // 最大连接数 poolConfig.setMaxIdle(50); // 最大空闲连接数 poolConfig.setMinIdle(10); // 最小空闲连接数 poolConfig.setTestOnBorrow(true); // 获取连接时测试连通性 poolConfig.setTestOnReturn(true); // 归还连接时测试连通性 ``` --- #### **2. 哨兵模式连接配置** 需要以下参数: - **主节点名称**(如 `mymaster`,需与哨兵配置一致) - **哨兵节点集合**(至少配置两个哨兵地址) - **连接超时时间**(如 2000ms) - **密码**(若 Redis 设置了密码) ```java String masterName = "mymaster"; Set<String> sentinels = new HashSet<>(); sentinels.add("127.0.0.1:26379"); sentinels.add("127.0.0.1:26380"); int timeout = 2000; String password = "your_password"; // 若无密码则设为 null ``` --- #### **3. 创建哨兵连接池** 通过 `JedisSentinelPool` 管理连接: ```java JedisSentinelPool sentinelPool = new JedisSentinelPool( masterName, sentinels, poolConfig, timeout, password ); try (Jedis jedis = sentinelPool.getResource()) { // 执行 Redis 操作 jedis.set("key", "value"); String result = jedis.get("key"); System.out.println(result); // 输出 "value" } catch (Exception e) { e.printStackTrace(); } finally { sentinelPool.close(); // 关闭连接池 } ``` --- #### **4. Spring Boot 集成哨兵模式** 若使用 `RedisTemplate`,需在 `application.yml` 中配置: ```yaml spring: redis: sentinel: master: mymaster nodes: 127.0.0.1:26379,127.0.0.1:26380 password: your_password jedis: pool: max-active: 200 max-idle: 50 min-idle: 10 ``` --- #### **关键注意事项** 1. **主节点名称一致性**:必须与哨兵配置的 `sentinel monitor <master-name>` 一致[^1]。 2. **哨兵节点冗余**:建议至少配置两个哨兵节点以提高可靠性[^2]。 3. **连接泄漏处理**:通过 `try-with-resources` 或手动关闭 `Jedis` 实例,避免连接释放。 4. **异常重试**:在网络波动时,需添加重试逻辑(如连接超时后重试 3 次)。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值