1.何为Redis发布订阅?
是一种消息通信模式,允许发布者直接把消息发布到某个频道上面,而不是直接发给接受者,然后订阅了这个频道的订阅者都可以接受消息
2.如何使用
先建立一个send.py文件(发布者)代码如下
import redis # 安装
def publish_message(channel, message):
r = redis.Redis(host='localhost', port=6379, db=0) 实例化
r.publish(channel, message) 发布信息到频道
print(f"Message '{message}' published to channel '{channel}'")
if __name__ == "__main__":
channel = "test_channel"
message = "Hello, Redis!"
publish_message(channel, message)
在建立一个recv的文件(订阅者)
import redis
def message_handler(message):
print(f"Received message: {message['data'].decode('utf-8')}")
def subscribe_to_channel(channel):
r = redis.Redis(host='localhost', port=6379, db=0) # 创建 Redis 客户端连接
pubsub = r.pubsub() # 创建 Pub/Sub 对象
pubsub.subscribe(**{channel: message_handler}) # 订阅频道,并指定消息处理函数
print(f"Subscribed to channel '{channel}'")
# 无限循环监听订阅的频道
for message in pubsub.listen():
if message['type'] == 'message': # 检查消息类型是否为 'message'
message_handler(message) # 调用消息处理函数处理收到的消息
if __name__ == "__main__":
channel = "test_channel"
subscribe_to_channel(channel)
注意点
pubsub.listen()
是一个生成器方法,会持续等待和接收新消息,因此需要使用无限循环来处理这些消息。- 使用无限循环可以确保订阅者持续监听频道,并处理所有收到的消息。
- 处理可能的连接中断可以提高系统的可靠性,确保订阅者在连接中断后能够重新连接并继续接收消息。
- 当发送方的代码send.py运行之后关闭,发布者不再发布消息,订阅者的
for message in pubsub.listen()
循环将不会退出。pubsub.listen()
会持续运行并等待新消息,即使没有新的消息发布。这个循环只有在连接出现问题(如网络连接断开)或程序显式终止时才会退出。如果你想显示的退出循环,你可以自己加条件判断退出循环 -
那请问这个发布信息到频道,这个频道在Redis数据库中可见吗? 在 Redis 中,频道并不像键值对那样存储在数据库中,因此它们在 Redis 的键空间中不可见。当你发布消息到一个频道时,Redis 不会在数据库中持久化存储这些频道或消息。相反,发布/订阅机制是一个内存中的消息传递系统,频道和消息在内存中暂时存在。
-
那会不会存在发布者发布信息的时候,数据丢失的情况?在 Redis 发布/订阅(Pub/Sub)模式下,如果没有订阅者,发布者发布的信息会被丢弃。这是因为 Redis 的 Pub/Sub 是一种即时消息传递机制,不会对消息进行持久化存储。因此,如果在消息发布时没有订阅者接收,消息将无法传递到任何客户端并且会丢失。
-
那怎么解决这种丢失的情况呢?
- 使用 Redis 列表:将消息存储在 Redis 列表中,订阅者可以从列表中消费消息。这种方法可以确保消息不会丢失,因为消息被存储在 Redis 中,订阅者可以随时读取。
- 使用 Redis 流(Streams):Redis Streams 提供了一种持久化消息的机制,支持发布/订阅和消息队列的功能。消息会存储在流中,消费者可以读取和确认消息。
- 使用消息队列:使用专门的消息队列系统,如 RabbitMQ、Apache Kafka 等,这些系统设计用于高可靠性消息传递,支持消息持久化和确认机制。
示例:使用 Redis 列表
发布者
import redis
def publish_message(channel, message):
r = redis.Redis(host='localhost', port=6379, db=0)
r.rpush(channel, message) # 将消息推送到列表中
print(f"Message '{message}' added to channel '{channel}'")
if __name__ == "__main__":
channel = "test_channel"
message = "Hello, Redis!"
publish_message(channel, message)
订阅者
import redis
import time
def consume_messages(channel):
r = redis.Redis(host='localhost', port=6379, db=0)
while True:
message = r.blpop(channel, timeout=0) # 阻塞等待消息
if message:
print(f"Received message: {message[1].decode('utf-8')}")
if __name__ == "__main__":
channel = "test_channel"
consume_messages(channel)
示例:使用 Redis Streams
发布者
import redis
def publish_message(stream, message):
r = redis.Redis(host='localhost', port=6379, db=0)
r.xadd(stream, {"message": message}) # 将消息添加到流中
print(f"Message '{message}' added to stream '{stream}'")
if __name__ == "__main__":
stream = "test_stream"
message = "Hello, Redis Streams!"
publish_message(stream, message)
订阅者
import redis
import time
def consume_messages(stream):
r = redis.Redis(host='localhost', port=6379, db=0)
last_id = '0' # 从最早的消息开始读取
while True:
messages = r.xread({stream: last_id}, count=1, block=0)
if messages:
for stream_name, message_list in messages:
for message_id, message in message_list:
print(f"Received message: {message[b'message'].decode('utf-8')}")
last_id = message_id # 更新最后读取的消息ID
if __name__ == "__main__":
stream = "test_stream"
consume_messages(stream)
- Redis 通过维护一个内部数据结构来跟踪每个频道的订阅者数量。这个具体是在哪维护的呢
Redis 内部数据结构
在 Redis 的内部,使用了一些哈希表来跟踪订阅者和频道。这些数据结构定义在 Redis 源代码中,具体来说,它们主要包括以下几个:
pubsub_channels
:这是一个哈希表,键是频道名,值是订阅该频道的客户端列表。pubsub_patterns
:这是一个链表,存储了所有的模式订阅信息。
- 常用场景
- 实时聊天应用 实时通知系(系统事件通知、警报通知) 金融市场数据、物联网(IoT)数据处理。微服务间的事件通知和通信 商品上新,实时游戏状态同步 直播弹幕 等