最近遇到一个生产环境问题,排查了比较久,记录一下,方便以后反思。 业务场景:实现每天的考勤打卡提醒,根据配置的规则数据,比如每天提前几分钟,提醒员工打卡,所以会下班前几分钟推送消息到微信公众号,提醒员工,记得打卡考勤
技术实现:会有一个定时任务,每天都会扫描一遍,根据配置的规则,比如提前n分钟提醒考勤打卡,这个过程会计算好需要提前n分钟执行的业务数据,然后放在一个基于Redis发布订阅模式实现的延时队列里,到预定的时间点,延时任务就会执行,发送消息提醒
但是项目运行一段时间后,发现收不到消息,所以需要排查具体原因,通过Arthas一步一步定位问题,发现是封装的Redis延时队列组件出问题,丢到队列里的消息,偶尔会收不到,和公司架构师讨论,解决方法是先修改redis的client-output-buffer-limit
配置,然后修改封装的延时队列组件,比如加上重试机制,保证不会丢失发布订阅消息
延时队列,基于Redis的Pub/Sub
模式实现
package cn.core.common.redis.delayqueue;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import redis.clients.jedis.JedisPubSub;
@Component
@Slf4j
public class KeyExpiredListener extends JedisPubSub {
@Override
public void onPSubscribe(String pattern, int subscribedChannels) {
log.info("onPSubscribe pattern:{} subscribedChannels:{}",pattern,subscribedChannels);
}
@Override
public void onPMessage(String pattern, String channel, String message) {
log.info("onPMessage pattern:{} channel:{} message:{}",pattern,channel,message);
}
}
借助Arthas排查问题,sc -d
命令查看一下JVM加载的类信息,比如获取一下Hutool的SpringUtil
类的信息,为什么获取这个?因为需要根据这个类来getBean
,然后配合ognl
命令来调用延时队列组件里的方法
sc -d cn.hutool.extra.spring.SpringUtil
上面命令在Arthas里执行后,会返回 6dc9d846
,再用ognl
命令,使用表达式来进行动态调用
ognl -c 6dc9d846 '@cn.hutool.extra.spring.SpringUtil@getBean("keyExpiredListener").onPMessage("1","1","test:key:110028")'
通过上面的Arthas动态调用测试,发现这个组件,偶尔能收到消息,偶尔收不到消息,所以通过和架构师讨论和网上查询资料,推测可能是网络带宽或者是生产消息过多超多了Redis的Pub/Sub
的最大限制
Redis为了避免输出缓冲区消息大量堆积的隐患,设置了一些保护机制:
Pub/Sub
客户端,也就是发布/订阅模式,大小限制是8M,当缓冲区超过8M时,会关闭连接Redis 的 client-output-buffer-limit
参数用于设置客户端输出缓冲区的大小限制,以防止慢速客户端消耗过多的内存资源。
参数格式:
client-output-buffer-limit <class> <hard limit> <soft limit> <soft seconds>
class
:客户端类型,可以是 normal
(普通客户端)、slave
(从节点客户端)、pubsub
(发布订阅模式下的客户端)。hard limit
:硬限制,表示输出缓冲区的最大字节数,一旦超过这个限制,Redis 会立即断开客户端的连接。soft limit
和 soft seconds
:软限制,表示在 soft seconds
指定的时间范围内,如果输出缓冲区的大小超过了 soft limit
,则 Redis 会断开客户端的连接。参数配置
redis.conf
配置client-output-buffer-limit pubsub 32mb 8mb 60 #当缓冲区数据达到硬限制32M时,连接会关闭;当缓冲区数据达到软限制每60秒8M时,连接也会关闭
client-output-buffer-limit pubsub 0 0 0 #将hard limit和soft limit同时置0,表示关闭该限制。生产环境不推荐
动态调整客户端的输出缓冲区限制,可以通过 CONFIG SET
命令来修改,可以直接在redis-cli执行命令:
127.0.0.1:0>CONFIG SET client-output-buffer-limit 'pubsub 64mb 32mb 60'
"OK"
127.0.0.1:0>CONFIG GET client-output-buffer-limit
1) "client-output-buffer-limit"
2) "normal 0 0 0 slave 268435456 67108864 60 pubsub 67108864 33554432 60"
监控慢速的客户端,可以使用 CLIENT LIST
命令来查看各个客户端的状态,在输出中,omem
表示该客户端当前使用的输出缓冲区大小。
127.0.0.1:0> CLIENT LIST
"id=3 addr=127.0.0.1:50097 laddr=127.0.0.1:6379 fd=7 name= age=1590 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=26 qbuf-free=32742 argv-mem=10 obl=0 oll=0 omem=0 tot-mem=49816 events=r cmd=client user=default redir=-1
"
通过这些配置,可以有效地控制不同类型客户端的输出缓冲区大小,确保 Redis 服务器的稳定性和性能。可以根据业务情况先修改redis的client-output-buffer-limit
配置,针对这种发布订阅模式,还可以加上重试机制,保证不会丢失发布订阅消息