不能直接用publish/subscribe做告警系统,因其subscribe会阻塞连接,导致无法同时执行采集与监听;且pub/sub无持久化、无ack、不保证顺序与送达,需配合lpush/redis streams及应用层去重、错误处理等机制才能用于生产环境。

为什么不能直接用 PUBLISH / SUBSCRIBE 做告警系统
因为 Redis 的原生命令 SUBSCRIBE 会阻塞连接,一旦进入订阅模式,该连接就只能收消息,不能再执行 GET、INFO 等其他命令。监控系统需要同时做「采集状态」+「接收告警事件」,单连接无法兼顾。
常见错误现象:用 PHP 或 Python 调用 pubsub().subscribe() 后,后续的 info() 调用一直卡住、超时或抛出 ConnectionError。
- 必须为「监控采集」和「告警监听」分别建立独立连接(或连接池)
- Node-Redis、CSRedis、Predis 等主流客户端都要求显式创建
PubSub实例,而不是复用主客户端 - Python 的
redis-py中,r.pubsub()返回的是新对象,底层新建 socket;PHP 的 Predis 同理需调用$client->getPubSub()
如何让告警消息不丢、不重复、可追溯
Redis Pub/Sub 本身是「发后即焚」机制:消息只推给当前在线的订阅者,断连期间的消息完全丢失,且无 ACK、无重试、无 offset。直接用于生产级告警风险极高。
实际可用的折中方案:
- 用
PUBLISH推送告警事件到频道(如alert:high-cpu),但仅作「实时通知触发器」,不承载完整告警上下文 - 完整告警数据(时间戳、指标值、服务名等)必须写入
LPUSH到一个持久化 list,例如alert_log,供下游轮询或消费 - 订阅端收到 Pub/Sub 消息后,立刻
LPOP对应 list 获取结构化数据,避免竞争条件 - 若需严格有序与回溯,改用 Redis Streams(
XADD/XREAD),它支持消费者组、ACK 和历史消息重放
多服务实例下如何避免告警重复通知
当多个监控 agent 同时订阅同一频道(比如都监听 health:check),又各自触发告警逻辑,会导致同一事件被发多次邮件/短信。
解决方法不是靠「谁先抢到消息」,而是靠协调机制:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 用
SETNX尝试争抢一个带 TTL 的锁(如lock:alert:202607131225),只有抢到锁的实例才真正执行通知 - 所有实例订阅前先
INCR一个计数器(subscribers:alert),再由 leader 实例负责广播结果,其余静默 - 更稳妥的做法:把告警决策逻辑集中到一个服务(如基于 Spring Boot 的告警中心),其他 agent 只
PUBLISH原始事件,由中心统一去重、收敛、分级
注意:PUB/SUB 本身不提供去重能力,任何「避免重复」都得在应用层补足。
Node-Redis 的 error 事件必须监听,否则进程会崩溃
这是 Node.js 环境下最常踩的坑。Node-Redis 客户端未设置 error 监听器时,网络抖动、Redis 重启、认证失败等都会抛出未捕获异常,直接导致 process.exit(1)。
正确写法(TypeScript):
const pubClient = createClient({ url: 'redis://localhost:6379' });
pubClient.on('error', (err) => {
console.error('Redis pub client error:', err);
// 这里可以触发降级逻辑,比如切到本地内存缓存告警队列
});
await pubClient.connect();
const subClient = createClient({ url: 'redis://localhost:6379' });
subClient.on('error', (err) => {
console.error('Redis sub client error:', err);
});
await subClient.connect();
// 必须用 subClient 的 pubsub 实例,不能混用 pubClient
const subscriber = subClient.duplicate();
await subscriber.connect();
subscriber.subscribe('alert:critical', (message) => {
handleAlert(JSON.parse(message));
});
关键点:两个独立 client、各自监听 error、subscribe 必须来自 duplicate() 或全新 client,否则 ready 状态可能错乱。
复杂点在于:Pub/Sub 不是事务性通道,它不保证消息顺序、不保证送达、也不反馈失败。把它当成「轻量广播喇叭」没问题,当成「可靠消息总线」就会出事。










