http://blog.csdn.net/canot/article/details/52040415

关于个人对Redis提供的Pub/Sub机制的认识在上一篇博客中涉及到了,也提到了关于如何避免Redis的Pub/Sub的一个最大的缺陷的思路—消息的持久化(http://blog.csdn.net/canot/article/details/51975566)。这篇文章主要是关于其思路(Redis的Pub/Sub的消息持久化)的代码实现:

Pub/Sub机制中最核心的Listener的实现:

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPubSub;
import redis.clients.util.RedisInputStream; public class PubSubListener extends JedisPubSub { private String clientId;
private HandlerRedis handlerRedis; // 生成PubSubListener的时候必须制定一个id
public PubSubListener(String cliendId, Jedis jedis) {
this.clientId = cliendId;
jedis.auth("xxxx");
handlerRedis = new HandlerRedis(jedis);
} @Override
public void onMessage(String channel, String message) {
if ("quit".equals(message)) {
this.unsubscribe(channel);
}
handlerRedis.handler(channel, message);
} // 真正处理接受的地方
private void message(String channel, String message) {
System.out.println("message receive:" + message + ",channel:" + channel + "...");
} @Override
public void onPMessage(String pattern, String channel, String message) {
// TODO Auto-generated method stub } @Override
public void onSubscribe(String channel, int subscribedChannels) {
// 将订阅者保存在一个"订阅活跃者集合中"
handlerRedis.subscribe(channel);
System.out.println("subscribe:" + channel);
} @Override
public void onUnsubscribe(String channel, int subscribedChannels) {
handlerRedis.ubsubscribe(channel);
System.out.println("unsubscribe:" + channel);
} @Override
public void onPUnsubscribe(String pattern, int subscribedChannels) {
// TODO Auto-generated method stub } @Override
public void onPSubscribe(String pattern, int subscribedChannels) {
// TODO Auto-generated method stub } class HandlerRedis {
private Jedis jedis; public HandlerRedis(Jedis jedis) {
this.jedis = jedis;
} public void handler(String channel, String message) {
int index = message.indexOf("/");
if (index < 0) {
// 消息不合法,丢弃
return;
}
Long txid = Long.valueOf(message.substring(0, index));
String key = clientId + "/" + channel;
while (true) {
String lm = jedis.lindex(key, 0);// 获取第一个消息
if (lm == null) {
break;
}
int li = lm.indexOf("/");
// 如果消息不合法,删除并处理
if (li < 0) {
String result = jedis.lpop(key);// 删除当前message
// 为空
if (result == null) {
break;
}
message(channel, lm);
continue;
}
Long lxid = Long.valueOf(lm.substring(0, li));// 获取消息的txid
// 直接消费txid之前的残留消息
if (txid >= lxid) {
jedis.lpop(key);// 删除当前message
message(channel, lm);
continue;
} else {
break;
}
}
} // 持久化订阅操作
public void subscribe(String channel) {
// 保证在订阅者集合中的格式为 唯一标识符/订阅的通道
String key = clientId + "/" + channel;
// 判断该客户端是否在集合中存在
boolean isExist = jedis.sismember("PERSIS_SUB", key);
if (!isExist) {
// 不存在则添加
jedis.sadd("PERSIS_SUB", key);
}
} public void ubsubscribe(String channel) {
String key = clientId + "/" + channel;
// 从“活跃订阅者”集合中
jedis.srem("PERSIS_SUB", key);
// 删除“订阅者消息队列”
jedis.del(channel);
}
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125

Listener中定义了一个内部类HandlerRedis。Listener类将onMessage以及onSubscribe两个方法交付于HandlerRedis。Handler处理这个方法的时候也即进行着队列的维护。Listener类中定义了一个message()方法,该方法是handler的回调方法,即真正的处理消息的地方。

通道的订阅客户端类:

import redis.clients.jedis.Jedis;

public class SubClient {
private Jedis jedis;
private PubSubListener listener; public SubClient(String host,PubSubListener pubSubListener){
jedis = new Jedis(host);
jedis.auth("XXXXX");
this.listener = pubSubListener;
} public void sub(String channel){
jedis.subscribe(listener, channel);
} public void unsubscribe(String channel){
listener.unsubscribe(channel);
} }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

通道的消息发布的客户端:

import java.util.Set;

import redis.clients.jedis.Jedis;

public class PubClient {
private Jedis jedis; public PubClient(String host) {
jedis = new Jedis(host);
jedis.auth("wx950709");
} /**
* 发布的每条消息,都需要在“订阅者消息队列”中持久
*
* @param message
*/
public void put(String message) {
//获取所有活跃的消息接收者客户端 clientID/channel
Set<String> subClients = jedis.smembers("PERSIS_SUB");
for (String subs : subClients) {
// 保存每个客户端的消息
jedis.rpush(subs, message);
}
} public void publish(String channel, String message) {
// 每个消息,都有具有一个全局唯一的id
// txid为了防止订阅端在数据处理时“乱序”,这就要求订阅者需要解析message
Long txid = jedis.incr("MESSAGE_TXID");
String content = txid + "/" + message;
this.put(content);
jedis.publish(channel, content);//为每个消息设定id,最终消息格式1000/messageContent
}
public void close(String channel){
jedis.publish(channel, "quit");
jedis.del(channel);//删除
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

测试引导类:

import redis.clients.jedis.Jedis;

public class Main {
public static void main(String[] args) throws Exception{
PubClient pubClient = new PubClient("127.0.0.1");
final String channel = "pubsub-channel222";
PubSubListener listener = new PubSubListener("client_one", new Jedis("127.0.0.1"));
SubClient subClient = new SubClient("127.0.0.1", listener);
Thread t1 = new Thread(new Runnable() { @Override
public void run() {
//在API级别,此处为轮询操作,直到unsubscribe调用,才会返回
subClient.sub(channel);
} });
t1.setDaemon(true);
t1.start(); int i = 0;
while(i < 2){
pubClient.publish(channel, "message"+i);
i++;
Thread.sleep(1000);
}
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
 
 

【转】 使用Redis的Pub/Sub来实现类似于JMS的消息持久化的更多相关文章

  1. redis的Pub/Sub

    redis的Pub/Sub机制类似于广播架构,Subscriber相当于收音机,可以收听多个channel(频道),Publisher(电台)可以在channel中发布信息. 命令介绍 PUBLISH ...

  2. Redis的Pub/Sub机制存在的问题以及解决方案

    Redis的Pub/Sub机制使用非常简单的方式实现了观察者模式,但是在使用过程中我们发现,它仅仅是实现了发布订阅机制,但是很多的场景没有考虑到.例如一下的几种场景: 1.数据可靠性无法保证 一个re ...

  3. Redis的Pub/Sub客户端实现

    前言   在学习T-io框架,从写一个Redis客户端开始一文中,已经简单介绍了Redis客户端的实现思路,并且基础架构已经搭建完成,只不过支持的命令不全,不过后期在加命令就会很简单了.本篇就要实现P ...

  4. Redis实战——Redis的pub/Sub(订阅与发布)在java中的实现

    借鉴:https://blog.csdn.net/canot/article/details/51938955 1.什么是pub/sub Pub/Sub功能(means Publish, Subscr ...

  5. 分布式缓存技术redis学习系列(三)——redis高级应用(主从、事务与锁、持久化)

    上文<详细讲解redis数据结构(内存模型)以及常用命令>介绍了redis的数据类型以及常用命令,本文我们来学习下redis的一些高级特性. 安全性设置 设置客户端操作秘密 redis安装 ...

  6. 分布式缓存技术redis学习(三)——redis高级应用(主从、事务与锁、持久化)

    上文<详细讲解redis数据结构(内存模型)以及常用命令>介绍了redis的数据类型以及常用命令,本文我们来学习下redis的一些高级特性.目录如下: 安全性设置 设置客户端操作秘密 客户 ...

  7. 分布式缓存技术redis系列(三)——redis高级应用(主从、事务与锁、持久化)

    上文<详细讲解redis数据结构(内存模型)以及常用命令>介绍了redis的数据类型以及常用命令,本文我们来学习下redis的一些高级特性. 安全性设置 设置客户端操作秘密 redis安装 ...

  8. Linux07 /redis的配置、五大数据类型、发布订阅、持久化、主从复制、哨兵配置、集群搭建

    Linux07 /redis的配置.五大数据类型.发布订阅.持久化.主从复制.哨兵配置.集群搭建 目录 Linux07 /redis的配置.五大数据类型.发布订阅.持久化.主从复制.哨兵配置.集群搭建 ...

  9. redis的pub/sub命令

    Redis 发布订阅 Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息. Redis 客户端可以订阅任意数量的频道. 下图展示了频道 cha ...

随机推荐

  1. AppInventor学习笔记(四)——打地鼠应用学习

    一.组件设计 1.整体预览 2.图片精灵的添加 (1)首先加入一个画布进去 调节属性如图 (2)添加精灵 添加精灵,然后上传张图片进行属性修改 3.加入Clock 直接加入,设定为1000ms 二.B ...

  2. JVM的本地方法栈

    对于一个运行中的Java程序而言,它还可能会用到一些跟本地方法相关的数据区.当某个线程调用一个本地方法时,它就进入了一个全新的并且不再受虚拟机限制的世界.本地方法可以通过本地方法接口来访问虚拟机的运行 ...

  3. MVC ViewEngineResult实际上是一种设计

    概述 MVC中, IView代表一个视图,最后是要表现为HTML或者其他的HttpResponse的应答流的: IViewEngine提供了类似工厂的作用或者提供器的作用,以返回一个视图. OO的视觉 ...

  4. LightOJ1158 Anagram Division(状压DP)

    题目问一个数字字符串的不重复全排列有几个能被d整除. dp[S][m]表示用字符集合S构成的%d为m的数字字符串个数 dp[0][0]=0 我为人人转移,dp[S+{x}][(m*10+str[x]- ...

  5. HDU 1026 (BFS搜索+优先队列+记录方案)

    题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=1026 题目大意:最短时间内出迷宫.迷宫里要杀怪,每个怪有一定HP,也就是说要耗一定时.输出方案. 解 ...

  6. TYVJ 1014 乘法游戏

    做题记录:2016-08-15 16:10:14 背景 太原成成中学第2次模拟赛 第四道 描述 乘法游戏是在一行牌上进行的.每一张牌包括了一个正整数.在每一个移动中,玩家拿出一张牌,得分是用它的数字乘 ...

  7. 【wikioi】1922 骑士共存问题(网络流/二分图匹配)

    用匈牙利tle啊喂?和网络流不都是n^3的吗(匈牙利O(nm), isap O(n^2m) 但是isap实际复杂度很优的(二分图匹配中,dinic是O(sqrt(V)*E),不知道isap是不是一样. ...

  8. Spring整合Quartz实现持久化、动态设定时间

    一.spring整合 网上一搜有很多整合的方式,这里我采用了其中的一种(暂时还没有对其他的方法研究过). 对于spring的整合其中的任务,spring提供了几个类.接口(这些类都实现了Job接口): ...

  9. SQLi filter evasion cheat sheet (MySQL)

    This week I presented my experiences in SQLi filter evasion techniques that I have gained during 3 y ...

  10. HTML&CSS----练习(运算符)

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...