• 使用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()
    就好。
     
    经过了一个礼拜的测试,订阅消息还活着。我想这差不多可以算是我猜对了。暂时当做这个问题解决了吧。
  • 相关阅读:
    CodeForces 659F Polycarp and Hay
    CodeForces 713C Sonya and Problem Wihtout a Legend
    CodeForces 712D Memory and Scores
    CodeForces 689E Mike and Geometry Problem
    CodeForces 675D Tree Construction
    CodeForces 671A Recycling Bottles
    CodeForces 667C Reberland Linguistics
    CodeForces 672D Robin Hood
    CodeForces 675E Trains and Statistic
    CodeForces 676D Theseus and labyrinth
  • 原文地址:https://www.cnblogs.com/anpengapple/p/7027979.html
Copyright © 2020-2023  润新知