发布订阅概述
消息发布者发布消息,
消息订阅者接收消息,
二者通过某种媒介关联起来。
首先要有消息的发布者,其次要有消息的订阅者。有了消息发布者和订阅者之后,还需要中间的媒介类似频道channel。
发布订阅机制
redis发布订阅功能用于消息的传输;redis发布订阅机制包含3个部分:发布者,订阅者,channel(频道)。
当一个客户端通过 PUBLISH 命令向订阅者发送信息的时候,我们称这个客户端为发布者(publisher)。
而当一个客户端使用 SUBSCRIBE 或者 PSUBSCRIBE命令接收信息的时候,我们称这个客户端为订阅者(subscriber)。
为了解耦发布者(publisher)和订阅者(subscriber)之间的关系,Redis 使用了 channel (频道)作为两者的中介 —— 发布者将信息直接发布给 channel ,而 channel 负责将信息发送给适当的订阅者,发布者和订阅者之间没有相互关系,也不知道对方的存在。
如上图所示,Client A
和 Client B
订阅了 channel
,当 Client C
通过 channel
发布消息 mess
时,Client A
和 Client B
就会收到该消息。
底层原理
关于底层原理,我想单独拉出写篇博客,发现是要通过分析 Redis 源码里的 pubsub.c 文件才行,Redis是使用C实现的,,但是我还是想写,所以这里简单概述一下。
Redis 通过 PUBLISH 、SUBSCRIBE 和 PSUBSCRIBE 等命令实现发布和订阅功能。
通过 SUBSCRIBE 命令订阅某频道后,redis-server 里维护了一个字典,字典的键就是一个个 channel ,而字典的值则是一个链表,链表中保存了所有订阅这个 channel 的客户端。SUBSCRIBE 命令的关键,就是将客户端添加到给定 channel 的订阅链表中。
通过 PUBLISH 命令向订阅者发送消息,redis-server 会使用给定的频道作为键,在它所维护的 channel 字典中查找记录了订阅这个频道的所有客户端的链表,遍历这个链表,将消息发布给所有订阅者。
命令
Redis 发布订阅模式(pub/sub mode) 是一种消息通信模式
Redis 客户端可以订阅任意数量的频道
发送者(Publisher)发送消息
订阅者(Subsciber)接收消息
subscribe
使用subscribe,订阅给定的一个或多个频道
subscribe z1 z2 z3
Reading messages... (press Ctrl-C to quit)
1) "subscribe" # 订阅反馈
2) "z1" # 订阅的频道
3) (integer) 1 # 目前客户端已订阅频道/模式的数量
1) "message" # 信息
2) "z1" # 发送信息的频道
3) " hello zsl " # 信息内容```
1) "message" # 信息
2) "z2" # 发送信息的频道
3) " hello zsl2" # 信息内容```
1) "message" # 信息
2) "z3" # 发送信息的频道
3) " hello zsl3 " # 信息内容
publish
使用publish,用于向给定的频道发送信息,返回值为接收到信息的订阅者数量
Centos6.5.6379:0>publish z1 "hello zsl"
"1"
Centos6.5.6379:0>publish z2 "hello zsl2"
"1"
Centos6.5.6379:0>publish z3 "hello zsl3"
"1"
psubscribe
模式订阅消息
使用psubscribe,订阅消息的时候还可以通过模式匹配订阅的方式订阅,
比如说,使用 zsl.* 为输入,就可以订阅所有以 zsl. 开头的频道,比如 zsl.yx 、 zsl.blog 、 zsl.np ,
psubscribe zsl.*
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "zsl.*"
3) (integer) 1
1) "pmessage"
2) "zsl.*" # 匹配的模式
3) "zsl.np" # 消息的来源频道
4) "zsl niupi" # 消息内容
1) "pmessage"
2) "zsl.*"
3) "zsl.youxiu"
4) "zsl youxiu"
java实现redis发布订阅小demo
Publisher发布者
@Component
public class Publisher extends Thread{
@Autowired
private JedisPool jedisPool;
public Publisher(JedisPool jedisPool) {
this.jedisPool = jedisPool;
}
@Override
public void run(){
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
Jedis jedis = jedisPool.getResource(); //连接池中取出一个连接
while (true) {
String line;
try {
line = reader.readLine();
if (!"quit".equals(line)) {
jedis.publish("mychannel", line); //从通过mychannel 频道发布消息
System.out.println(String.format("发布消息成功!channel: %s, message: %s", "mychannel", line));
} else {
break;
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
MsgListener监听
@Component
public class MsgListener extends JedisPubSub{
@Override
public void onMessage(String channel, String message) {
// TODO Auto-generated method stub
System.out.println("收到消息成功!channel:"+channel+"msg:"+message);
this.unsubscribe();
}
@Override
public void onPMessage(String pattern, String channel, String message) {
// TODO Auto-generated method stub
super.onPMessage(pattern, channel, message);
}
@Override
public void onSubscribe(String channel, int subscribedChannels) {
// TODO Auto-generated method stub
System.out.println(String.format("订阅频道成功! channel: %s, subscribedChannels %d",
channel, subscribedChannels));
}
@Override
public void onUnsubscribe(String channel, int subscribedChannels) {
// TODO Auto-generated method stub
System.out.println(String.format("取消订阅频道! channel: %s, subscribedChannels: %d",
channel, subscribedChannels));
}
@Override
public void onPUnsubscribe(String pattern, int subscribedChannels) {
// TODO Auto-generated method stub
super.onPUnsubscribe(pattern, subscribedChannels);
}
@Override
public void onPSubscribe(String pattern, int subscribedChannels) {
// TODO Auto-generated method stub
super.onPSubscribe(pattern, subscribedChannels);
}
}
Subscriber订阅者
@Component
public class Subscriber extends Thread{
@Resource
private JedisPool jedisPool;
private final MsgListener msgListener = new MsgListener();
private final String channel = "mychannel";
public Subscriber(JedisPool jedisPool) {
super("Subscriber");
this.jedisPool = jedisPool;
}
@Override
public void run() {
Jedis jedis = null;
try {
jedis = jedisPool.getResource(); //取出一个连接
jedis.subscribe(msgListener, channel); //通过subscribe的api去订阅,参数是订阅者和频道名
//注意:subscribe是一个阻塞的方法,在取消订阅该频道前,会一直阻塞在这,无法执行后续的代码
//这里在msgListener的onMessage方法里面收到消息后,调用了this.unsubscribe();来取消订阅,才会继续执行
System.out.println("继续执行后续代码。。。");
} catch (Exception e) {
System.out.println(String.format("subsrcibe channel error, %s", e));
} finally {
if (jedis != null) {
jedis.close();
}
}
}
}