首先使用redis客户端来进行publish与subscribe的功能是否能够正常运行。

打开redis服务器

[root@localhost ~]# redis-server /opt/redis-2.4.10/redis.conf
[7719] 16 Apr 11:37:22 # Warning: 32 bit instance detected but no memory limit set. Setting 3.5 GB maxmemory limit with 'noeviction' policy now.
[7719] 16 Apr 11:37:22 * Server started, Redis version 2.4.10
[7719] 16 Apr 11:37:22 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.

打开一个客户端订阅一个news.sports的channel。

[root@localhost ~]# redis-cli
redis 127.0.0.1:6379> subscribe news.sports
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "news.sports"
3) (integer) 1

可以看到已经开始了监听,向news.sports channel发布一条消息

[root@localhost ~]# redis-cli
redis 127.0.0.1:6379> publish news.sports "kaka is back"
(integer) 1

订阅的客户端顺利收到消息

redis 127.0.0.1:6379> subscribe news.sports
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "news.sports"
3) (integer) 1
1) "message"
2) "news.sports"
3) "kaka is back"

接下来使用jedis来进行发布/订阅的验证

发布消息是通过jedis.publish(String channel, String message)来发布的,其实就是往redis服务器发布一条publish命令。

public void publish(final byte[] channel, final byte[] message) {
sendCommand(PUBLISH, channel, message);
}

订阅消息是通过jedis.subscribe(JedisPub pub,String channel)来进行的,channel好理解,那么JedisPub是什么呢。

看源码吧。

Jedis订阅方法的源码为

public void subscribe(JedisPubSub jedisPubSub, String... channels) {
checkIsInMulti();
connect();
client.setTimeoutInfinite();
jedisPubSub.proceed(client, channels);
client.rollbackTimeout();
}

可以看到,主要是通过jedisPubSub.proceed(client, channels);来进行订阅的。看proceed方法。

public void proceed(Client client, String... channels) {
this.client = client;
client.subscribe(channels);
client.flush();
process(client);
}

追踪client.subscribe(channels)可以看到,

public void subscribe(final byte[]... channels) {
sendCommand(SUBSCRIBE, channels);
}

其只是向服务器发送了一个subcribe的命令而已。

那么要了解jedisPubSub的作用,只能看process方法了。简单看process其实是一个do...while循环

private void process(Client client) {
do { } while (isSubscribed());
}

我们可以猜测正是靠着这个循环来不断的读取服务器那边传到来的订阅的消息。

看主体

List<Object> reply = client.getObjectMultiBulkReply();
final Object firstObj = reply.get(0);
if (!(firstObj instanceof byte[])) {
throw new JedisException("Unknown message type: " + firstObj);
}
final byte[] resp = (byte[]) firstObj;
if (Arrays.equals(SUBSCRIBE.raw, resp)) {
subscribedChannels = ((Long) reply.get(2)).intValue();
final byte[] bchannel = (byte[]) reply.get(1);
final String strchannel = (bchannel == null) ? null
: SafeEncoder.encode(bchannel);
onSubscribe(strchannel, subscribedChannels);
} else if (Arrays.equals(UNSUBSCRIBE.raw, resp)) {
subscribedChannels = ((Long) reply.get(2)).intValue();
final byte[] bchannel = (byte[]) reply.get(1);
final String strchannel = (bchannel == null) ? null
: SafeEncoder.encode(bchannel);
onUnsubscribe(strchannel, subscribedChannels);
} else if (Arrays.equals(MESSAGE.raw, resp)) {
final byte[] bchannel = (byte[]) reply.get(1);
final byte[] bmesg = (byte[]) reply.get(2);
final String strchannel = (bchannel == null) ? null
: SafeEncoder.encode(bchannel);
final String strmesg = (bmesg == null) ? null : SafeEncoder
.encode(bmesg);
onMessage(strchannel, strmesg);
} else if (Arrays.equals(PMESSAGE.raw, resp)) {
final byte[] bpattern = (byte[]) reply.get(1);
final byte[] bchannel = (byte[]) reply.get(2);
final byte[] bmesg = (byte[]) reply.get(3);
final String strpattern = (bpattern == null) ? null
: SafeEncoder.encode(bpattern);
final String strchannel = (bchannel == null) ? null
: SafeEncoder.encode(bchannel);
final String strmesg = (bmesg == null) ? null : SafeEncoder
.encode(bmesg);
onPMessage(strpattern, strchannel, strmesg);
} else if (Arrays.equals(PSUBSCRIBE.raw, resp)) {
subscribedChannels = ((Long) reply.get(2)).intValue();
final byte[] bpattern = (byte[]) reply.get(1);
final String strpattern = (bpattern == null) ? null
: SafeEncoder.encode(bpattern);
onPSubscribe(strpattern, subscribedChannels);
} else if (Arrays.equals(PUNSUBSCRIBE.raw, resp)) {
subscribedChannels = ((Long) reply.get(2)).intValue();
final byte[] bpattern = (byte[]) reply.get(1);
final String strpattern = (bpattern == null) ? null
: SafeEncoder.encode(bpattern);
onPUnsubscribe(strpattern, subscribedChannels);
} else {
throw new JedisException("Unknown message type: " + firstObj);
}

可以看到,通过client.getObjectMultiBulkReply()来得到返回来的消息。判断消息的类型来进行不同的操作。比如Arrays.equals(SUBSCRIBE.raw, resp)判断返回来的消息是订阅,subscribedChannels = ((Long) reply.get(2)).intValue();是取得消息,也是do...while判断循环的条件,也就是说这一次如果读到消息了,则进行下一次循环。那么onSubscribe(String channel, int subscribedChannels)究竟做了什么事,看开头

public abstract void onMessage(String channel, String message); 

   public abstract void onPMessage(String pattern, String channel,
String message); public abstract void onSubscribe(String channel, int subscribedChannels); public abstract void onUnsubscribe(String channel, int subscribedChannels); public abstract void onPUnsubscribe(String pattern, int subscribedChannels); public abstract void onPSubscribe(String pattern, int subscribedChannels);

可以看到这是xetorthio留给我们的方法。onSubscrible是订阅时应该做些什么,onMessage就是有消息传来是做些什么,以此类推。

接下来可以写一个方法来发布和订阅消息了。

package redis.client.jredis.tests; 

import java.util.Timer;
import java.util.TimerTask; import org.junit.Test; import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.JedisPubSub; public class JedisTest extends JedisTestBase {
JedisPool pool = null;
/**
* 测试发布验证
*/
@Test
public void testPS(){
/**
Jedis jedis = new Jedis("192.168.5.146",6379);
jedis.set("name", "xiaoruoen");
jedis.publish("news.blog.title", "Hello,World");
//*/
final String host = "192.168.5.146";
JedisPoolConfig config = new JedisPoolConfig();
pool = new JedisPool(new JedisPoolConfig(),host);
subscribe(new NewsListener(), "news.sports");
Timer timer = new Timer();
timer.schedule(new TimerTask() { @Override
public void run() {
// TODO Auto-generated method stub
publish("news.sports", "{\"_id\":335566,\"author\":\"xiaoruoen\",\"title\":\"kaka is back\"}");
}
}, 1000, 3000); } public Jedis getResource(int dbnum){
Jedis jedis = pool.getResource();
jedis.select(dbnum);
return jedis;
} /**
*
* @param channel
* @param message ex:"{\"_id\":335566,\"author\":\"xiaoruoen\",\"title\":\"kaka is back\"}"
*/
public void publish(String channel,String message){
Jedis jedis = getResource(12);
jedis.publish(channel, message);
pool.returnResource(jedis);
} public void subscribe(JedisPubSub listener,String channel){
Jedis jedis = getResource(12);
jedis.subscribe(listener, channel);
pool.returnResource(jedis);
} }
package redis.client.jredis.tests; 

import redis.clients.jedis.JedisPubSub; 

public class NewsListener extends JedisPubSub { 

    @Override
public void onMessage(String channel, String message) {
System.out.println("get message from"+channel+" "+message);
} @Override
public void onPMessage(String pattern, String channel, String message) {
System.out.println("get message from"+channel+" "+message);
} @Override
public void onSubscribe(String channel, int subscribedChannels) {
System.out.println("subscribe the channel:"+channel);
} @Override
public void onUnsubscribe(String channel, int subscribedChannels) {
System.out.println("get message from"+channel);
} @Override
public void onPUnsubscribe(String pattern, int subscribedChannels) {
System.out.println("get message from"+subscribedChannels);
} @Override
public void onPSubscribe(String pattern, int subscribedChannels) {
System.out.println("get message from"+subscribedChannels);
} }

发现只打印了一条数据subscribe the channel:news.sports

没按我们所期望的那样那所有发布的消息都打印出来。

到官网查看

https://github.com/xetorthio/jedis/wiki/AdvancedUsage

看到Note that subscribe is a blocking operation operation because it will poll Redis for responses on the thread that calls subscribe.可以看到subcribe是一个线程中的块操作。我猜测是在发布与接收的过程中,如果在同一线程里面进行操作,一边阻塞着流,另一边无法进行操作。于是将publish改写为另一线程启动。修改如下:

public static void main(String[] args){
final String host = "192.168.5.146";
JedisPoolConfig config = new JedisPoolConfig();
pool = new JedisPool(new JedisPoolConfig(),host);
Thread thread = new Thread(new Test().new PublishThread());
thread.start();
subscribe(new NewsListener(), "news.sports"); }
class PublishThread implements Runnable{
@Override
public void run() {
Timer timer = new Timer();
timer.schedule(new TimerTask() { @Override
public void run() {
// TODO Auto-generated method stub
publish("news.sports", "{\"_id\":335566,\"author\":\"xiaoruoen\",\"title\":\"kaka is back\"}");
}
}, 1000, 3000);
} }

最终发布订阅成功。

Pool:redis.clients.jedis.JedisPool@18e2b22
subscribe the channel:news.sports
Pool:redis.clients.jedis.JedisPool@18e2b22
get message fromnews.sports {"_id":335566,"author":"xiaoruoen","title":"kaka is back"}
Pool:redis.clients.jedis.JedisPool@18e2b22
get message fromnews.sports {"_id":335566,"author":"xiaoruoen","title":"kaka is back"}
Pool:redis.clients.jedis.JedisPool@18e2b22
get message fromnews.sports {"_id":335566,"author":"xiaoruoen","title":"kaka is back"}
Pool:redis.clients.jedis.JedisPool@18e2b22
get message fromnews.sports {"_id":335566,"author":"xiaoruoen","title":"kaka is back"}
Pool:redis.clients.jedis.JedisPool@18e2b22

本文出自 “若是人间” 博客,请务必保留此出处http://xiaoruoen.blog.51cto.com/4828946/835710

jedis的publish/subscribe[转]含有redis源码解析的更多相关文章

  1. .Net Core缓存组件(Redis)源码解析

    上一篇文章已经介绍了MemoryCache,MemoryCache存储的数据类型是Object,也说了Redis支持五中数据类型的存储,但是微软的Redis缓存组件只实现了Hash类型的存储.在分析源 ...

  2. Redis源码解析:15Resis主从复制之从节点流程

    Redis的主从复制功能,可以实现Redis实例的高可用,避免单个Redis 服务器的单点故障,并且可以实现负载均衡. 一:主从复制过程 Redis的复制功能分为同步(sync)和命令传播(comma ...

  3. Redis源码解析:13Redis中的事件驱动机制

    Redis中,处理网络IO时,采用的是事件驱动机制.但它没有使用libevent或者libev这样的库,而是自己实现了一个非常简单明了的事件驱动库ae_event,主要代码仅仅400行左右. 没有选择 ...

  4. Redis源码解析之跳跃表(三)

    我们再来学习如何从跳跃表中查询数据,跳跃表本质上是一个链表,但它允许我们像数组一样定位某个索引区间内的节点,并且与数组不同的是,跳跃表允许我们将头节点L0层的前驱节点(即跳跃表分值最小的节点)zsl- ...

  5. redis源码解析之内存管理

    zmalloc.h的内容如下: void *zmalloc(size_t size); void *zcalloc(size_t size); void *zrealloc(void *ptr, si ...

  6. Redis源码解析之ziplist

    Ziplist是用字符串来实现的双向链表,对于容量较小的键值对,为其创建一个结构复杂的哈希表太浪费内存,所以redis 创建了ziplist来存放这些键值对,这可以减少存放节点指针的空间,因此它被用来 ...

  7. Redis源码解析

    一.src/server.c 中的redisCommandTable列出的所有redis支持的命令,其中字符串命令包括从get到mget:列表命令从rpush到rpoplpush:集合命令包括从sad ...

  8. Redis源码解析:26集群(二)键的分配与迁移

    Redis集群通过分片的方式来保存数据库中的键值对:一个集群中,每个键都通过哈希函数映射到一个槽位,整个集群共分16384个槽位,集群中每个主节点负责其中的一部分槽位. 当数据库中的16384个槽位都 ...

  9. Redis源码解析:25集群(一)握手、心跳消息以及下线检测

    Redis集群是Redis提供的分布式数据库方案,通过分片来进行数据共享,并提供复制和故障转移功能. 一:初始化 1:数据结构 在源码中,通过server.cluster记录整个集群当前的状态,比如集 ...

随机推荐

  1. 转-SpringMVC——之 国际化

    原文地址:http://www.cnblogs.com/liukemng/p/3750117.html 在系列(7)中我们讲了数据的格式化显示,Spring在做格式化展示的时候已经做了国际化处理,那么 ...

  2. 浅谈使用 PHP 进行手机 APP 开发(API 接口开发)

    做过 API 的人应该了解,其实开发 API 比开发 WEB 更简洁,但可能逻辑更复杂,因为 API 其实就是数据输出,不用呈现页面,所以也就不存在 MVC(API 只有 M 和 C),那么我们来探讨 ...

  3. 【UVA】1596 Bug Hunt(模拟)

    题目 题目     分析 算是个模拟吧     代码 #include <bits/stdc++.h> using namespace std; map<int,int> a[ ...

  4. 【学习笔记】dp入门

    知识点 动态规划(简称dp),可以说是各种程序设计中遇到的第一个坎吧,这篇博文是我对dp的一点点理解,希望可以帮助更多人dp入门.   先看看这段话 动态规划(dynamic programming) ...

  5. HTTPS安全超文本传输协议

    一.什么是HTTPS 简单的理解HTTPS就是使用SSL/TLS加密内容的.安全的HTTP协议 HTTPS = HTTP + SSL/TLS 二.对称加密与非对称加密 对称加密:加密和解密使用同一密钥 ...

  6. VirtualBox从USB设备(PE)启动图文教程

    很多朋友都为VBox无法进入U盘启动而苦恼,别懊恼,魔笛这就为你讲解 方法/步骤   1 大家先把U盘插入实体电脑. 单击“确定”打开“磁盘管理”, 键盘上的Win+R组合键,输入diskmgnt.. ...

  7. 13_java之final|static|包|匿名对象|代码块|内部类

    01final关键字概念 * A: 概述 继承的出现提高了代码的复用性,并方便开发.但随之也有问题,有些类在描述完之后,不想被继承, 或者有些类中的部分方法功能是固定的,不想让子类重写.可是当子类继承 ...

  8. gridView删除提示框

    实现方法: 双击GridView的OnRowDataBound事件: 在后台的GridView1_RowDataBound()方法添加代码,最后代码如下所示: protected void GridV ...

  9. flutter 交互提示方式

    交互提示方式dialog和snackbar 首先看看dialog的方式 new PopupMenuButton( icon: new Icon(Icons.phone_iphone, color: C ...

  10. spring boot 项目打包到maven仓库供其它模块使用

    在对spring boot项目进行打包发布的时候发现其它spring boot项目服务真正引用使用该spring boot包中的类 需对打包插件做如下修改: <build> <plu ...