您当前的位置:首页 > 电脑百科 > 数据库 > Redis

记一次 Redis 连接问题排查

时间:2023-04-06 13:41:37  来源:  作者:kiritomoe

问题发现

客户端:业务应用使用 lettuce 客户端

服务端:redis server 部署架构采用 1 主 + 1 从 + 3 哨兵

Redis 和业务应用部署在同一个 K8s 集群中,Redis Server 暴露了一个 redis-service,指向到 master 节点,业务应用通过 redis-service 连接 Redis。

某个时刻起,开始发现业务报错,稍加定位,发现是 Redis 访问出了问题,搜索业务应用日志,发现关键信息:

 
org.springframework.data.redis.RedisSystemException: Error in execution; nested exception is io.lettuce.core.RedisCommandExecutionException: READONLY You can't write agAInst a read only replica.

这是一个 Redis 访问的报错,看起来跟 Redis 的读写配置有关。

问题定位

首先排查下业务应用和 Redis 的连接情况

#.NETstat -ano | grep 6379
tcp        0      0 172.24.7.34:44602       10.96.113.219:6379      ESTABLISHED off (0.00/0/0)

其中 172.24.7.34 是业务 pod 的 ip,10.96.113.219 是 redis 的 K8s service ip,连接是 ESTABLISHED 状态,说明连接没有断。

继续排查 Redis 的 pod 是否正常:

 
redis-shareredis-0                           2/2     Running   0
redis-shareredis-1                           2/2     Running   0
redis-shareredis-sentinel-5f7458cd89-7dwpz   2/2     Running   0
redis-shareredis-sentinel-5f7458cd89-rrfz7   2/2     Running   0
redis-shareredis-sentinel-5f7458cd89-xzpmb   2/2     Running   0

无论是读写节点还是哨兵节点,都没有重启过。

既然报了只读节点的异常,索性看下 redis 节点的读写角色情况。

 
root@redis-shareredis-0:/data# redis-cli -h 172.24.1.95 -a xxxx role
1) "slave"
2) "172.24.1.96"
3) (integer) 6379
4) "connected"
5) (integer) 6942040980
root@redis-shareredis-0:/data# redis-cli -h 172.24.1.96 -a xxxx role
1) "master"
2) (integer) 6942173072
3) 1) 1) "172.24.1.95"
      2) "6379"
      3) "6942173072"

可以看到此时 redis-shareredis-0(172.24.1.95)是 slave 节点,redis-shareredis-1(172.24.1.96)是 master 节点。

排查到这里,猜测是业务 pod 实际通过 K8s service 连到了 slave 节点。进入 slave 确认这一信息,发现果然如此,并且 master 节点并没有检查到有该业务 pod 的连接

 
root@redis-shareredis-0:/data# netstat -ano | grep 172.24.7.34:44602
tcp        0      0 172.24.1.95:6379        172.24.7.34:44602       ESTABLISHED keepalive (24.09/0/0)

怀疑是某个时刻开始,master 和 slave 角色发生了互换,而主从切换过程中由于 pod 没有重启,长连接会一直保留着,此时即使 Redis service 的 endpoint 被修正,也不会影响到已有的连接。

图片

为了验证上述猜想,着手排查 Redis server 节点和 sentinel 节点。

查看 Redis 哨兵日志:

 
1:X 03 Feb 2023 06:21:41.357 * +slave slave 172.24.1.96:6379 172.24.1.96 6379 @ mymaster 172.24.1.95 6379
1:X 14 Feb 2023 06:53:27.683 # +reset-master master mymaster 172.24.1.96 6379
1:X 14 Feb 2023 06:53:28.692 * +slave slave 172.24.1.95:6379 172.24.1.95 6379 @ mymaster 172.24.1.96 6379
1:X 14 Feb 2023 06:53:33.271 # +reset-master master mymaster 172.24.1.96 6379

可以看到在 2023/2/14 14:53 (时区+8)时发生了主从切换。

尝试排查主从切换的原因,进到 redis-0 查看日志:

 
1:M 14 Feb 2023 14:53:27.343 # Connection with replica 172.24.1.96:6379 lost.
1:S 14 Feb 2023 14:53:27.616 * Before turning into a replica, using my master parameters to synthesize a cached master: I may be able to synchronize with the new master with just a partial transfer.
1:S 14 Feb 2023 14:53:27.616 * REPLICAOF 172.24.1.96:6379 enabled (user request from 'id=1238496 addr=172.24.1.91:49388 fd=7 name= age=0 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=45 qbuf-free=32723 obl=0 oll=0 omem=0 events=r cmd=slaveof')
1:S 14 Feb 2023 14:53:27.646 * REPLICAOF would result into synchronization with the master we are already connected with. No operation performed.
1:S 14 Feb 2023 14:53:27.670 * REPLICAOF would result into synchronization with the master we are already connected with. No operation performed.
1:S 14 Feb 2023 14:53:28.076 * Connecting to MASTER 172.24.1.96:6379
1:S 14 Feb 2023 14:53:28.076 * MASTER <-> REPLICA sync started
1:S 14 Feb 2023 14:53:28.076 * Non blocking connect for SYNC fired the event.
1:S 14 Feb 2023 14:53:28.076 * Master replied to PING, replication can continue...
1:S 14 Feb 2023 14:53:28.077 * Trying a partial resynchronization (request 816c44412b9008e6969b2fef6401a6cef85fff87:6901666283).
1:S 14 Feb 2023 14:53:28.081 * Full resync from master: 86aa2f4759f73114594586e2e7d2cfbdd1ed2b69:6901664978
1:S 14 Feb 2023 14:53:28.081 * Discarding previously cached master state.
1:S 14 Feb 2023 14:53:28.140 * MASTER <-> REPLICA sync: receiving 1117094 bytes from master
1:S 14 Feb 2023 14:53:28.144 * MASTER <-> REPLICA sync: Flushing old data
1:S 14 Feb 2023 14:53:28.157 * MASTER <-> REPLICA sync: Loading DB in memory
1:S 14 Feb 2023 14:53:28.234 * MASTER <-> REPLICA sync: Finished with success

从日志分析是主从同步时出现了网络分区,导致哨兵进行重新选主,但为什么出现网络分区,就无从得知了,K8s 中两个 pod 之间的通信都能出现 Connection lost 的确挺诡异的。

到这里,问题的根源基本定位清楚了。

问题复盘

无论 Redis 的主从切换是故意的还是不小心,都应当被当做是一个常态,程序需要兼容这类场景。反映出两个问题:

  • 问题一,Redis 使用了哨兵机制,程序应当首选通过哨兵连接 Redis
  • 问题二,Lettuce 客户端没有自动断开错误的连接

那么改进思路自然是有两种,一是改用哨兵连接 Redis,二是替换掉 Lettuce。对于本文遇到的问题,方案一可能可以,但不能确保没有其他极端情况导致其他连接问题,所以我实际采用的是方案二,使用 Jedis 替换掉 Lettuce。

项目一开始采用 Lettuce,主要是因为 spring-boot-data-redis 默认采用了 Lettuce 的实现,尽管我一开始已经留意到搜索引擎中诸多关于 Lettuce 的问题,但实际测试发现,高版本 Lettuce 基本均已修复了这些问题,忽略了特殊场景下其可能存在的风险。简单对比下 Jedis 和 Lettuce:

  • Lettuce:
  • Lettuce 客户端没有连接保活探测,错误连接存在连接池中会造成请求超时报错。
  • Lettuce 客户端未实现 testOnBorrow 等连接池检测方法,无法在使用连接之前进行连接校验。
  • Jedis:
  • Jedis 客户端实现了 testOnBorrow、testWhileIdle、testOnReturn 等连接池校验配置。

    开启 testOnBorrow 在每次借用连接前都会进行连接校验,可靠性最高,但是会影响性能(每次 Redis 请求前会进行探测)。

     

  • testWhileIdle 可以在连接空闲时进行连接检测,合理配置阈值可以及时剔除连接池中的异常连接,防止使用异常连接造成业务报错。

     

  • 在空闲连接检测之前,连接出现问题,可能会造成使用该连接的业务报错,此处可以通过参数控制检测间隔(timeBetweenEvictionRunsMillis)。

     

因此,Jedis 客户端在面对连接异常,网络抖动等场景下的异常处理和检测能力明显强于 Lettuce,可靠性更强。

参数

配置介绍

配置建议

maxTotal

最大连接,单位:个

根据Web容器的Http线程数来进行配置,估算单个Http请求中可能会并行进行的Redis调用次数,例如:Tomcat中的Connector内的maxConnections配置为150,每个Http请求可能会并行执行2个Redis请求,在此之上进行部分预留,则建议配置至少为:150 x 2 + 100= 400限制条件:单个Redis实例的最大连接数。maxTotal和客户端节点数(CCE容器或业务VM数量)数值的乘积要小于单个Redis实例的最大连接数。例如:Redis主备实例配置maxClients为10000,单个客户端maxTotal配置为500,则最大客户端节点数量为20个。

maxIdle

最大空闲连接,单位:个

建议配置为maxTotal一致。

minIdle

最小空闲连接,单位:个

一般来说建议配置为maxTotal的X分之一,例如此处常规配置建议为:100。对于性能敏感的场景,防止经常连接数量抖动造成影响,也可以配置为与maxIdle一致,例如:400。

maxWaitMillis

最大获取连接等待时间,单位:毫秒

获取连接时最大的连接池等待时间,根据单次业务最长容忍的失败时间减去执行命令的超时时间得到建议值。例如:Http最大容忍超时时间为15s,Redis请求的timeout设置为10s,则此处可以配置为5s。

timeout

命令执行超时时间,单位:毫秒

单次执行Redis命令最大可容忍的超时时间,根据业务程序的逻辑进行选择,一般来说处于对网络容错等考虑至少建议配置为210ms以上。特殊的探测逻辑或者环境异常检测等,可以适当调整达到秒级。

minEvictableIdleTimeMillis

空闲连接逐出时间,大于该值的空闲连接一直未被使用则会被释放,单位:毫秒

如果希望系统不会经常对连接进行断链重建,此处可以配置一个较大值(xx分钟),或者此处配置为-1并且搭配空闲连接检测进行定期检测。

timeBetweenEvictionRunsMillis

空闲连接探测时间间隔,单位:毫秒

根据系统的空闲连接数量进行估算,例如系统的空闲连接探测时间配置为30s,则代表每隔30s会对连接进行探测,如果30s内发生异常的连接,经过探测后会进行连接排除。根据连接数的多少进行配置,如果连接数太大,配置时间太短,会造成请求资源浪费。对于几百级别的连接,常规来说建议配置为30s,可以根据系统需要进行动态调整。

testOnBorrow

向资源池借用连接时是否做连接有效性检测(ping),检测到的无效连接将会被移除。

对于业务连接极端敏感的,并且性能可以接受的情况下,可以配置为True,一般来说建议配置为False,启用连接空闲检测。

testWhileIdle

是否在空闲资源监测时通过ping命令监测连接有效性,无效连接将被销毁。

True

testOnReturn

向资源池归还连接时是否做连接有效性检测(ping),检测到无效连接将会被移除。

False

maxAttempts

在JedisCluster模式下,您可以配置maxAttempts参数来定义失败时的重试次数。

建议配置3-5之间,默认配置为5。根据业务接口最大超时时间和单次请求的timeout综合配置,最大配置不建议超过10,否则会造成单次请求处理时间过长,接口请求阻塞。

再次回到本次案例,如果使用了 Jedis,并且配置了合理的连接池策略,可能仍然会存在问题,因为 Jedis 底层检测连接是否可用,使用的是 ping 命令,当连接到只读节点,ping 命令仍然可以工作,所以实际上连接检查机制并不能解决本案例的问题。

但 Jedis 提供了一个 minEvictableIdleTimeMillis 参数,该参数表示一个连接至少停留在 idle 状态的最短时间,然后才能被 idle object evitor 扫描并驱逐,该参数会受到 minIdle 的影响,驱逐到 minIdle 的数量。也就意味着:默认配置 minEvictableIdleTimeMillis=60s,minIdle=0 下,连接在空闲时间达到 60s 时,将会被释放。由于实际的业务场景 Redis 读写空闲达到 60s 的场景是很常见的,所以该方案勉强可以达到在主从切换之后,在较短时间内恢复。但如果 minIdle > 0,这些连接依旧会有问题。而 Lettuce 默认配置下,连接会一直存在。

出于一些不可描述的原因,我无法将应用连接 Redis 的模式切换成哨兵模式,所以最终采取了切换到 Jedis 客户端,并且配置 minIdle=0、minEvictableIdleTimeMillis=60s 的方案。

问题总结

当使用域名/K8s Service 连接 Redis 集群时,需要考虑主从切换时可能存在的问题。Redis 通常使用长连接通信,主从切换时如果连接不断开,会导致无法进行写入操作。可以在客户端、服务端两个层面规避这一问题,以下是一些行之有效的方案:

  • 客户端连接哨兵集群,哨兵会感知到主从切换,并推送给客户端这一变化
  • 客户端配置 minIdle=0,及时断开空闲的连接,可以一定程度规避连接已经不可用但健康检测又检查不出来的场景。(即本文的场景)
  • 服务端主从切换时断开所有已有的连接,依靠客户端的健康检测以及重连等机制,确保连接到正确的节点。

Redis 客户端推荐使用 Jedis 客户端,其在面对连接异常,网络抖动等场景下的异常处理和检测能力明显强于 Lettuce。



Tags: Redis   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
如何使用 Redis 实现消息队列
Redis不仅是一个强大的内存数据存储系统,它还可以用作一个高效的消息队列。消息队列是应用程序间或应用程序内部进行异步通信的一种方式,它允许数据生产者将消息放入队列中,然...【详细内容】
2024-03-22  Search: Redis  点击:(18)  评论:(0)  加入收藏
手动撸一个 Redis 分布式锁
大家好呀,我是楼仔。今天第一天开工,收拾心情,又要开始好好学习,好好工作了。对于使用 Java 的小伙伴,其实我们完全不用手动撸一个分布式锁,直接使用 Redisson 就行。但是因为这些...【详细内容】
2024-02-19  Search: Redis  点击:(40)  评论:(0)  加入收藏
关于 Redis ,这里有你不知道的知识
前言本篇文章不是一篇具体的教程,阿粉打算记录一下自己对Redis的一些思考。说来惭愧,阿粉刚接触Redis的时候只是简单地使用了一下,背了一些面试题,就在简历上写下了Redis这个技...【详细内容】
2023-11-24  Search: Redis  点击:(257)  评论:(0)  加入收藏
巧用 Redis,实现微博 Feed 流功能!
一、背景最近接到一个需求,用一句话来说就是:展示关注人发布的动态,这个涉及到 feed 流系统的设计。本文主要介绍一个一般企业可用的 Feed 流解决方案。二、相关概念下面先介绍...【详细内容】
2023-10-16  Search: Redis  点击:(331)  评论:(0)  加入收藏
为什么单线程的 Redis 能那么快?
今天,我们来探讨一个很多人都很关心的问题:“为什么单线程的 Redis 能那么快?”首先,我要和你厘清一个事实,我们通常说,Redis 是单线程,主要是指 Redis 的网络 IO 和键值对读写是由...【详细内容】
2023-10-15  Search: Redis  点击:(323)  评论:(0)  加入收藏
2 个 .NET 操作的 Redis 客户端类库
Redis ,是一个高性能(NOSQL)的key-value数据库,Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。Redi...【详细内容】
2023-10-11  Search: Redis  点击:(245)  评论:(0)  加入收藏
探索 Redis 与 MySQL 的双写问题
在日常的应用开发中,我们经常会遇到需要使用多种不同类型的数据库管理系统来满足各种业务需求。其中最典型的就是Redis和MySQL的组合使用。这两者拥有各自的优点,例如Redis为...【详细内容】
2023-10-04  Search: Redis  点击:(392)  评论:(0)  加入收藏
学透 Redis HyperLogLog,看这篇就够了
在移动互联网的业务场景中,数据量很大,系统需要保存这样的信息:一个 key 关联了一个数据集合,同时对这个数据集合做统计做一个报表给运营人员看。比如。 统计一个 APP 的日活、...【详细内容】
2023-09-25  Search: Redis  点击:(375)  评论:(0)  加入收藏
一文读懂 Redis 缓存系统
本文介绍了Redis缓存原理、详细解析了缓存模型、缓存一致性和缓存异常场景。尽管(关系型)数据库系统 (SQL) 带来了许多出色的属性,例如 ACID,但为了保持这些属性,数据库的性能在...【详细内容】
2023-09-21  Search: Redis  点击:(273)  评论:(0)  加入收藏
一台服务器上部署 Redis 伪集群
哈喽大家好,我是咸鱼。今天这篇文章介绍如何在一台服务器(以 CentOS 7.9 为例)上通过 redis-trib.rb 工具搭建 Redis cluster (三主三从)。redis-trib.rb 是一个基于 Ruby 编写的...【详细内容】
2023-09-05  Search: Redis  点击:(338)  评论:(0)  加入收藏
▌简易百科推荐
Linux获取Redis 性能指标方法
一、监控指标&Oslash; 性能指标:Performance&Oslash; 内存指标: Memory&Oslash; 基本活动指标:Basic activity&Oslash; 持久性指标: Persistence&Oslash; 错误指标:Error二、监...【详细内容】
2024-04-11  上海天正信息科技有限    Tags:Redis   点击:(2)  评论:(0)  加入收藏
Redis与缓存一致性问题
缓存一致性问题是在使用缓存系统,如Redis时经常遇到的问题。当数据在原始数据源(如数据库)中发生变化时,如何确保缓存中的数据与数据源保持一致,是开发者需要关注的关键问题。一...【详细内容】
2024-04-11  后端Q    Tags:Redis   点击:(2)  评论:(0)  加入收藏
Redis 不再 “开源”,未来采用 SSPLv1 和 RSALv2 许可证
Redis 官方于21日宣布修改开源协议 &mdash;&mdash; 未来所有版本都将使用 “源代码可用” 的许可证 (source-available licenses)。具体来说,Redis 将不再遵循 BSD 3-Clause...【详细内容】
2024-03-27  dbaplus社群    Tags:Redis   点击:(12)  评论:(0)  加入收藏
Redis“叛逃”开源,得罪了几乎所有人
内存数据库供应商Redis近日在开源界砸下了一块“巨石”。Redis即将转向双许可模式,并实施更为严格的许可条款。官方对此次变更的公告直截了当:从Redis 7.4版本开始,Redis将在Re...【详细内容】
2024-03-25    51CTO  Tags:Redis   点击:(10)  评论:(0)  加入收藏
如何使用 Redis 实现消息队列
Redis不仅是一个强大的内存数据存储系统,它还可以用作一个高效的消息队列。消息队列是应用程序间或应用程序内部进行异步通信的一种方式,它允许数据生产者将消息放入队列中,然...【详细内容】
2024-03-22  后端Q  微信公众号  Tags:Redis   点击:(18)  评论:(0)  加入收藏
Redis不再 “开源”
Redis 官方今日宣布修改开源协议 &mdash;&mdash; 未来所有版本都将使用 “源代码可用” 的许可证 (source-available licenses)。具体来说,Redis 将不再遵循 BSD 3-Clause 开...【详细内容】
2024-03-21  OSC开源社区    Tags:Redis   点击:(9)  评论:(0)  加入收藏
在Redis中如何实现分布式锁的防死锁机制?
在Redis中实现分布式锁是一个常见的需求,可以通过使用Redlock算法来防止死锁。Redlock算法是一种基于多个独立Redis实例的分布式锁实现方案,它通过协调多个Redis实例之间的锁...【详细内容】
2024-02-20  编程技术汇    Tags:Redis   点击:(49)  评论:(0)  加入收藏
手动撸一个 Redis 分布式锁
大家好呀,我是楼仔。今天第一天开工,收拾心情,又要开始好好学习,好好工作了。对于使用 Java 的小伙伴,其实我们完全不用手动撸一个分布式锁,直接使用 Redisson 就行。但是因为这些...【详细内容】
2024-02-19  楼仔  微信公众号  Tags:Redis   点击:(40)  评论:(0)  加入收藏
工作中Redis有哪些好用的运维工具
工作中使用 Redis 时,如果大家公司没有专业运维,可能开发人员就会面临这些运维的工作,包括 Redis 的运行状态监控,数据迁移,主从集群、切片集群的部署和运维等等。本文我就从这三...【详细内容】
2024-02-06  waynaqua    Tags:Redis   点击:(56)  评论:(0)  加入收藏
批量执行Redis命令的四种方式!
前言在我们的印象中Redis命令好像都是一个个单条进行执行的,如果有人问你如何批量执行Redis命令,你能回答的上吗,或者说能答出几种方式呢?最容易想到的是Redis的一些批量命令,例...【详细内容】
2024-01-17  小许code  微信公众号  Tags:Redis命令   点击:(60)  评论:(0)  加入收藏
站内最新
站内热门
站内头条