Redis的Pub/Sub机制使用非常简单的方式实现了观察者模式,但是在使用过程中我们发现,它仅仅是实现了发布订阅机制,但是很多的场景没有考虑到。例如一下的几种场景:

  1.数据可靠性无法保证

  一个redis_cli发送消息的时候,消息是无状态的,也就是说负责发送消息的redis_cli只管发送消息,并不会理会消息是否被订阅者接收到,也不会理会是否在传输过程中丢失,即对于发布者来说,消息是”即发即失”的。

  2.扩展性差

  不能通过增加消费者来加快消耗发布者的写入的数据,如果发布者发布的消息很多,则数据阻塞在通道中已等待被消费着来消耗。阻塞时间越久,数据丢失的风险越大(网络或者服务器的一个不稳定就会导致数据的丢失)

  3.资源消耗高

  在pub/sub中消息发布者不需要独占一个Redis的链接,而消费者则需要单独占用一个Redis的链接,在java中便不得独立出分出一个线程来处理消费者。这种场景一般对应这多个消费者,此时则有着过高的资源消耗。

  对于如上的几种不足,如果在项目中需要考虑的话可以使用JMS来实现该功能。JMS提供了消息的持久化/耐久性等各种企业级的特性。如果依然想使用Redis来实现并做一些数据的持久化操作,则可以根据JMS的特性来通过Redis模拟出来.

  模拟的步骤如下:
  1.subscribe端首先向一个Set集合中增加“订阅者ID”,此Set集合保存了“活跃订阅”者,订阅者ID标记每个唯一的订阅者,例如:sub:email,sub:web。此SET称为“活跃订阅者集合”

  2.subcribe端开启订阅操作,并基于Redis创建一个以“订阅者ID”为KEY的LIST数据结构,此LIST中存储了所有的尚未消费的消息。此LIST称为“订阅者消息队列”

  3.publish端:每发布一条消息之后,publish端都需要遍历“活跃订阅者集合”,并依次向每个“订阅者消息队列”尾部追加此次发布的消息。到此为止,我们可以基本保证,发布的每一条消息,都会持久保存在每个“订阅者消息队列”中。

  4.subscribe端,每收到一个订阅消息,在消费之后,必须删除自己的“订阅者消息队列”头部的一条记录。subscribe端启动时,如果发现自己的自己的“订阅者消息队列”有残存记录,那么将会首先消费这些记录,然后再去订阅。

  实现如下:

public class PubSubListener extends JedisPubSub{

    private String clientId;
private RedisHandler redisHandler; public PubSubListener(String clientId, Jedis jedis) {
this.clientId = clientId;
this.redisHandler = new RedisHandler(jedis);
} @Override
public void onMessage(String channel, String message) {
if ("exit".equals(message)) {
redisHandler.onUnsubscribe(channel);
} redisHandler.hanlder(channel,message);
} @Override
public void onSubscribe(String channel, int subscribedChannels) {
redisHandler.subscribe(channel);
} @Override
public void onUnsubscribe(String channel, int subscribedChannels) {
redisHandler.onUnsubscribe(channel);
} class RedisHandler{ private Jedis jedis =null; public RedisHandler(Jedis jedis) {
this.jedis = jedis;
} /**
* 订阅操作步骤:
* 1.判断clientID是否在PERSITS_SUB队列中
* 2.如果在队列中说明已经订阅,或则把clientID添加到队列中
* @param channel
*/
public void subscribe(String channel){
String key = clientId + "/" + channel;
boolean isExists = this.jedis.sismember("PERSITS_SUB",key);
if(!isExists){
this.jedis.sadd("PERSITS_SUB",key);
}
} /**
* 取消订阅
* @param channel
*/
public void onUnsubscribe(String channel){
String key = clientId + "/" + channel;
//从订阅者队列中删除
this.jedis.srem("",key);
//删除订阅者消息队列
this.jedis.del(channel);
} public void hanlder(String channel,String message){
int index = message.indexOf("/");
if(index < ){
//消息不合法,丢弃
return;
} Long txid = Long.valueOf(message.substring(,index));
String key = clientId + "/" + channel;
while(true){
String lm = this.jedis.lindex(key,);//获取第一个消息
if(lm == null){
break;
}
int li = lm.indexOf("/");
if(li < ){
//消息不合法
String result = this.jedis.lpop(key);
if(result == null){
break;
}
message(channel,message);
continue;
}
long lmid = Long.parseLong(lm.substring(,li));
if(txid >= lmid){
this.jedis.lpop(key);
message(channel,message);
continue;
}else{
break;
}
}
}
} private void message(String channel, String message) {
System.out.println("receive message " + message);
}
}

  

public class SubClient {

    private Jedis jedis = null;
private PubSubListener listener = null; public SubClient(Jedis jedis, PubSubListener listener) {
this.jedis = jedis;
this.listener = listener;
} public void subscribe(String channel){
jedis.subscribe(listener,channel);
} public void onUnsubscribe(String channel){
listener.unsubscribe(channel);
}
}

  

public class PubClient {

    private Jedis jedis = null;

    public PubClient(String host) {
this.jedis = new Jedis(host);
} public void put(String message){
Set<String> clients = this.jedis.smembers("PERSITS_SUB");
for(String client : clients){
//在每个客户端对应的消息队列中持久化消息
this.jedis.rpush(client,message);
}
} /**
* 每个消息,都有具有一个全局唯一的id
* txid为了防止订阅端在数据处理时“乱序”,这就要求订阅者需要解析message
* @param channel
* @param message
*/
public void publish(String channel, String 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, "exit");
jedis.del(channel);//删除
}
}

Redis的Pub/Sub机制存在的问题以及解决方案的更多相关文章

  1. 根据redis的pub/sub机制,写一个即时在线聊天应用

    在Redis中,有个Pub/Sub,他的主要的工作流程如: redis订阅一个模式频道如:chat_*,然后由小a想找人聊天了,就发送一个消息“现在有人聊天吗?chat_a”,末尾的chat_a为标识 ...

  2. redis的Pub/Sub

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

  3. 【转】 使用Redis的Pub/Sub来实现类似于JMS的消息持久化

    http://blog.csdn.net/canot/article/details/52040415 关于个人对Redis提供的Pub/Sub机制的认识在上一篇博客中涉及到了,也提到了关于如何避免R ...

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

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

  5. Redis提供的持久化机制(RDB和AOF)

    Redis提供的持久化机制 Redis是一种面向"key-value"类型数据的分布式NoSQL数据库系统,具有高性能.持久存储.适应高并发应用场景等优势.它虽然起步较晚,但发展却 ...

  6. redis持久化数据的机制——转发

    转载:https://www.cnblogs.com/xingzc/p/5988080.html Redis提供的持久化机制(RDB和AOF)   Redis提供的持久化机制 Redis是一种面向“k ...

  7. Redis提供的持久化机制(一)

    Redis提供的持久化机制 redis是一个内存数据库,也就是说它的所有的数据都是保存在内存中的,而内存中的数据当程序结束时就会消失,所以我们要想办法把内存中的数据写到磁盘中.当程序异常退出或者正常退 ...

  8. Redis的内存回收机制

    Redis的内存回收机制 2018年01月16日 17:11:48 chs007chs 阅读数:1172   Redis的内存回收机制主要体现在一下两个方面: 删除过期时间的键对象 删除过期键对象 : ...

  9. Redis提供的持久化机制(RDB和AOF)【转载】

    Redis提供的持久化机制    Redis是一种面向“key-value”类型数据的分布式NoSQL数据库系统,具有高性能.持久存储.适应高并发应用场景等优势.它虽然起步较晚,但发展却十分迅速. 近 ...

随机推荐

  1. Laravel图表扩展包推荐:Charts

     2016年11月15日 ·  2283次 ·  4条 ·  laravel,package,charts 介绍 在项目开发中,创建图表通常是一件痛苦的事情.因为你必须将数据转换为图表库支持的格式传输 ...

  2. 学习类App原型制作分享-Wokabulary

    Wokabulary是一款多功能词汇学习App,可以学习多国语言词汇.原型的引导页面采用的图片+文字+分页器,需要注意的是分页器选中位置要与页面顺序一致.其次是语言的选择页面,在前面给大家介绍过滚动区 ...

  3. Debian8 下面 muduo库编译与使用

    其实<Linux 多线程服务端编程>已经写得很详细 但是考虑到代码版本的更新和操作系统的不同 可能部分位置会有些许出入 这里做个记录 方便以后学习运行 我使用的虚拟 安装的是debian系 ...

  4. 如何将网站部署到tomcat根目录下

    更改前访问:http://192.168.1.2/baby 更改后访问:http://192.168.1.2/ 打开tomcat/conf/server.xml找到 <Host name=&qu ...

  5. 2018.10.20 NOIP模拟 面包(数学期望)

    传送门 把方差的式子拆开. 方差=平方的期望-期望的平方. 显然只用维护点对的个数和总方案数就行了. 利用分步的思想来统计. 要统计覆盖一个矩形(x1,y1,x2,y2)(x1,y1,x2,y2)(x ...

  6. 2018.09.16 bzoj1086: [SCOI2005]王室联邦(贪心)

    传送门 就是给树分块. 对于一个节点. 如果它的几棵子树加起来超过了下限,就把它们分成一块. 这样每次可能会剩下几个节点. 把它们都加入栈中最顶上那一块就行了. 代码: #include<bit ...

  7. 2018.07.29~30 uoj#170. Picks loves segment tree VIII(线段树)

    传送门 线段树好题. 维护区间取两种最值,区间加,求区间两种历史最值,区间最小值. 自己的写法调了一个晚上+一个上午+一个下午+一个晚上并没有调出来,90" role="prese ...

  8. 一致性哈希Java源码分析

    首次接触一致性哈希是在学习memcached的时候,为了解决分布式服务器的负载均衡或者说选路的问题,一致性哈希算法不仅能够使memcached服务器被选中的概率(数据分布)更加均匀,而且使得服务器的增 ...

  9. 事务不起作用 Closing non transactional SqlSession

    In proxy mode (which is the default), only external method calls coming in through the proxy are int ...

  10. Spring bean加载2--FactoryBean情况处理

    Spring bean加载2--FactoryBean情况处理 在Spring bean加载过程中,每次bean实例在返回前都会调用getObjectForBeanInstance来处理Factory ...