目录

1. 前言

2. Python示例

2.1 Python脚本代码

 2.2 运行以及结果

3.  补充说明及遗留问题


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。因为这就像广播一样,你打开收音机之前已经播出的消息你就已经错过去了!

        运行以上脚本后就可以看到各终端上打印的消息如下:

python redis pubsub python redis pubsub Event_python redis pubsub

 

python redis pubsub python redis pubsub Event_python redis pubsub_02

 

python redis pubsub python redis pubsub Event_python_03

        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如预期一样会继续收到后续消息。