百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

Redis的发布/订阅工作模式详解(redis发布订阅常用命令)

mhr18 2024-10-29 14:34 18 浏览 0 评论

Redis的SUBSCRIBEUNSUBSCRIBEPUBLISH命令实现了消息的发布/订阅功能。发送者(消息发布者)不需要编程,就能够向特定的接收者(消息订阅者)发送消息了。Redis会将已发布的消息放入指定的频道之中,消息发布者不需要知道具体有哪些消息订阅者。订阅者可能会订阅一个或多个频道,并且只能接收已订阅频道中的消息,它们也不需要知道具体有哪些消息发布者。这种方式能够充分解耦发布者和订阅者之间的关系,也使得网络拓扑具有更好的伸缩性和动态性。

例如,若客户端想要订阅频道foo和bar,它可以运行SUBSCRIBE命令,以频道名称作为参数,如下所示:

SUBSCRIBE foo bar

如果其他客户端向这些频道发送消息,那么Redis就会将这些消息推送给所有的订阅客户端。

如果一个客户端订阅了一个或多个频道,那么除了订阅和退订命令之外,这个客户端就不能运行其他命令了。订阅和退订操作的应答信息是以消息的形式发送的,客户端只能读取与其相关的消息流,每个消息的第一个部分表示消息的类型。处于订阅状态的客户端只能运行SUBSCRIBEPSUBSCRIBEUNSUBSCRIBEPUNSUBSCRIBEPINGQUIT命令。

一、环境描述

  1. 主机配置

    CPU:单核

    内存:2 GB

    IP:192.168.1.109

  2. 操作系统

    CentOS 6.6 x86_64 Minimal

  3. Redis版本

    Redis server v=3.2.4 sha=00000000:0 malloc=jemalloc-4.0.3 bits=64 build=431d8e4a684c794b

  4. Redis安装方式

    按照《在CentOS上安装Redis缓存系统

二、发布/订阅的相关命令

1. SUBSCRIBE

使得客户端订阅指定的频道。一旦客户端进入订阅状态,它就不能运行除了SUBSCRIBEPSUBSCRIBEUNSUBSCRIBEPUNSUBSCRIBE命令之外的其他命令。

这个命令的运行格式如下所示:

SUBSCRIBE channel [channel ...]

时间复杂度为O(N),N是客户端想要订阅的频道的数量。

2. UNSUBSCRIBE

使得客户端退订指定的频道,如果不指定任何频道,那么就退订所有频道。

这个命令的运行格式如下所示:

UNSUBSCRIBE [channel [channel ...]]

时间复杂度为O(N),N是客户端想要退订的频道的数量。

3. PSUBSCRIBE

使得客户端订阅指定模式的频道,支持glob风格的模式,例如:

  • h?llo:可以订阅hello、hallo和hxllo频道(?表示单个任意字符)。

  • h*llo:可以订阅hllo和heeeello频道(*表示任意多个任意字符,包括空字符)。

  • h[ae]llo:可以订阅hello和hallo频道,但是不能订阅hillo频道(选择[]之间的任意一个字符)。

如果想要将上述通配符作为普通字符进行处理,则需要使用\符号进行转义。

这个命令的运行格式如下所示:

PSUBSCRIBE pattern [pattern ...]

时间复杂度为O(N),N是客户端已经订阅的模式的数量。

4. PUNSUBSCRIBE

使得客户端退订指定的模式所对应的频道,如果不指定任何模式,那么就退订所有的模式。

这个命令的运行格式如下所示:

PUNSUBSCRIBE [pattern [pattern ...]]

时间复杂度为O(N+M),N是客户端已经订阅的模式的数量,M是系统中已经订阅的模式的总数量。。

5. PUBLISH

向指定的频道提交一条消息。返回值是一个整数,表示接收到这条消息的客户端的数量。

这个命令的运行格式如下所示:

PUBLISH channel message

时间复杂度为O(N+M),N是订阅这个接收频道的客户端的数量,M是系统中已经订阅的模式的总数量。

6. PUBSUB

PUBSUB命令是一个自检命令,可用于检查发布/订阅子系统的状态。这个命令是由几个子命令组成的,下面会分别描述。这个命令的一般格式如下所示:

PUBSUB <subcommand> ... args ...

6.1 PUBSUB CHANNELS [pattern]

列出当前有效的频道。有效频道是具有一个或多个订阅者(不包括订阅模式的客户端)的发布/订阅频道。

如果没有指定任何模式,那么就会列出所有的频道。如果指定了模式,那么就只会列出匹配这个模式的所有频道(此处使用glob风格的模式)。

这个命令的返回值是一个数组,它会列出所有的有效频道,包括匹配指定模式的有效频道。

时间复杂度为O(N),N是有效频道的数量,假设模式匹配的时间是恒定的(相对于较短的频道名称和模式而言)。

6.2 PUBSUB NUMSUB [channel-1 … channel-N]

返回指定频道的订阅者的数量(不会计算订阅模式的客户端的数量)。

这个命令的返回值是一个数组,它会列出参数指定的所有频道,以及每个频道的订阅者的数量。返回值的格式为频道、数量、频道、数量、...,因此,这个列表是扁平的。返回值列出的频道顺序和命令调用时指定的频道顺序是相同的。注意,调用这个命令时可以不指定频道,此时返回值是一个空列表。

时间复杂度为O(N),N是命令中指定的频道的数量。

6.3 PUBSUB NUMPAT

返回模式的订阅数量(也就是所有客户端运行PSUBSCRIBE命令的总次数)。注意,这个数量不是订阅模式的客户端的数量,而是所有客户端订阅的模式的总数量。

这个命令的返回值是一个整数,表示所有客户端订阅的模式的总数量。

时间复杂度为O(1)。

三、推送消息的格式

Redis的发布/订阅功能有两种工作模式:订阅频道(channel)和订阅模式(pattern)。这两种工作模式推送消息的格式是不同的,如下文所述。

1. 订阅频道

  • subscribe消息

    这种消息表示客户端已经成功地订阅了指定的频道,它由三部分组成:

    第一部分是subscribe字符串;第二部分表示想要订阅的频道名称;第三部分表示客户端当前已经订阅的频道数量。消息格式如下图所示:

  • unsubscribe消息

    这种消息表示客户端已经成功地退订了指定的频道,它由三部分组成:

    第一部分是unsubscribe字符串;第二部分表示想要退订的频道名称;第三部分表示客户端当前已经订阅的频道数量。若第三部分的值为零,则表示客户端没有订阅任何频道,此时客户端便可以运行任意种类的Redis命令了。消息格式如下图所示:

  • message消息

    这种消息表示订阅频道的客户端已经成功地收到了另一个客户端向这个频道发送的信息,它由三部分组成:

    第一部分是message字符串;第二部分表示推送消息的频道名称;第三部分表示另一个客户端向这个频道发送的消息内容。消息格式如下图所示:

2. 订阅模式

  • psubscribe消息

    这种消息表示客户端已经成功地订阅了指定的模式,它由三部分组成:

    第一部分是psubscribe字符串;第二部分表示想要订阅的模式名称;第三部分表示客户端当前已经订阅的模式数量。消息格式如下图所示:

  • punsubscribe消息

    这种消息表示客户端已经成功地退订了指定的模式,它由三部分组成:

    第一部分是punsubscribe字符串;第二部分表示想要退订的模式名称;第三部分表示客户端当前已经订阅的模式数量。若第三部分的值为零,则表示客户端没有订阅任何模式,此时客户端便可以运行任意种类的Redis命令了。消息格式如下图所示:

  • pmessage消息

    这种消息表示订阅模式的客户端已经成功地收到了另一个客户端向这个模式所对应的频道发送的信息,它由三部分组成:

    第一部分是pmessage字符串;第二部分表示推送消息的频道模式的名称;第三部分表示另一个客户端向这个频道模式发送的消息内容。消息格式如下图所示:

四、注意事项

1. 数据库和作用域

发布/订阅和键空间没有任何关系。它被设计为不会对键空间造成任何影响,包括数据库编号。这就意味着,在db 10上发布消息,仍然可以被在db 1上的订阅者监听到。

如果你需要某种类型的作用域,那么可以为频道名称添加环境前缀,例如:test、staging、production,等等。

2. 频道退订

如果客户端是在Redis命令行(redis-cli)中进入订阅监听状态的,那么它是不能直接运行UNSUBSCRIBEPUNSUBSCRIBE命令的,必须通过telnet之类的工具才能在订阅监听状态中进行退订操作。

3. 订阅计数

subscribeunsubscribepsubscribepunsubscribe消息类型中,消息的最后一部分是客户端仍然有效的订阅数量。这个数量实际上是客户端仍然在订阅的频道和模式的总数。只有当这个数量变为零时,客户端才会退出发布/订阅状态,这就意味着客户端已经退订了所有的频道和模式

4. 模式订阅和频道订阅

如果某个客户端订阅了多个模式(或者多个模式和频道),并且这些模式都能匹配到同一条消息,那么这个客户端就会多次收到这条相同的消息。例如,某个客户端同时订阅了一个频道和一个模式:

SUBSCRIBE foo

PSUBSCRIBE f*

在上面的例子中,如果向频道foo发送一条消息,那么这个客户端将会收到两条消息:一条是message类型的消息,另一条是pmessage类型的消息。

五、命令行示例

1. 订阅频道

Step-1 订阅频道

打开一个Shell终端(此处取名为终端-1),运行以下命令:

telnet localhost 6379

然后,在telnet提示符中输入以下命令:

subscribe mychannel

若上述命令的返回信息(也就是subscribe消息)如下图所示,则表示频道订阅成功:

在上图中,*3表示消息有三部分组成;$9表示下面有9字节长的字符串,也就是subscribe字符串,这是消息的第一部分;接下来,还有一个$9,表示下面有9字节长的字符串,也就是mychannel字符串,这是消息的第二部分;最后,:1表示这个客户端订阅的频道数量。在下面的示例中,返回消息的结构和含义大致相同,本文也就不再赘述。

Step-2 发布消息

打开另一个Shell终端(此处取名为终端-2),运行以下命令:

redis-cli

进入Redis客户端的命令行之后,运行以下命令,发布一条消息:

publish mychannel hello

若上述命令在终端-2中的返回信息如下图所示,则表示消息发送成功:

在上图中,(integer) 1表示收到这条消息的客户端的数量。

此时,在终端-1中可以看到客户端收到的消息(也就是message消息),如下图所示:

Step-3 退订频道

终端-1的telnet提示符中输入以下命令:

unsubscribe mychannel

若上述命令的返回信息(也就是unsubscribe消息)如下图所示,则表示频道退订成功:

2. 订阅模式

Step-1 订阅模式

打开一个Shell终端(此处取名为终端-1),运行以下命令:

telnet localhost 6379

然后,在telnet提示符中输入以下命令:

psubscribe mychannel.*

若上述命令的返回信息(也就是psubscribe消息)如下图所示,则表示模式订阅成功:

Step-2 发布消息

打开另一个Shell终端(此处取名为终端-2),运行以下命令:

redis-cli

进入Redis客户端的命令行之后,运行以下命令,发布一条消息:

publish mychannel.test hello123

若上述命令在终端-2中的返回信息如下图所示,则表示消息发送成功:

在上图中,(integer) 1表示收到这条消息的客户端的数量。

此时,在终端-1中可以看到客户端收到的消息(也就是pmessage消息),如下图所示:

Step-3 退订模式

终端-1的telnet提示符中输入以下命令:

punsubscribe mychannel.*

若上述命令的返回信息(也就是punsubscribe消息)如下图所示,则表示模式退订成功:

3. 同时订阅频道和模式

Step-1 订阅频道和模式

打开一个Shell终端(此处取名为终端-1),运行以下命令:

telnet localhost 6379

然后,在telnet提示符中输入以下命令:

subscribe foop

subscribe f*

若上述命令的返回信息(也就是psubscribe消息)如下图所示,则表示频道和模式订阅成功:

Step-2 发布消息

打开另一个Shell终端(此处取名为终端-2),运行以下命令:

redis-cli

进入Redis客户端的命令行之后,运行以下命令,发布一条消息:

publish foo hello

若上述命令在终端-2中的返回信息如下图所示,则表示消息发送成功:

在上图中,(integer) 2表示收到这条消息的客户端的数量。

此时,在终端-1中可以看到客户端收到两条消息(也就是message消息和pmessage消息),如下图所示:

六、Java示例

Jedis是一种用Java语言开发的Redis客户端,它具有轻量级和功能完备的特点。Jedis完全兼容于Redis 2.8.x和3.0.x版本。

以下代码简单示范了如何通过Jedis操作Redis缓存。

  • App.java

package org.xninja.ghoulich.JedisTest;

import redis.clients.jedis.Jedis;

public class App {

@SuppressWarnings("resource")

public static void main(String[] args) {

final Jedis jedis = new Jedis("192.168.1.109", 6379);

final Jedis pjedis = new Jedis("192.168.1.109", 6379);

final MyListener listener = new MyListener();

final MyListener plistener = new MyListener();

Thread thread = new Thread(new Runnable() {

public void run() {

jedis.subscribe(listener, "mychannel");

}

});

Thread pthread = new Thread(new Runnable() {

public void run() {

pjedis.psubscribe(plistener, "mychannel.*");

}

});

thread.start();

pthread.start();

}

}

该程序建立了两个Jedis客户端,然后又建立了两个发布/订阅监听器,最后启动了两个线程,分别用于监听一个频道和一个模式。

  • MyListener.java

package org.xninja.ghoulich.JedisTest;

import redis.clients.jedis.JedisPubSub;

public class MyListener extends JedisPubSub {

// 取得订阅的消息后的处理

public void onMessage(String channel, String message) {

System.out.println("onMessage: " + channel + "=" + message);

if (message.equals("quit"))

this.unsubscribe(channel);

}

// 初始化订阅时候的处理

public void onSubscribe(String channel, int subscribedChannels) {

System.out.println("onSubscribe: " + channel + "=" + subscribedChannels);

}

// 取消订阅时候的处理

public void onUnsubscribe(String channel, int subscribedChannels) {

System.out.println("onUnsubscribe: " + channel + "=" + subscribedChannels);

}

// 初始化按表达式的方式订阅时候的处理

public void onPSubscribe(String pattern, int subscribedChannels) {

System.out.println("onPSubscribe: " + pattern + "=" + subscribedChannels);

}

// 取消按表达式的方式订阅时候的处理

public void onPUnsubscribe(String pattern, int subscribedChannels) {

System.out.println("onPUnsubscribe: " + pattern + "=" + subscribedChannels);

}

// 取得按表达式的方式订阅的消息后的处理

public void onPMessage(String pattern, String channel, String message) {

System.out.println("onPMessage: " + pattern + "=" + channel + "=" + message);

if (message.equals("quit"))

this.punsubscribe(pattern);

}

}

这个监听器会对频道和模式的订阅、接收消息和退订等事件进行监听,然后进行相应的处理。

在Eclipse中运行App.java的main函数,此时会触发频道和模式的订阅事件,控制台中的输出如下图所示:

此时,示例程序已经订阅了mychannel频道和mychannel.*模式。然后,在redis-cli命令行中输入以下命令,向mychannel频道和mychannel.*模式各发送一条消息:

publish mychannel "hello message for channel"

publish mychannel.test "hello message for pattern"

此时,会触发示例程序的接收消息事件,Eclipse的控制台输出如下图所示:

最后,在redis-cli命令行中输入以下命令,向mychannel频道和mychannel.*模式各发送一条用于退订的消息:

publish mychannel quit

publish mychannel.test quit

由示例程序的源码可知,当监听器收到内容为“quit”的消息时,便会退订mychannel频道和mychannel.*模式,然后终止执行。Eclipse的控制台输出如下图所示:

相关推荐

Redis合集-使用benchmark性能测试

采用开源Redis的redis-benchmark工具进行压测,它是Redis官方的性能测试工具,可以有效地测试Redis服务的性能。本次测试使用Redis官方最新的代码进行编译,详情请参见Redis...

Java简历总被已读不回?面试挂到怀疑人生?这几点你可能真没做好

最近看了几十份简历,发现大部分人不是技术差,而是不会“卖自己”——一、简历死穴:你写的不是经验,是岗位说明书!反面教材:ד使用SpringBoot开发项目”ד负责用户模块功能实现”救命写法:...

redission YYDS(redission官网)

每天分享一个架构知识Redission是一个基于Redis的分布式Java锁框架,它提供了各种锁实现,包括可重入锁、公平锁、读写锁等。使用Redission可以方便地实现分布式锁。red...

从数据库行锁到分布式事务:电商库存防超卖的九重劫难与破局之道

2023年6月18日我们维护的电商平台在零点刚过3秒就遭遇了严重事故。监控大屏显示某爆款手机SKU_IPHONE13_PRO_MAX在库存仅剩500台时,订单系统却产生了1200笔有效订单。事故复盘发...

SpringBoot系列——实战11:接口幂等性的形而上思...

欢迎关注、点赞、收藏。幂等性不仅是一种技术需求,更是数字文明对确定性追求的体现。在充满不确定性的网络世界中,它为我们建立起可依赖的存在秩序,这或许正是技术哲学最深刻的价值所在。幂等性的本质困境在支付系...

如何优化系统架构设计缓解流量压力提升并发性能?Java实战分享

如何优化系统架构设计缓解流量压力提升并发性能?Java实战分享在高流量场景下。首先,我需要回忆一下常见的优化策略,比如负载均衡、缓存、数据库优化、微服务拆分这些。不过,可能还需要考虑用户的具体情况,比...

Java面试题: 项目开发中的有哪些成长?该如何回答

在Java面试中,当被问到“项目中的成长点”时,面试官不仅想了解你的技术能力,更希望看到你的问题解决能力、学习迭代意识以及对项目的深度思考。以下是回答的策略和示例,帮助你清晰、有说服力地展示成长点:一...

互联网大厂后端必看!Spring Boot 如何实现高并发抢券逻辑?

你有没有遇到过这样的情况?在电商大促时,系统上线了抢券活动,结果活动刚一开始,服务器就不堪重负,出现超卖、系统崩溃等问题。又或者用户疯狂点击抢券按钮,最后却被告知无券可抢,体验极差。作为互联网大厂的后...

每日一题 |10W QPS高并发限流方案设计(含真实代码)

面试场景还原面试官:“如果系统要承载10WQPS的高并发流量,你会如何设计限流方案?”你:“(稳住,我要从限流算法到分布式架构全盘分析)…”一、为什么需要限流?核心矛盾:系统资源(CPU/内存/数据...

Java面试题:服务雪崩如何解决?90%人栽了

服务雪崩是指微服务架构中,由于某个服务出现故障,导致故障在服务之间不断传递和扩散,最终造成整个系统崩溃的现象。以下是一些解决服务雪崩问题的常见方法:限流限制请求速率:通过限流算法(如令牌桶算法、漏桶算...

面试题官:高并发经验有吗,并发量多少,如何回复?

一、有实际高并发经验(建议结构)直接量化"在XX项目中,系统日活用户约XX万,核心接口峰值QPS达到XX,TPS处理能力为XX/秒。通过压力测试验证过XX并发线程下的稳定性。"技术方案...

瞬时流量高并发“保命指南”:这样做系统稳如泰山,老板跪求加薪

“系统崩了,用户骂了,年终奖飞了!”——这是多少程序员在瞬时大流量下的真实噩梦?双11秒杀、春运抢票、直播带货……每秒百万请求的冲击,你的代码扛得住吗?2025年了,为什么你的系统一遇高并发就“躺平”...

其实很多Java工程师不是能力不够,是没找到展示自己的正确姿势。

其实很多Java工程师不是能力不够,是没找到展示自己的正确姿势。比如上周有个小伙伴找我,五年经验但简历全是'参与系统设计''优化接口性能'这种空话。我就问他:你做的秒杀...

PHP技能评测(php等级考试)

公司出了一些自我评测的PHP题目,现将题目和答案记录于此,以方便记忆。1.魔术函数有哪些,分别在什么时候调用?__construct(),类的构造函数__destruct(),类的析构函数__cal...

你的简历在HR眼里是青铜还是王者?

你的简历在HR眼里是青铜还是王者?兄弟,简历投了100份没反应?面试总在第三轮被刷?别急着怀疑人生,你可能只是踩了这些"隐形求职雷"。帮3630+程序员改简历+面试指导和处理空窗期时间...

取消回复欢迎 发表评论: