最近公司开了新项目,我负责的内容之一是系统的后端。具体项目内容我就不介绍了,但是用到的技术有些还是很有趣的,值得记录一下。今天介绍的就是其中一个:利用redis的pubsub订阅消息功能做消息队列。



 



对于这个功能本身,还是比较简单的。redis本身支持了publish/subscribe的功能,publish是广播消息,subscribe是订阅消息。服务端使用



publish [channel] [content]



 



发布了一条消息,如果客户端已经提前订阅了这个频道,这个时候就可以收到消息了。订阅的命令也很简单



subscribe [channel]



 



之后客户端就开始进入监听状态了。



 



这个功能用python实现起来也很简单,直接使用redis库就可以。至于基本的使用方法,我就不介绍了,这个随便百度一下就一大片。重点来说说redis里面的pubsub功能——其实也是百度翻到的,写一个辅助类:



 



class RedisSubscriber(object):
    """
    Redis频道订阅辅助类
    """

    def __init__(self, channel):
        self._sentinel = Sentinel(config.RedisConfig.HOST_PORT, password=config.RedisConfig.PASSWORD)
        self.conn = self._sentinel.master_for(config.RedisConfig.MASTER)
        self.channel = channel  # 定义频道名称

    def psubscribe(self):
        """
        订阅方法
        """
        pub = self.conn.pubsub()
        pub.psubscribe(self.channel)  # 同时订阅多个频道,要用psubscribe
        pub.listen()
        return pub



 



这个类里面需要解释的有两个地方:



  • 一是连接方式。使用python连接redis有三种方式:①使用库中的Redis类(或StrictRedis类,其实差不多);②使用ConnectionPool连接池(可保持长连接);③使用Sentinel类(如果有多个redis做集群时,程序会自己选择一个合适的连接)。我项目中的redis就是个集群,所以使用了第三种方式。
  • 二是订阅方法。这里使用的是StrictRedis类中的pubsub方法。连接好之后,可使用subscribe或psubscribe方法来订阅redis消息。其中subscribe是订阅一个频道,psubscribe可订阅多个频道(这样写的时候,作为参数的频道应该是一个列表)。之后就可以开始监听了。

 



接收的地方是这样:



 



def test():
  subscriber = RedisSubscriber([channel1, channel2, ...])
  redis_sub = subscriber.psubscribe()   # 调用订阅方法
 
  while True:
      msg = redis_sub.parse_response(block=False, timeout=60)
      print("收到订阅消息 %s" % msg)



 



注意:



  1. 刚开始监听的时候,会收到一条消息,类似于 [b'psubscribe', b'#你订阅的频道#', 1] 这样。出现了这条消息,说明订阅成功了。
  2. parse_response像这么使用的话,是非阻塞的,如果收不到消息,60秒收不到消息就会返回None。这俩参数可以不加,变成阻塞的。

 



这就完了。



 



这就完了?大多数文章就只是简单的介绍到这里了。但是我在使用的时候发现一个非常恶心的问题: 订阅消息过一段时间后就没动静了。没有任何异常,就是简单的停下了。时间不定,比较常见的是2-4个小时,长的话可能两三天(python群里有位朋友也出现了一毛一样的问题,也是找了很多资料无果)。我也找了很多资料,有的说是redis服务器缓存满了,就断开了,可以通过修改redis-server的缓存大小来解决。可是,这不科学啊!



客户端只是主动连接了服务器,而服务器是不在意的,过段时间发现这个客户端没啥用,就主动断开了。之后,客户端也不会有报错,只是尴尬地订阅着空气。。。



 




python中的rss订阅网站更新是怎么实现的 python redis 订阅发布_后端


这个世界好安静啊!


 


于是我又尝试了各种方法,比如:订阅返回None的时候把订阅取消,重新订阅——不管用;把连接断掉重新建立连接——不管用;随便给redis发一条消息——也不管用。


 


python中的rss订阅网站更新是怎么实现的 python redis 订阅发布_redis_02


 


所以我不开心了。我决定采用比较暴力的方式:redis连接建立后,就开一条线程,每分钟主动给服务器发送一条消息(这就好比你睡觉的时候,有人在你身边,每分钟问你一遍,喂,你还活着吗?)。我在RedisSubscriber这个辅助类里面加了个方法:


 


def keep_alive(self):
        """
        保持客户端长连接
        """
        ka_thread = threading.Thread(target=self._ping)
        ka_thread.start()

    def _ping(self):
        """
        发个消息,刷存在感
        """
        while True:
            time.sleep(60)
            # 尝试向redis-server发一条消息
            if not self.conn.ping():
                print("oops~ redis-server get lost. call him back now!")
                del self._sentinel
                self._sentinel = Sentinel(config.RedisConfig.HOST_PORT, password=config.RedisConfig.PASSWORD)
                self.conn = self._sentinel.master_for(config.RedisConfig.MASTER)


 


然后,在test()中,创建好RedisSubscriber类对象之后,加一句


 


subscriber.keep_alive()


 


就好。