前面我们讲了 Redis 消息队列的使用方法,但是没有提到 Redis 消息队列的不足之处,那就是它不支持消息的多播机制。

消息多播
  消息多播允许生产者生产一次消息,中间件负责将消息复制到多个消息队列,每个消息队列由相应的消费组进行消费。它是分布式系统常用的一种解耦方式,用于将多个消费组的逻辑进行拆分。支持了消息多播,多个消费组的逻辑就可以放到不同的子系统中。

  如果是普通的消息队列,就得将多个不同的消费组逻辑串接起来放在一个子系统中,进
行连续消费。

PubSub
  为了支持消息多播,Redis 不能再依赖于那 5 种基本数据类型了。它单独使用了一个模块来支持消息多播,这个模块的名字叫着 PubSub,也就是 PublisherSubscriber,发布者订阅者模型。我们使用 Python 语言来演示一下 PubSub 如何使用。

# -*- coding: utf-8 -*-
import time
import redis
client = redis.StrictRedis()
p = client.pubsub()
p.subscribe("codehole")
time.sleep(1)
print p.get_message()
client.publish("codehole", "java comes")
time.sleep(1)
print p.get_message()
client.publish("codehole", "python comes")
time.sleep(1)
print p.get_message()
print p.get_message()
{'pattern': None, 'type': 'subscribe', 'channel': 'codehole', 'data': 1L}
{'pattern': None, 'type': 'message', 'channel': 'codehole', 'data': 'java comes'}
{'pattern': None, 'type': 'message', 'channel': 'codehole', 'data': 'python comes'}
None
  客户端发起订阅命令后,Redis 会立即给予一个反馈消息通知订阅成功。因为有网络传输延迟,在 subscribe 命令发出后,需要休眠一会,再通过 get\_message 才能拿到反馈消息。客户端接下来执行发布命令,发布了一条消息。同样因为网络延迟,在 publish 命令发出后,需要休眠一会,再通过 get\_message 才能拿到发布的消息。如果当前没有消息,get\_message 会返回空,告知当前没有消息,所以它不是阻塞的。Redis PubSub 的生产者和消费者是不同的连接,也就是上面这个例子实际上使用了两个Redis 的连接。这是必须的,因为 Redis 不允许连接在 subscribe 等待消息时还要进行其它的操作。

  在生产环境中,我们很少将生产者和消费者放在同一个线程里。如果它们真要在同一个线程里,何必通过中间件来流转,直接使用函数调用就行。所以我们应该将生产者和消费者分离,接下来我们看看分离后的代码要怎么写。

消费者
# -*- coding: utf-8 -*-
import time
import redis
client = redis.StrictRedis()
p = client.pubsub()
p.subscribe("codehole")
while True:
msg = p.get_message()
if not msg:
time.sleep(1)
continue
print msg
生产者
# -*- coding: utf-8 -*-
import redis
client = redis.StrictRedis()
client.publish("codehole", "python comes")
client.publish("codehole", "java comes")
client.publish("codehole", "golang comes")
必须先启动消费者,然后再执行生产者,消费者我们可以启动多个,pubsub 会保证它们
收到的是相同的消息序列。

{'pattern': None, 'type': 'subscribe', 'channel': 'codehole', 'data': 1L}
{'pattern': None, 'type': 'message', 'channel': 'codehole', 'data': 'python comes'}
{'pattern': None, 'type': 'message', 'channel': 'codehole', 'data': 'java comes'}
{'pattern': None, 'type': 'message', 'channel': 'codehole', 'data': 'golang comes'}
我们从消费者的控制台窗口可以看到上面的输出,每个消费者窗口都是同样的输出。第
一行是订阅成功消息,它很快就会输出,后面的三行会在生产者进程执行的时候立即输出。

上面的消费者是通过轮询 get_message 来收取消息的,如果收取不到就休眠 1s。这让我们想起了第 3 节的消息队列模型,我们使用 blpop 来代替休眠来提高消息处理的及时性。PubSub 的消费者如果使用休眠的方式来轮询消息,也会遭遇消息处理不及时的问题。不过我们可以使用 listen 来阻塞监听消息来进行处理,这点同 blpop 原理是一样的。下面我们改造一下消费者

阻塞消费者
# -*- coding: utf-8 -*-
import time
import redis
client = redis.StrictRedis()
p = client.pubsub()
p.subscribe("codehole")
for msg in p.listen():
print msg
代码简短了很多,不需要再休眠了,消息处理也及时了。
模式订阅
上面提到的订阅模式是基于名称订阅的,消费者订阅一个主题是必须明确指定主题的名
称。如果我们想要订阅多个主题,那就 subscribe 多个名称。

> subscribe codehole.image codehole.text codehole.blog # 同时订阅三个主题,会有三条订阅成功反
馈信息
1) "subscribe"
2) "codehole.image"
3) (integer) 1
1) "subscribe"
2) "codehole.text"
3) (integer) 2
1) "subscribe"
2) "codehole.blog"
3) (integer) 3
这样生产者向这三个主题发布的消息,这个消费者都可以接收到。
> publish codehole.image https://www.google.com/dudo.png
(integer) 1
> publish codehole.text " 你好,欢迎加入码洞 "
(integer) 1
> publish codehole.blog '{"content": "hello, everyone", "title": "welcome"}'
(integer) 1
如果现在要增加一个主题 codehole.group,客户端必须也跟着增加一个订阅指令才可以收
到新开主题的消息推送。

为了简化订阅的繁琐,redis 提供了模式订阅功能 Pattern Subscribe,这样就可以一次订
阅多个主题,即使生产者新增加了同模式的主题,消费者也可以立即收到消息
> psubscribe codehole.* # 用模式匹配一次订阅多个主题,主题以 codehole. 字符开头的消息都可
以收到
1) "psubscribe"
2) "codehole.*"
3) (integer) 1
消息结构
前面的消费者消息输出时都是下面的这样一个字典形式
{'pattern': None, 'type': 'subscribe', 'channel': 'codehole', 'data': 1L}
{'pattern': None, 'type': 'message', 'channel': 'codehole', 'data': 'python comes'}
{'pattern': None, 'type': 'message', 'channel': 'codehole', 'data': 'java comes'}
{'pattern': None, 'type': 'message', 'channel': 'codehole', 'data': 'golang comes'}
那这几个字段是什么含义呢?
data 这个毫无疑问就是消息的内容,一个字符串。channel 这个也很明显,它表示当前订阅的主题名称。type 它表示消息的类型,如果是一个普通的消息,那么类型就是 message,如果是控制消息,比如订阅指令的反馈,它的类型就是 subscribe,如果是模式订阅的反馈,它的类型就是 psubscribe,还有取消订阅指令的反馈 unsubscribe 和 punsubscribe。

  pattern 它表示当前消息是使用哪种模式订阅到的,如果是通过 subscribe 指令订阅的,那么这个字段就是空。

PubSub 缺点
  PubSub 的生产者传递过来一个消息,Redis 会直接找到相应的消费者传递过去。如果一
个消费者都没有,那么消息直接丢弃。如果开始有三个消费者,一个消费者突然挂掉了,生产者会继续发送消息,另外两个消费者可以持续收到消息。但是挂掉的消费者重新连上的时候,这断连期间生产者发送的消息,对于这个消费者来说就是彻底丢失了。如果 Redis 停机重启,PubSub 的消息是不会持久化的,毕竟 Redis 宕机就相当于一个消费者都没有,所有的消息直接被丢弃。正是因为 PubSub 有这些缺点,它几乎找不到合适的应用场景。所以 Redis 的作者单独开启了一个项目 Disque 专门用来做多播消息队列。该项目目前没有成熟,一直长期处于Beta 版本,但是相应的客户端 sdk 已经非常丰富了,就待 Redis 作者临门一脚发布一个Release 版本。

redis之PubSub的更多相关文章

  1. redis的发布订阅模式pubsub

    前言 redis支持发布订阅模式,在这个实现中,发送者(发送信息的客户端)不是将信息直接发送给特定的接收者(接收信息的客户端),而是将信息发送给频道(channel),然后由频道将信息转发给所有对这个 ...

  2. python之redis和memcache操作

    Redis 教程 Redis是一个开源(BSD许可),内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理.Redis 是完全开源免费的,遵守BSD协议,是一个高性能的key-value数据 ...

  3. Redis使用总结(3):实现简单的消息队列

    参考Redis实现简单消息队列 Redis提供了两种方式来作消息队列.一个是使用生产者消费模式模式,另外一个方法就是发布订阅者模式.前者会让一个或者多个客户端监听消息队列,一旦消息到达,消费者马上消费 ...

  4. 使用 Redis 实现分布式系统轻量级协调技术

    http://www.ibm.com/developerworks/cn/opensource/os-cn-redis-coordinate/index.html 在分布式系统中,各个进程(本文使用进 ...

  5. Redis 发布/订阅机制原理分析

    Redis 通过 PUBLISH. SUBSCRIBE 和 PSUBSCRIBE 等命令实现发布和订阅功能.   这些命令被广泛用于构建即时通信应用,比如网络聊天室(chatroom)和实时广播.实时 ...

  6. 基于类和redis的监控系统开发

    最近学习python运维开发,编写得一个简单的监控系统,现记录如下,仅供学习参考. 整个程序分为7个部分: 第一个部分根据监控架构设计文档架构如下: .├── m_client│   ├── conf ...

  7. redis-2.6.16源码分析之pub-sub系统

    redis实现的发送订阅系统,即pub-sub,这部分的的代码比较少,也方便分析.在这只将会分析下普通的pub-sub(会忽略掉Pattern-matching subscriptions),以此来简 ...

  8. Redis实现简单消息队列

    http://www.jianshu.com/p/9c04890615ba 任务异步化 打开浏览器,输入地址,按下回车,打开了页面.于是一个HTTP请求(request)就由客户端发送到服务器,服务器 ...

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

    好久没写博客了.   最近公司开了新项目,我负责的内容之一是系统的后端.具体项目内容我就不介绍了,但是用到的技术有些还是很有趣的,值得记录一下.今天介绍的就是其中一个:利用redis的pubsub订阅 ...

随机推荐

  1. HashMap浅析

    一.概述 HashMap,基于哈希结构的Map接口的一个实现,无序,允许null键值对,线程不安全的.可以使用集合工具类Collections中的synchronizedMap方法,去创建一个线程安全 ...

  2. LCX使用心得

    最近在搞内网渗透,碰到 端口转发&边界处理 的时候,我们就可以借助一些小工具了,这类工具有很多,这里主要说明lcx的用法. lcx是个很老的端口转发工具,而它的使用也很简单.不过想要把lcx玩 ...

  3. odoo Botton标签属性详解

    按钮属性 1)icon 按钮图标名,可用的按钮图标在 addons/web/static/src/img/下. 2)string 按钮的显示文字 3)type 动作执行类型.可能值是:workflow ...

  4. class命名归类

    常见class关键词: 布局类:header, footer, container, main, content, aside, page, section 包裹类:wrap, inner 区块类:r ...

  5. 虚拟机VMware14 pro下安装REHL5U11

    1. 创建虚拟磁盘,自定义,磁盘类型选IDE,确保安装系统过程中只有一个物理光盘驱动/ISO镜像: 2. 安装VMware Tools 2.1 虚拟机>安装VMware Tools 2.2 在光 ...

  6. 从壹开始学习NetCore 45 ║ 终于解决了事务问题

    一.项目说明 哈喽,又来写文章了,原来放假可以这么爽,可以学习和分享,

  7. 06-border

    边框 border:边框,描述盒子的边框 边框的三要素:粗细 线性样式 颜色 例如:border:1px solid red: 如果颜色不写,默认是黑色:如果粗细不写,不显示边框:如果只写线性样式,默 ...

  8. Linux Shell 基础知识(二)

    1.本文知识结构 2.文件的查询与检索 2.1. cd 目录切换 找到文件/目录位置:cd 切换到上一个工作目录: cd - 切换到home目录: cd or cd ~ 显示当前路径: pwd 更改当 ...

  9. Java 学习笔记之 父子类Synchronized

    父子类Synchronized: 我们通过一个例子来验证下,父类和子类的Synchronized方法被同时调用,是否是同步的. public class FatherClass { synchroni ...

  10. Kafka 学习笔记之 ZooKeeper作用

    Kafka使用ZooKeeper 配置管理 Leader Election 服务发现 首先进入ZooKeeper客户端: ls / 可以看到有以下节点: 查看Topic 配置信息:体现了ZooKeep ...