不推荐用双写策略将 redis pub/sub 迁移至 kafka,因其语义本质不同:pub/sub 是无状态广播,kafka 是有状态日志系统;双写会加剧不一致、时序混乱与消费模型错配。应采用“功能替代+渐进切换”路径,按场景拆解并逐个替换,同步处理位点、幂等、ack 等关键差异。

直接说结论:不推荐用双写策略把 Redis Pub/Sub 平滑迁到 Kafka,因为 Pub/Sub 本身不具备消息可靠性保障能力,双写会放大不一致风险,且无法解决语义差异问题。
Redis Pub/Sub 和 Kafka 的语义根本不同
Pub/Sub 是纯广播、无状态、无持久化的即时通信机制;Kafka 是有分区、有位点、可回溯、带 ACK 的日志型消息系统。两者不是“同一种东西的两个版本”,强行双写等于在两个不同范式之间硬搭桥。
常见错误现象包括:
- 业务误以为
PUBLISH成功 = 消息已送达,但实际下游订阅者离线时消息直接丢弃,而 Kafka 中send()返回成功仅表示进入 broker 队列,后续可能因 ack 设置不当丢失 - 双写时序难控制:先写 Kafka 再
PUBLISH,则旧逻辑收不到事件;先PUBLISH再写 Kafka,则 Kafka 消息可能重复(因 Pub/Sub 无幂等性保障) - 消费模型错配:Pub/Sub 是“所有在线订阅者都收到同一份副本”,Kafka 是“每个 consumer group 独立消费一份流”,迁移后权限、重试、死信逻辑全要重设计
真正可行的过渡路径是“功能替代 + 渐进切换”
不要试图让旧代码同时调用 PUBLISH 和 kafkaProducer.send(),而是按业务场景拆解 Pub/Sub 的实际用途,逐个替换:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 如果用于实时通知类场景(如订单创建后推送给前端 WebSocket 服务),保留 Redis Pub/Sub,新增 Kafka Topic 仅用于下游异步处理(如风控、积分、日志归档),两者并存,互不干扰
- 如果用于服务间解耦(如库存扣减后通知物流),停用
PUBLISH,改由原发布方直接发 Kafka,并在消费者端补全幂等和重试逻辑 —— 这才是 Kafka 的正确打开方式 - 如果用于广播配置变更(如开关刷新),改用 Kafka 的 compact topic +
log.cleaner.policy=compact,配合 consumer 使用seekToBeginning()实现“启动即同步最新值”
关键参数差异:
-
redisTemplate.convertAndSend("order:created", ...)→ 必须替换成kafkaTemplate.send("order-created-topic", key, value),注意key要设为订单 ID 才能保证同一订单路由到同一 partition - Kafka 需显式配置
acks=all和retries=Integer.MAX_VALUE,否则无法替代 Pub/Sub 的“尽力送达”假象 - 旧订阅者代码中
MessageListenerAdapter+PatternTopic要全部下线,改用@KafkaListener(topics = "order-created-topic", groupId = "inventory-consumer")
最容易被忽略的坑:消费者位点与上线节奏
Pub/Sub 没有 offset,Kafka 有。迁移时若直接切流量,新 Kafka 消费者会从 latest 开始读,导致历史事件全部丢失;若从 earliest 开始读,又可能重复处理已执行过的逻辑。
必须做三件事:
- 在 Kafka Topic 中预先写入一个 marker 消息(如
{"type":"MIGRATION_START","ts":1748269876}),并在消费者中跳过该 marker 之前的所有消息 - 旧 Redis 订阅者保持运行至少 72 小时,期间监控 Kafka 消费延迟和业务指标,确认无异常后再下线
- 所有 Kafka 消费者必须实现基于业务主键的幂等写入(如 MySQL
INSERT ... ON DUPLICATE KEY UPDATE或 RedisSET key value NX EX 300)
真正的平滑不在代码双写,而在业务语义对齐和消费者生命周期管理。Pub/Sub 切 Kafka 不是换一个客户端,而是把“广播喇叭”换成“邮局挂号信”,寄信规则、收件地址、签收回执,全都得重新约定。










