• 使用python来搞定redis的订阅功能


    好久没写博客了。
     
    最近公司开了新项目,我负责的内容之一是系统的后端。具体项目内容我就不介绍了,但是用到的技术有些还是很有趣的,值得记录一下。今天介绍的就是其中一个:利用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的缓存大小来解决。可是,这不科学啊!
    再经过几天的实验和研究,我猜测了这种情况可能发生的原因:客户端只是主动连接了服务器,而服务器是不在意的,过段时间发现这个客户端没啥用,就主动断开了。之后,客户端也不会有报错,只是尴尬地订阅着空气。。。
     
    这个世界好安静啊!
     
    于是我又尝试了各种方法,比如:订阅返回None的时候把订阅取消,重新订阅——不管用;把连接断掉重新建立连接——不管用;随便给redis发一条消息——也不管用。
     
     
    所以我不开心了。我决定采用比较暴力的方式: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()
    就好。
     
    经过了一个礼拜的测试,订阅消息还活着。我想这差不多可以算是我猜对了。暂时当做这个问题解决了吧。
  • 相关阅读:
    lua源码分析 伪索引
    visual studio 插件
    修改Linux内核参数 减少TIME-WAIT
    linux下编译libmysqlclient, 安装mysql-server mysql-client
    编译静态库tinyxml2
    linux下编译lua库
    在Xshell中文件内容显示乱码
    Java中的自增自减
    Integer的缓存机制
    八大基本排序
  • 原文地址:https://www.cnblogs.com/anpengapple/p/7027979.html
Copyright © 2020-2023  润新知