目录
1. 前言
Redis pubsub 模块,是一种消息传递系统,实现了消息多播功能,是对设计模式之一的发布订阅者模式的一种实现。
在基于事件的系统中,Pub/Sub是目前广泛使用的通信模型,它采用事件作为基本的通信机制,提供大规模系统所要求的松散耦合的交互模式:订阅者(如客户端)以事件订阅的方式表达出它有兴趣接收的一个事件或一类事件;发布者(如服务器)可将订阅者感兴趣的事件随时通知相关订阅者。
发布者(即消息发送方,Publisher)发送消息,订阅者(即消息接收方, Subsriber)接收消息,而用来传递消息的链路则被称为 channel。在 Redis 中,一个客户端可以订阅任意数量的 channel(可译为频道),也可以在任何channel发布消息。
以下通过简单的python示例实验来直观地理解redis pubsub机制的运作方式。
2. Python示例
以下代码示例是在Windows10/Anconda环境下运行过。当然前提条件下已经安装了redis server,并且已经启动了redis server。关于在Windows下安装和启动redis server可以参考Ref1. 另外还需要安装python redis module(pip install redis即可)。
在以下示例中,我启动了两个publisher(publisher#1和publisher#2)分别从channel1/2和channel3/4发布消息。然后有三个subscriber,subscriber#1从channel1侦听消息,subscriber#2从channel2侦听消息,而subscriber#3从所有4个频道channel1~4侦听消息.
2.1 Python脚本代码
三个Subscriber的python脚本代码分别如下:
# redis_subscriber1.py
import time
import datetime
import redis
r = redis.Redis(host="localhost",port=6379,password='redis123456',decode_responses=True)
r.set("msg","client1 connect to redis_server sucessfully!")
print(r.get("msg"))
ps = r.pubsub()
ps.subscribe('channel1') # Subsribe message from channel1
for item in ps.listen(): # keep listening, and print the message upon the detection of message in the channel
if item['type'] == 'message':
print('Get message from {0}: message = {1}, @{2}'.format(item['channel'], item['data'], datetime.datetime.now()))
# redis_subscribe2.py
import time
import datetime
import redis
r = redis.Redis(host="localhost",port=6379,password='redis123456',decode_responses=True)
#r = redis.Redis(host="192.168.1.67",port=6379,password='redis123456',decode_responses=True) # NG...But why?
r.set("msg","client2 connect to redis_server sucessfully!")
print(r.get("msg"))
ps = r.pubsub()
ps.subscribe('channel2') # Subsribe message from channel1
for item in ps.listen(): # keep listening, and print the message upon the detection of message in the channel
if item['type'] == 'message':
print('Get message from {0}: message = {1}, @{2}'.format(item['channel'], item['data'], datetime.datetime.now()))
# redis_subscrib3.py
import time
import datetime
import redis
r = redis.Redis(host="localhost",port=6379,password='redis123456',decode_responses=True)
r.set("msg","client3 connect to redis_server sucessfully!")
print(r.get("msg"))
ps = r.pubsub()
ps.subscribe('channel1','channel2','channel3','channel4') # Subsribe message from multiple channels
# ps.psubscribe('channel*') # Subsribe message from multiple channels
for item in ps.listen(): # keep listening, and print the message upon the detection of message in the channel
if item['type'] == 'message':
print('Get message from {0}: message = {1}, @{2}'.format(item['channel'], item['data'], datetime.datetime.now()))
两个Publisher的python脚本代码分别如下:
# redis_publish1.py
import time
import datetime
import redis
rc = redis.StrictRedis(host='localhost', port='6379', db=3, password='redis123456',decode_responses=True)
rc.set("msg","publisher#1 connect to redis_server sucessfully!")
print(rc.get("msg"))
for i in range(20):
msg = 'Hello Redis, msg#' + str(2*i+0)
rc.publish("channel1", msg) # publish message to channel1
print('publish#1 to channel1: message = {0} @{1}'.format(msg, datetime.datetime.now()))
time.sleep(1)
msg = 'Hello Redis, msg#' + str(2*i+1)
rc.publish("channel2", msg) # publish message to channel2
print('publish#1 to channel2: message = {0} @{1}'.format(msg, datetime.datetime.now()))
time.sleep(1)
# redis_publish2.py
import time
import datetime
import redis
rc = redis.StrictRedis(host='localhost', port='6379', db=3, password='redis123456',decode_responses=True)
rc.set("msg","publisher#2 connect to redis_server sucessfully!")
print(rc.get("msg"))
for i in range(20):
msg = 'Hello Redis, msg#' + str(2*i+0)
rc.publish("channel3", msg) # publish message to channel1
print('publish#2 to channel3: message = {0} @{1}'.format(msg, datetime.datetime.now()))
time.sleep(1)
msg = 'Hello Redis, msg#' + str(2*i+1)
rc.publish("channel4", msg) # publish message to channel2
print('publish#2 to channel4: message = {0} @{1}'.format(msg, datetime.datetime.now()))
time.sleep(1)
2.2 运行以及结果
然后在5个命令行终端分别启动以上5个脚本,当然如果希望所有的被发布的消息都能够被接收到,你得先启动Subscriber,然后再启动Publisher。因为这就像广播一样,你打开收音机之前已经播出的消息你就已经错过去了!
运行以上脚本后就可以看到各终端上打印的消息如下:
Subscriber1和 Subscriber2的消息接收记录就省略了。
由以上图可知,两个Publisher发布的所有消息都被各Subscriber正确地接收到了。当然发送时间戳和接收时间戳会有细微的差异。毕竟消息的传送处理是需要时间(正如广播电视台发射的信号到达你的收音机也是需要时间一样)
3. 补充说明及遗留问题
关于连接方式,使用python连接redis有三种方式:①使用库中的Redis类(或StrictRedis类,其实差不多);②使用ConnectionPool连接池(可保持长连接);③使用Sentinel类(如果有多个redis做集群时,程序会自己选择一个合适的连接)。以上示例使用的是第一种,关于后两种这里就不做介绍了。
关于订阅方法,以上示例中使用的是StrictRedis类中的pubsub方法。连接好之后,可使用subscribe或psubscribe方法来订阅redis消息。subscribe可以用于监听一个频道(如以上示例中的Subscriber1/2),也可以指定多个频道进行监听(如以上示例中的Subscriber3)。
遗留问题1:据说psubscribe可以用于以模式匹配的方式指定监听所有符合匹配条件的频道,但是我在Subscriber3中做了实验,结果NG,没有收到任何消息。原因不明(唯一已知的原因是我还太菜^-^),待查。
遗留问题2:我在Anaconda Prompt上执行以上脚本。Publisher在发完指定的消息数后就会正常退出。但是Subscribe因为是持续监听,即便Publisher已经退出去后仍然会继续监听。但是问题是我用Ctrl-C也不能让程序终止运行退出,只能用关闭终端窗口这种‘粗暴’的方式。。。感觉很不爽。。。(还是我还太菜^-^)
以上实验中,在Publisher退出去后再次运行后重新发布消息,Subscriber如预期一样会继续收到后续消息。
Ref1: Windows系统安装部署redis服务器