【Redis】发布订阅模型 —— Pub/Sub 原理、消息队列、聊天系统实战

📞Redis 发布订阅模型 ------ Pub/Sub 原理、消息队列、聊天系统实战


🎯 引言

嘿,各位小伙伴,欢迎回到老曹的 Redis课堂!今天我们要聊的是一个非常有趣也非常实用的功能------发布订阅模型(Pub/Sub)。如果你觉得 Redis 只能用来做缓存和存储数据,那你可就太小看它的潜力了!

在这节课中,我们将深入探讨 Redis 的 Pub/Sub 模型是如何工作的,如何用它构建轻量级的消息队列,甚至动手做一个简易的聊天室系统。废话不多说,Let's go!


🎯 学习目标

  • 理解 Redis Pub/Sub 的基本概念与工作机制
  • 掌握 PUBLISHSUBSCRIBEPSUBSCRIBE 等核心命令
  • 实现基于 Redis 的消息队列(Message Queue)
  • 动手搭建一个多用户的实时聊天系统
  • 探讨 Pub/Sub 在生产环境中的优缺点及替代方案

🔍 一、Redis Pub/Sub 基础知识

1.1 什么是发布订阅模式?

发布订阅是一种消息通信范式,允许发送者(Publisher)将消息广播给多个接收者(Subscriber)。这种模式类似于微信公众号:作者发布一篇文章,所有关注该公众号的人都能收到推送。

在 Redis 中:

  • 频道(Channel):相当于"公众号名称"
  • 订阅者(Subscriber):即关注者
  • 发布者(Publisher):就是那个发文章的人

1.2 核心命令一览

命令 作用
SUBSCRIBE channel [channel...] 订阅指定频道
UNSUBSCRIBE [channel [channel...]] 取消订阅
PUBLISH channel message 向频道发送消息
PSUBSCRIBE pattern [pattern...] 按模式匹配订阅多个频道
PUNSUBSCRIBE [pattern [pattern...]] 取消按模式订阅

📌 示例演示:

bash 复制代码
# 客户端 A(订阅者)
127.0.0.1:6379> SUBSCRIBE news
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "news"
3) (integer) 1

# 客户端 B(发布者)
127.0.0.1:6379> PUBLISH news "Hello World!"
(integer) 1

# 客户端 A 收到消息
1) "message"
2) "news"
3) "Hello World!"

🧠 二、工作原理解析(Mermaid 流程图)

下面是 Redis Pub/Sub 的内部流转过程:
Yes
No
Publish 发送消息
Redis Server
是否有订阅者?
转发至所有订阅者
丢弃消息
Client 1
Client 2
Client N...

🔍 关键点:

  • 消息是即时传递的,不会持久化保存。
  • 如果某个客户端断开连接后再重连,默认不会补发之前的消息。
  • Redis 使用内部结构维护频道与订阅者的映射关系。

🧪 三、实战项目①:简易消息队列

虽然 Redis Pub/Sub 不适合做严格意义上的消息队列(缺乏确认机制和持久化),但我们依然可以用它快速实现一个 demo 版 MQ。

步骤说明:

Step 1:创建消费者(Consumer)
python 复制代码
import redis

r = redis.Redis(host='localhost', port=6379, db=0)

def consume_messages(channel):
    pubsub = r.pubsub()
    pubsub.subscribe(channel)
    
    print(f"Listening on channel '{channel}'...")
    for msg in pubsub.listen():
        if msg['type'] == 'message':
            print(f"[Received]: {msg['data'].decode()}")

consume_messages('tasks')
Step 2:创建生产者(Producer)
python 复制代码
import redis
import time

r = redis.Redis(host='localhost', port=6379, db=0)

for i in range(5):
    task = f"Task #{i+1}"
    r.publish('tasks', task)
    print(f"[Sent]: {task}")
    time.sleep(1)

🚀 运行效果:

shell 复制代码
[Listening on channel 'tasks'...]
[Sent]: Task #1
[Received]: Task #1
[Sent]: Task #2
[Received]: Task #2
...

🎯 小贴士:

这种方法非常适合临时性的异步通知场景,比如邮件提醒、日志收集等。


💬 四、实战项目②:多人在线聊天室

现在我们升级一下难度,尝试做一个支持多用户实时交流的聊天室系统!

架构设计

  • 使用 WebSocket 协议建立长连接
  • 后端采用 Python + Flask-SocketIO
  • 利用 Redis Pub/Sub 实现实时广播

核心代码片段

服务端(Flask + SocketIO)
python 复制代码
from flask import Flask, render_template
from flask_socketio import SocketIO, emit, join_room, leave_room
import redis

app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app)
r = redis.Redis()

@app.route('/')
def index():
    return render_template('chat.html')

@socketio.on('join')
def handle_join(data):
    username = data['username']
    room = data['room']
    join_room(room)
    emit('status', {'msg': f'{username} has entered the room.'}, room=room)

@socketio.on('send_message')
def handle_send_message(data):
    room = data['room']
    msg = data['msg']
    r.publish(f'room:{room}', msg)  # 广播到 Redis 频道
    emit('receive_message', {'msg': msg}, room=room)

if __name__ == '__main__':
    socketio.run(app, debug=True)
前端页面(HTML + JS)
html 复制代码
<!-- chat.html -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.0/socket.io.js"></script>
<script>
const socket = io();

document.getElementById('joinBtn').onclick = () => {
    const username = prompt("Enter your name:");
    const room = prompt("Enter room ID:");
    socket.emit('join', { username, room });
};

document.getElementById('sendBtn').onclick = () => {
    const input = document.getElementById('messageInput');
    socket.emit('send_message', {
        room: currentRoom,
        msg: input.value
    });
    input.value = '';
};
</script>

💡 效果预览:

当你打开多个浏览器窗口分别输入不同用户名进入同一房间时,就能看到文字实时跳动的效果啦!


⚠️ 五、Pub/Sub 的局限性和改进方向

5.1 主要缺陷

缺陷项 描述
❌ 无持久化 断线期间的消息丢失
❌ 无 ACK 机制 无法确认消息是否送达
❌ 内存占用高 大量活跃订阅会影响性能

5.2 生产环境推荐做法

  • 对可靠性要求高的场景 → 使用 RabbitMQ/Kafka
  • 需要历史消息追溯 → 自行扩展 Redis,记录每条消息到数据库
  • 控制订阅数量 → 添加权限校验和限流措施

📊 六、高频面试题汇总

序号 问题 回答要点
Q1 Redis Pub/Sub 与普通 List 实现消息队列有何区别? Pub/Sub 是广播式,List 是拉取式
Q2 是否可以使用通配符订阅多个频道? 可以,使用 PSUBSCRIBE 命令
Q3 如何优雅地关闭一个订阅连接? 调用 UNSUBSCRIBE 或直接断开连接
Q4 消费速度跟不上生产速度会怎样? 新消息覆盖旧消息,容易导致积压
Q5 Redis Pub/Sub 最大支持多少并发订阅? 理论上受操作系统文件句柄限制
Q6 能否在一个客户端同时既是发布者又是订阅者? 当然可以
Q7 消息是否会重复投递? 不会,除非网络抖动造成重试
Q8 如何检测某频道是否存在订阅者? 使用 PUBSUB NUMSUB 命令
Q9 Redis 集群环境下 Pub/Sub 行为如何变化? 所有节点共享全局订阅状态
Q10 是否可以在 Lua 脚本中调用 PUBLISH? 不建议这么做,可能导致阻塞

🎉 七、结语 & 下一步预告

今天我们完成了 Redis Pub/Sub 的全面学习,从基础理论到实战演练应有尽有。相信你现在对这个功能已经有了深刻的理解!

下一节我们将聚焦于 Pipeline 技术,教你如何大幅提升 Redis 的吞吐量。记得准时回来哦~


相关推荐
SQL必知必会1 小时前
使用 SQL 构建转化漏斗
数据库·sql·数据分析
丿BAIKAL巛2 小时前
Docker部署的Mysql数据库自动化备份
数据库·mysql·docker
爬山算法2 小时前
MongoDB(11)MongoDB的默认端口号是多少?
数据库·mongodb
betazhou2 小时前
Mongodb日志类型以及日志轮转
数据库·mongodb
一次旅行2 小时前
接口自动化测试模板
数据库·python·pytest
广州华水科技2 小时前
单北斗GNSS变形监测系统应用与安装指南
前端
coding随想2 小时前
深入Modernizr源码:揭秘CSS伪类检测的底层逻辑
前端·css
奋斗吧程序媛2 小时前
vue3初体验(1)
前端·javascript·vue.js
brucelee1862 小时前
AWS IoT Core + Lambda + DynamoDB + Redis 完整教程(Java JDK21)
redis·物联网·aws