好久没写博客了。
 
最近公司开了新项目,我负责的内容之一是系统的后端。具体项目内容我就不介绍了,但是用到的技术有些还是很有趣的,值得记录一下。今天介绍的就是其中一个:利用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()
就好。
 
经过了一个礼拜的测试,订阅消息还活着。我想这差不多可以算是我猜对了。暂时当做这个问题解决了吧。

使用python来搞定redis的订阅功能的更多相关文章

  1. python+mitmproxy抓包过滤+redis消息订阅+websocket实时消息发送,日志实时输出到web界面

    本实例实现需求 在游戏SDK测试中,经常需要测试游戏中SDK的埋点日志是否接入正确.本实例通过抓包(客户端http/https 请求)来判定埋点日志是是否接入正确. 实现细节:使用django项目,后 ...

  2. 一篇文章搞定redis

    Redis 简介 Redis 是完全开源免费的,遵守 BSD 协议,是一个高性能的 key - value 数据库 Redis 与 其他 key - value 缓存产品有以下三个特点: Redis ...

  3. 20 行代码:Serverless 架构下用 Python 轻松搞定图像分类和预测

    作者 | 江昱 前言 图像分类是人工智能领域的一个热门话题.通俗解释就是,根据各自在图像信息中所反映的不同特征,把不同类别的目标区分开来的图像处理方法. 它利用计算机对图像进行定量分析,把图像或图像中 ...

  4. STM32F103C8T6最小板搞定CMSIS-DAP和SWO功能

    转载:http://www.stmcu.org.cn/module/forum/forum.php?mod=viewthread&tid=616081&extra=page%3D&am ...

  5. 一篇博客搞定redis基础

    redis简介 redis 一款高性能key-value数据库,实际上多用作缓存队列或者消息分发(celery),但是最常常被用来做缓存. redis安装 源码安装 $ wget http://dow ...

  6. Redis-用思维导图二天搞定Redis用法。

    Redis整体面貌 Redis基本数据结构 1.String 1.1 数据结构 long len byte数组长度 long free 可用数组长度 char buff[] 数据内容 1.2 命令 键 ...

  7. 手把手教你使用Python轻松搞定发邮件

    前言 现在生活节奏加快,人们之间交流方式也有了天差地别,为了更加便捷的交流沟通,电子邮件产生了,众所周知,电子邮件其实就是客户端和服务器端发送接受数据一样,他有一个发信和一个收信的功能,电子邮件的通信 ...

  8. 掘地三尺搞定 Redis 与 MySQL 数据一致性问题

    Redis 拥有高性能的数据读写功能,被我们广泛用在缓存场景,一是能提高业务系统的性能,二是为数据库抵挡了高并发的流量请求,点我 -> 解密 Redis 为什么这么快的秘密. 把 Redis 作 ...

  9. 一行 Python 代码搞定一棵树

    使用 Python 内建的 defaultdict 方法可以轻松定义一个树的数据结构. 简单的说树也可以是一个字典数据结构           Python   1 def tree(): retur ...

随机推荐

  1. 解决 windows npm ERR! asyncWrite is not a function 问题

    重装过node,cmd中 node -v,npm -v 提示版本都没有问题,但是在vue项目中npm i 的时候出现了npm ERR! asyncWrite is not a function 问题, ...

  2. PHP中关于时间(戳)、时区、本地时间、UTC时间等的梳理

    PHP中关于时间(戳).时区.本地时间.UTC时间等的梳理 在PHP开发中,我们经常会在时间问题上被搞糊涂,比如我们希望显示一个北京时间,但是当我们使用date函数进行输出时,却发现少了8个小时.几乎 ...

  3. __block和__weak修饰符的区别

    block下循环引用的问题 __block本身并不能避免循环引用,避免循环引用需要在block内部把__block修饰的obj置为nil __weak可以避免循环引用,但是其会导致外部对象释放了之后, ...

  4. 二进制GCD算法 减少%的时间消耗

    /* 二进制求最大公约数.由于传统的GCD,使用了%,在计算机运行过程中要花费大量的时间,所以,采取二进制的求法,来减少时间的消耗. 算法: 当a,b都是偶数时: gcd(a,b)=2*gcd(a/2 ...

  5. Mysql-安装指南

    1.设置用户名密码 首次登录后修改密码如下: 如果密码设置太过简单会报以下错误 mysql修改密码Your password does not satisfy the current policy r ...

  6. PHP中文件操作(2)-- 写文件

    1.fread — 读取文件(可安全用于二进制文件) 语法:string fread ( int $handle , int $length ) $filename = "aa.txt&qu ...

  7. java注册后缀样式(ajax提示)

    方法位置 <FORM id=form1 name=form1 action = "${pageContext.request.contextPath }/user_regist.act ...

  8. 增加图例 Legend和删除图例

    private void button1_Click(object sender, System.EventArgs e) { //Get the GraphicsContainer IGraphic ...

  9. syscall to rop

    前言 hitcon 2017 的 start 题,比较简单,练练手. 题目链接: https://gitee.com/hac425/blog_data/tree/master/hitcon2017 正 ...

  10. 润乾报表JSF FORM 标签中使用填报表解决方案

     需求 润乾报表的标签需要在jsf页面中和其他填报控件一起使用,润乾报表负责展现录入部分数据,并且这部分数据和页面的其它控件的数据存在前台和后台的交互. 问题 润乾报表在前台会生成自己的form, ...