写点什么

大数据 -42 Redis 发布 / 订阅详解:机制、弱事务性与实际风险分析

作者:武子康
  • 2025-07-16
    美国
  • 本文字数:3410 字

    阅读完需:约 11 分钟

大数据-42 Redis 发布/订阅详解:机制、弱事务性与实际风险分析

点一下关注吧!!!非常感谢!!持续更新!!!

🚀 AI 篇持续更新中!(长期更新)

AI 炼丹日志-30-新发布【1T 万亿】参数量大模型!Kimi‑K2 开源大模型解读与实践,持续打造实用 AI 工具指南!📐🤖

💻 Java 篇正式开启!(300 篇)

目前 2025 年 07 月 16 日更新到:Java-74 深入浅出 RPC Dubbo Admin 可视化管理 安装使用 源码编译、Docker 启动 MyBatis 已完结,Spring 已完结,Nginx 已完结,Tomcat 已完结,分布式服务正在更新!深入浅出助你打牢基础!

📊 大数据板块已完成多项干货更新(300 篇):

包括 Hadoop、Hive、Kafka、Flink、ClickHouse、Elasticsearch 等二十余项核心组件,覆盖离线+实时数仓全栈!大数据-278 Spark MLib - 基础介绍 机器学习算法 梯度提升树 GBDT 案例 详解


章节内容

上一节我们完成了如下的内容:


  • bitmap 位操作类型

  • geo 空间类型 空间计算 Z 阶曲线 Base32 编码

  • Stream 类型 消息队列

背景介绍

这里是三台公网云服务器,每台 2C4G,搭建一个大数据的学习环境,供我学习。


  • 2C4G 编号 h121

  • 2C4G 编号 h122

  • 2C2G 编号 h123


发布订阅

基本简介

Redis 提供了订阅发布的功能,可以用于消息的传输



  • 发布者订阅者都是Redis的客户端Channel则为Redis的服务端

  • 发布者将消息发送到某个频道,订阅了这个频道的订阅者就能收到这条消息。

基本概念

Redis 的 Pub/Sub 模式是一种消息通信机制,允许客户端订阅某些频道(channel),然后由其他客户端向这些频道发布消息。发布的消息会实时推送到所有订阅者。


  • PUBLISH channel message:向某个频道发送消息。

  • SUBSCRIBE channel1 channel2 …:订阅一个或多个频道。

  • PSUBSCRIBE pattern:按模式订阅,支持通配符。

工作机制

  • Redis 不会持久化消息(默认情况下),也不会缓存历史消息;

  • 一旦消息发布,只有当前订阅的客户端才能收到;

  • 消息传递是即发即弃(fire-and-forget),没有重试机制;

  • 客户端断连就会自动取消订阅。

适用场景

  • 实时通知(如聊天室、系统事件推送)

  • 日志流分发

  • 简单的数据总线(不推荐用于复杂业务)

什么是“弱事务性”?

在分布式系统或消息队列中,事务性可以分为强事务性和弱事务性两种类型。强事务性(Strong Transactionality) 意味着要严格保证以下三性:


  1. 可靠发送(消息一定会送达)

  2. 典型实现:通过持久化存储和确认机制确保

  3. 示例:RabbitMQ 的持久化队列 + 生产者确认机制

  4. 对比:Redis Pub/Sub 不保证消息一定会送达订阅者

  5. 顺序保证(消息按发送顺序消费)

  6. 典型实现:单线程处理或序列号机制

  7. 示例:Kafka 分区内的消息顺序保证

  8. 对比:Redis Pub/Sub 虽然按顺序发布,但不保证消费者处理顺序

  9. 一次性消费(不重复、不丢失)

  10. 典型实现:消费确认和幂等处理

  11. 示例:RocketMQ 的事务消息机制

  12. 对比:Redis Pub/Sub 无法保证消息只被消费一次

不持久化消息 —— 有丢失风险

Redis Pub/Sub 采用内存存储模式,这种设计带来了几个显著问题:


  1. 瞬时消息特性

  2. 如果发布消息时,没有客户端在线订阅该频道,那么这条消息将直接被丢弃;

  3. 消息不会进入任何缓冲区或持久化队列

  4. 实际场景:突发流量时,新扩容的消费者无法获取扩容前发布的消息

  5. 客户端可靠性问题

  6. 如果客户端网络波动或刚好宕机,也会错过消息;

  7. 典型场景:移动设备网络切换、服务重启期间发布的消息都会丢失

  8. 对比:Kafka 可以保留消息指定时长(如 7 天),期间随时可以消费

不确认机制 —— 无法确认消息已送达

Redis Pub/Sub 的确认机制存在以下缺陷:


  1. 单向通知模型

  2. Redis 发布后不会等待或接收订阅者的确认;

  3. 实际影响:无法实现可靠的"最少一次"投递保证

  4. 发布者盲区

  5. 发布端不知道谁接收了消息;

  6. 无法统计消息触达率

  7. 业务场景:无法确认重要通知是否送达所有相关系统

  8. 无消费者状态跟踪

  9. 无法知道哪些消费者处理成功/失败

  10. 对比:RabbitMQ 通过消费者 ACK 机制可以精确控制

无重试机制 —— 不可恢复传输失败

Redis Pub/Sub 在可靠性方面缺失的关键能力:


  1. 网络问题不可恢复

  2. 网络中断、客户端延迟等不会触发自动重发;

  3. 典型故障:跨机房网络闪断导致消息永久丢失

  4. 无死信处理

  5. 无法处理持续失败的消息

  6. 对比:RabbitMQ 可以配置死信队列进行特殊处理

  7. 无消费者负载保护

  8. 消费者处理能力不足时,不会自动调整发送速率

  9. 可能导致消费者雪崩

  10. 对比:Kafka 通过消费者组机制实现背压控制

Subscribe

Redis 客户端 1:订阅频道 1、订阅频道 2 执行完之后,程序会阻塞住。


代码实现如下:


root@h121:/usr/redis/bin# ./redis-cli127.0.0.1:6379> subscribe ch1 ch2Reading messages... (press Ctrl-C to quit)1) "subscribe"2) "ch1"3) (integer) 11) "subscribe"2) "ch2"3) (integer) 2
复制代码

Publish

Redis 客户端 2:将消息发布到频道 1、频道 2


root@h121:/usr/redis/bin# ./redis-cli127.0.0.1:6379> publish ch1 hello!(integer) 1127.0.0.1:6379> publish ch2 hellworld!(integer) 1127.0.0.1:6379> 
复制代码

观察结果

此时查看客户端 1,可以看到消息已经拿到了:


127.0.0.1:6379> subscribe ch1 ch2Reading messages... (press Ctrl-C to quit)1) "subscribe"2) "ch1"3) (integer) 11) "subscribe"2) "ch2"3) (integer) 21) "message"2) "ch1"3) "hello!"1) "message"2) "ch2"3) "hellworld!"
复制代码

使用场景

  • 哨兵模式 哨兵之间通过发布与订阅的方式与 Redis 主服务器和从服务器进行通信。

  • Redisson 是一个分布式框架,分布式锁释放时,是使用发布订阅的方式。

事务相关

事务(Transaction),是指为单个逻辑工作单元执行一系列的操作。

ACID

  • Atomic(原子性):构成事务所有操作必须是一个逻辑单元,要么全部执行,要么全部失败。

  • Consistency(一致性):数据库在事务执行前后状态都必须是稳定的或者一致的

  • Isolation(隔离性):事务之间不会互相影响

  • Durability(持久性):事务执行成功后必须全部写入磁盘

Redis 事务

  • Redis事务通过multi、exec、discard、watch四个命令来完成的

  • Redis单个命令都是原子性的,所以这里需要确保事务性的对象是命令集合

  • Redis命令集合序列化并确保处于同一个事务的命令集合连续且不被打断的执行

  • Redis不支持回滚操作

事务相关指令

  • multi:用于标记事务块的开始,Redis 会将后续的命令逐个放入队列中,然后使用 exec 原子化执行这个命令队列。

  • exec:执行命令队列

  • discard:清除命令队列

  • watch:监视 key

  • unwatch:清除监视 key



实现代码如下:


127.0.0.1:6379> multiOK127.0.0.1:6379(TX)> set s1 22QUEUED127.0.0.1:6379(TX)> hset set1 name wzkQUEUED127.0.0.1:6379(TX)> exec1) OK2) (integer) 1127.0.0.1:6379> multiOK127.0.0.1:6379(TX)> set s2 22QUEUED127.0.0.1:6379(TX)> hset set2 name kangkangQUEUED127.0.0.1:6379(TX)> discardOK127.0.0.1:6379> exec(error) ERR EXEC without MULTI127.0.0.1:6379> 
复制代码

Redis 弱事务

Redis 语法错误时,会导致整个任务的命令在队列里都清除


127.0.0.1:6379(TX)> set ss 111QUEUED127.0.0.1:6379(TX)> seta cuowu(error) ERR unknown command `seta`, with args beginning with: `cuowu`, 127.0.0.1:6379(TX)> set m2 222QUEUED127.0.0.1:6379(TX)> exec(error) EXECABORT Transaction discarded because of previous errors.127.0.0.1:6379> 
复制代码

Redis 事务为什么不能回滚

Redis 事务不支持传统意义上的回滚机制,主要基于以下两个核心原因:

1. 开发阶段可预见的错误类型

  • 语法错误:在命令入队阶段就能被发现。例如输入了不存在的 Redis 命令:


  SET name "Alice"  SADD name "Bob"  # 这里会立即报错,因为SET命令的key不能用于SADD
复制代码


  • 类型错误:操作了错误的数据类型。比如:


  LPUSH mylist "value1"  SADD mylist "value2"  # 对列表类型执行集合操作
复制代码


这些错误在开发测试阶段就能被发现并修正,不需要依赖运行时的事务回滚机制。Redis 采用这种设计理念是认为生产环境应该只运行经过充分测试的正确命令。

2. 性能优化考量

实现完整的事务回滚机制需要:


  1. 版本记录:保存所有被修改键的历史版本

  2. 操作日志:记录事务中的所有操作步骤

  3. 回滚执行:出错时需要逆向执行所有操作


这些功能会带来显著的性能开销:


  • 内存占用增加(需要保存历史数据)

  • 命令执行时间延长(需要记录额外信息)

  • 系统复杂度提高(需要处理回滚逻辑)


Redis 作为内存数据库,追求极致的性能和简单性,因此选择不实现完整的事务回滚功能。这种设计权衡在 Redis 的典型使用场景(高速缓存、计数器等)中是合理的。

实际应用中的替代方案

虽然不能回滚,但可以通过以下方式保证数据一致性:


  1. 使用 WATCH 命令实现乐观锁

  2. Lua 脚本保证原子性执行

  3. 开发阶段充分测试避免语法/类型错误

  4. 重要操作前校验数据类型和条件

发布于: 刚刚阅读数: 3
用户头像

武子康

关注

永远好奇 无限进步 2019-04-14 加入

Hi, I'm Zikang,好奇心驱动的探索者 | INTJ / INFJ 我热爱探索一切值得深究的事物。对技术、成长、效率、认知、人生有着持续的好奇心和行动力。 坚信「飞轮效应」,相信每一次微小的积累,终将带来深远的改变。

评论

发布
暂无评论
大数据-42 Redis 发布/订阅详解:机制、弱事务性与实际风险分析_Java_武子康_InfoQ写作社区