大数据-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. 可靠发送(消息一定会送达)

    • 典型实现:通过持久化存储和确认机制确保
    • 示例:RabbitMQ 的持久化队列 + 生产者确认机制
    • 对比:Redis Pub/Sub 不保证消息一定会送达订阅者
  2. 顺序保证(消息按发送顺序消费)

    • 典型实现:单线程处理或序列号机制
    • 示例:Kafka 分区内的消息顺序保证
    • 对比:Redis Pub/Sub 虽然按顺序发布,但不保证消费者处理顺序
  3. 一次性消费(不重复、不丢失)

    • 典型实现:消费确认和幂等处理
    • 示例:RocketMQ 的事务消息机制
    • 对比:Redis Pub/Sub 无法保证消息只被消费一次

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

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

  1. 瞬时消息特性

    • 如果发布消息时,没有客户端在线订阅该频道,那么这条消息将直接被丢弃;
    • 消息不会进入任何缓冲区或持久化队列
    • 实际场景:突发流量时,新扩容的消费者无法获取扩容前发布的消息
  2. 客户端可靠性问题

    • 如果客户端网络波动或刚好宕机,也会错过消息;
    • 典型场景:移动设备网络切换、服务重启期间发布的消息都会丢失
    • 对比:Kafka 可以保留消息指定时长(如7天),期间随时可以消费

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

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

  1. 单向通知模型

    • Redis 发布后不会等待或接收订阅者的确认;
    • 实际影响:无法实现可靠的"最少一次"投递保证
  2. 发布者盲区

    • 发布端不知道谁接收了消息;
    • 无法统计消息触达率
    • 业务场景:无法确认重要通知是否送达所有相关系统
  3. 无消费者状态跟踪

    • 无法知道哪些消费者处理成功/失败
    • 对比:RabbitMQ 通过消费者ACK机制可以精确控制

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

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

  1. 网络问题不可恢复

    • 网络中断、客户端延迟等不会触发自动重发;
    • 典型故障:跨机房网络闪断导致消息永久丢失
  2. 无死信处理

    • 无法处理持续失败的消息
    • 对比:RabbitMQ 可以配置死信队列进行特殊处理
  3. 无消费者负载保护

    • 消费者处理能力不足时,不会自动调整发送速率
    • 可能导致消费者雪崩
    • 对比:Kafka 通过消费者组机制实现背压控制

Subscribe

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

代码实现如下:

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

Publish

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

shell 复制代码
root@h121:/usr/redis/bin# ./redis-cli
127.0.0.1:6379> publish ch1 hello!
(integer) 1
127.0.0.1:6379> publish ch2 hellworld!
(integer) 1
127.0.0.1:6379> 

观察结果

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

shell 复制代码
127.0.0.1:6379> subscribe ch1 ch2
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "ch1"
3) (integer) 1
1) "subscribe"
2) "ch2"
3) (integer) 2
1) "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

实现代码如下:

shell 复制代码
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set s1 22
QUEUED
127.0.0.1:6379(TX)> hset set1 name wzk
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) (integer) 1
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set s2 22
QUEUED
127.0.0.1:6379(TX)> hset set2 name kangkang
QUEUED
127.0.0.1:6379(TX)> discard
OK
127.0.0.1:6379> exec
(error) ERR EXEC without MULTI
127.0.0.1:6379> 

Redis 弱事务

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

shell 复制代码
127.0.0.1:6379(TX)> set ss 111
QUEUED
127.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 222
QUEUED
127.0.0.1:6379(TX)> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> 

Redis事务为什么不能回滚

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

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

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

    redis 复制代码
    SET name "Alice"
    SADD name "Bob"  # 这里会立即报错,因为SET命令的key不能用于SADD
  • 类型错误:操作了错误的数据类型。比如:

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

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

2. 性能优化考量

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

  1. 版本记录:保存所有被修改键的历史版本
  2. 操作日志:记录事务中的所有操作步骤
  3. 回滚执行:出错时需要逆向执行所有操作

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

  • 内存占用增加(需要保存历史数据)
  • 命令执行时间延长(需要记录额外信息)
  • 系统复杂度提高(需要处理回滚逻辑)

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

实际应用中的替代方案

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

  1. 使用WATCH命令实现乐观锁
  2. Lua脚本保证原子性执行
  3. 开发阶段充分测试避免语法/类型错误
  4. 重要操作前校验数据类型和条件
相关推荐
程序员爱钓鱼1 小时前
Go语言实战案例-创建模型并自动迁移
后端·google·go
javachen__1 小时前
SpringBoot整合P6Spy实现全链路SQL监控
spring boot·后端·sql
一只栖枝5 小时前
华为 HCIE 大数据认证中 Linux 命令行的运用及价值
大数据·linux·运维·华为·华为认证·hcie·it
uzong7 小时前
技术故障复盘模版
后端
GetcharZp7 小时前
基于 Dify + 通义千问的多模态大模型 搭建发票识别 Agent
后端·llm·agent
桦说编程7 小时前
Java 中如何创建不可变类型
java·后端·函数式编程
IT毕设实战小研7 小时前
基于Spring Boot 4s店车辆管理系统 租车管理系统 停车位管理系统 智慧车辆管理系统
java·开发语言·spring boot·后端·spring·毕业设计·课程设计
wyiyiyi8 小时前
【Web后端】Django、flask及其场景——以构建系统原型为例
前端·数据库·后端·python·django·flask
阿华的代码王国9 小时前
【Android】RecyclerView复用CheckBox的异常状态
android·xml·java·前端·后端
Jimmy9 小时前
AI 代理是什么,其有助于我们实现更智能编程
前端·后端·ai编程