目录

  前言

  生产者和消费者

  发布和订阅

Java实现

  注意

前言

  随着业务复杂, 业务的项目依赖关系增强, 使用消息队列帮助系统降低耦合度.发布订阅(pub/sub)是一种消息通信模式,主要目的是解除消息发布者、消息订阅者之间的耦合
  订阅分布本身也是一种生产者消费者模式, 订阅者是消费者, 发布者是生产者.
  订阅发布模式, 发布者发布消息后, 只要有订阅方, 则多个订阅方会收到同样的消息
  生产者消费者模式, 生产者往队列里放入消息, 由多个消费者对一条消息进行抢占.
  订阅分布模式可以将一些不着急完成的工作放到其他进程或者线程中进行离线处理.

pub/sub的特点

(1)时间非耦合
  发布者和订阅者不必同时在线,它们不必同时参与交互
(2)空间非耦合
  发布者和订阅者不必相互知道对方所在的位置
(3)同步非耦合
  发布者/订阅者是异步模式,发布者可不断地生产消息,订阅者则可异步地得到消息通知
pub/sub的使用场景
    基于pub/sub的特点,他的典型使用场景就是实时消息系统,比如即时聊天,群聊等功能,还常用作减轻高并发的I/O写压力,例如大量的写日志操作,如果实时写入日志文件或者数据库,会造成I/O超负荷,降低系统性能,那么就可以用pub/sub方式,写日志时先不进行写操作,而是向日志频道发布一条日志消息,然后有一个单独的日志程序来订阅日志频道,异步的读取日志消息写入文件或数据库

Redis中的订阅发布模式, 当没有订阅者时, 消息会被直接丢弃(Redis不会持久化保存消息)

生产者和消费者

  生产者使用Redis中的list数据结构进行实现, 将待处理的消息塞入到消息队列中.

具体内容放在下次“消息通道异步处理”讲

发布和订阅

在Redis Pubsub中, 一个频道(channel)相当于一个消息队列,

“发布/订阅”模式包含两种角色,分别是发布者和订阅者。订阅者可以订阅一个或若干个频道(channel),而发布者可以向指定的频道发送消息,所有订阅此频道的订阅者都会收到此消息。

发布者发送消息的命令是PUBLISH,用法是PUBLISH channel message,如向channel.1说一声“hi”:
redis>PUBLISH channel.1 hi
(integer) 0
这样消息就发出去了。返回值表示接收到这条消息的订阅者数量。发出去的消息不会被持久化,也就是说当客户端订阅channel.1后只能收到后续发布到该频道的消息,之前发送到就收不到了。

订阅频道的命令是SUBSCRIBE,可以同时订阅多个频道,用法是 SUBSCRIBE channel [channel ...]。
redis>SUBSCRIBE channel.1
  Reading messages... (press Ctrl-C to quit)
  1) "subscribe"
  2) "channel.1"
  3) (integer) 1
执行SUBSCRIBE命令后进入订阅状态,处于此状态下客户端不能使用除SUBSCRIBE/UNSUBSCRIBE/PSUBSCRIBE/PUNSUBSCRIBE这四个属于“发布/订阅”模式之外的命令,否则会报错。
进入订阅模式后客户端可能收到三种类型的回复。每种类型的回复都包含3个值,第一个值是消息的类型,根据消息类型的不同,第二第三个值的含义也不同。消息类型可能的取值有:
  1)Subscribe。表示订阅成功的反馈信息。第二个值是订阅成功的频道名称,第三个值是当前客户端订阅的频道数。
  2)message。这个类型的回复表示收到的消息。第二个值表示产生消息的频道名称,第三个值是消息内容。
  3)unsubscribe。表示成功取消订阅某个频道。第二个值是对应的频道名称,第三个值是当前客户端订阅的频道数量,当此值为0时客户端会退出订阅状态。

redis 将所有频道的订阅关系都保存在 pubsub_channels 字典里面,这个字典的键是某个被订阅的频道,而键的值则是一个链表,链表里面记录了所有订阅这个频道的客户端。当某频道有新消息时,就会查找对应的链表,向链表中每个客户端发送通知

当一个连接通过subscribe或者psubscribe订阅通道后就进入订阅模式。在这种模式除了再订阅额外的通道或者用unsubscribe或者punsubscribe命令退出订阅模式,就不能再发送其他命令。另外使用 psubscribe命令订阅多个通配符通道,如果一个消息匹配上了多个通道模式的话,会多次收到同一个消息。

java 类实现

a.订阅

 package com.dengzy.redis;

 import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPubSub; public class Subscriber extends JedisPubSub { private static Logger logger = LoggerFactory.getLogger(Subscriber.class); @Override
public void onMessage(String channel, String message) {
logger.warn("Message received. Channel: [" + channel + "], Msg: [" + message + "] )");
} @Override
public void onPMessage(String pattern, String channel, String message) {
System.out.println("onPMessage: channel[" + channel + "], message[" + message + "]");
} @Override
public void onSubscribe(String channel, int subscribedChannels) {
System.out.println("onSubscribe: channel[" + channel + "]," + "subscribedChannels[" + subscribedChannels + "]");
} @Override
public void onUnsubscribe(String channel, int subscribedChannels) {
System.out.println("onUnsubscribe: channel[" + channel + "], " + "subscribedChannels[" + subscribedChannels + "]");
} @Override
public void onPUnsubscribe(String pattern, int subscribedChannels) {
System.out.println("onPUnsubscribe: pattern[" + pattern + "]," + "subscribedChannels[" + subscribedChannels + "]");
} @Override
public void onPSubscribe(String pattern, int subscribedChannels) {
System.out.println("onPSubscribe: pattern[" + pattern + "], " + "subscribedChannels[" + subscribedChannels + "]");
} public static void main(String[] args) {
Jedis jr = null;
try {
jr = new Jedis("192.168.1.10", 6379, 0);// redis服务地址和端口号
Subscriber sp = new Subscriber();
sp.proceed(jr.getClient(), "news.share", "news.blog");
// sp.proceedWithPatterns(jr.getClient(), "news.*");
} catch (Exception e) {
e.printStackTrace();
} finally {
if (jr != null) {
jr.disconnect();
}
}
}
}

b.发布

 import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis; import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader; public class Publisher { private static final Logger logger = LoggerFactory.getLogger(Publisher.class); private final Jedis publisherJedis; private final String channel; public Publisher(Jedis publisherJedis, String channel) {
this.publisherJedis = publisherJedis;
this.channel = channel;
} public void start() {
logger.info("Type your message (quit for terminate)");
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); while (true) {
String line = reader.readLine(); if (!"quit".equals(line)) {
publisherJedis.publish(channel, line);
} else {
break;
}
} } catch (IOException e) {
logger.error("IO failure while reading input, e");
}
}
}

3. main

 import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig; public class Program {
private static Logger logger = LoggerFactory.getLogger(Program.class); public static final String CHANNEL_NAME = "commonChannel"; public static void main(String[] args) throws Exception { JedisPoolConfig poolConfig = new JedisPoolConfig(); JedisPool jedisPool = new JedisPool(poolConfig, "192.168.1.10", 6379, 0); final Jedis subscriberJedis = jedisPool.getResource(); final Subscriber subscriber = new Subscriber(); new Thread(new Runnable() {
@Override
public void run() {
try {
logger.info("Subscribing to \"commonChannel\". This thread will be blocked.");
subscriberJedis.subscribe(subscriber, CHANNEL_NAME);
logger.info("Subscription ended.");
} catch (Exception e) {
logger.error("Subscribing failed." + e.getMessage());
}
}
}).start(); Jedis publisherJedis = jedisPool.getResource(); new Publisher(publisherJedis, CHANNEL_NAME).start(); subscriber.unsubscribe();
jedisPool.returnResource(subscriberJedis);
jedisPool.returnResource(publisherJedis);
}
}

注意 

  1.在Jedis中订阅方处理是采用异步的方式,
    如同步的方式,在do-while循环中, 会等到当前消息处理完毕才能够处理下一条消息, 这样会导致当入队列消息量过大的时候, redis链接被强制关闭.

  2.“单点问题”,如果发布者的消息过多,一台redis-server处理不过来,redis还没支持负载均衡集群,主从配置在“消息过多”情况下还是无能为力,接收消息的压力始终都得在主的压力上面。这种情况下只能人为的将发布者的消息按照业务拆分,将某些消息发布到另外一台redis server上面去。
  3.通过负载均衡集群来增加自己的接收消息的能力,通过主从配置解决 redis-server的消息分发能力不够。
  4.如果redis-server的订阅端处理能力不足怎么办?这一点对于redis sever非常危险。因为redis-server会将消息存储在redis server 服务端内存中,如果订阅端的处理始终处理缓慢,那么redis server的内存就会不断变大。

Redis_发布订阅(基础)的更多相关文章

  1. Redis_发布订阅(Spring Boot)

    目录 前言 生产者和消费者 发布和订阅 Java实现 注意 转至 http://www.tianmaying.com/tutorial/springboot-redis-message 前言 利用Sp ...

  2. RabbitMQ 发布订阅

    互联网公司对消息队列是深度使用者,因此需要我们了解消息队列的方方面面,良好的设计及深入的理解,更有利于我们对消息队列的规划. 当前我们使用消息队列中发现一些问题: 1.实际上是异步无返回远程调用,由发 ...

  3. MQTT的学习研究(十四) MQTT moquette 的 Callback API 消息发布订阅的实现

    在moquette-mqtt中提供了回调callback模式的发布和订阅但是在订阅之后没有发现有消息接收的方法,参看moquette-mqtt中Block,Future式的发布订阅基础是callbac ...

  4. kafka 基础知识梳理-kafka是一种高吞吐量的分布式发布订阅消息系统

    一.kafka 简介 今社会各种应用系统诸如商业.社交.搜索.浏览等像信息工厂一样不断的生产出各种信息,在大数据时代,我们面临如下几个挑战: 如何收集这些巨大的信息 如何分析它 如何及时做到如上两点 ...

  5. Spring Boot 2.x基础教程:使用Redis的发布订阅功能

    通过前面一篇集中式缓存的使用教程,我们已经了解了Redis的核心功能:作为K.V存储的高性能缓存. 接下来我们会分几篇来继续讲讲Redis的一些其他强大用法!如果你对此感兴趣,一定要关注收藏我哦! 发 ...

  6. Redis基础知识 之——发布/订阅

    一.说明: 订阅,取消订阅和发布实现了发布/订阅消息范式(引自wikipedia),发送者(发布者)不是计划发送消息给特定的接收者(订阅者).而是发布的消息分到不同的频道,不需要知道什么样的订阅者订阅 ...

  7. RedisRepository封装—Redis发布订阅以及StackExchange.Redis中的使用

    本文版权归博客园和作者本人吴双共同所有,转载请注明本Redis系列分享地址.http://www.cnblogs.com/tdws/tag/NoSql/ Redis Pub/Sub模式 基本介绍 Re ...

  8. 分布式消息总线,基于.NET Socket Tcp的发布-订阅框架之离线支持,附代码下载

    一.分布式消息总线以及基于Socket的实现 在前面的分享一个分布式消息总线,基于.NET Socket Tcp的发布-订阅框架,附代码下载一文之中给大家分享和介绍了一个极其简单也非常容易上的基于.N ...

  9. 分布式发布订阅消息系统 Kafka 架构设计[转]

    分布式发布订阅消息系统 Kafka 架构设计 转自:http://www.oschina.net/translate/kafka-design 我们为什么要搭建该系统 Kafka是一个消息系统,原本开 ...

随机推荐

  1. 【莫队算法】【权值分块】bzoj3585 mex

    orz PoPoQQQ. 本来蒟蒻以为这种离散化以后就对应不起来的题不能权值分块搞的说. ……结果,实际上>n的权值不会对答案作出贡献. #include<cstdio> #incl ...

  2. 【kmp算法】bzoj1355 [Baltic2009]Radio Transmission

    引用题解:http://blog.csdn.net/wyfcyx_forever/article/details/40347425 #include<cstdio> #include< ...

  3. 1.1(Spring学习笔记)Spring-事务基础

    一.Spring 事务 Spring提供对事务支持的核心包是spring-tx-4.3.6.RELEASE包. 该包类有三个核心接口,提供对事务的支持: 1.1PlatformTransactionM ...

  4. ARM“庖丁解牛”之存储器管理单元MMU

    转:http://blog.sina.com.cn/s/blog_a07635070101bcbt.html 最近笔者详细地学习了由杜春雷老师编写的<ARM体系结构与编程>.对ARM存储管 ...

  5. ActiveMQ实战-集群

    原文:http://blog.csdn.net/lifetragedy/article/details/51869032 ActiveMQ的集群 内嵌代理所引发的问题: 消息过载 管理混乱 如何解决这 ...

  6. C#调用页面中的窗体中的方法,获取窗体的元素。

    页面中的窗体 <div class="div_width" style="width: 100%; height: 95%;"> <ifram ...

  7. 前端:微信支付和支付宝支付在pc端和h5页面中的应用

    1:h5微信支付 使用的是https://pay.weixin.qq.com/wiki/doc/api/index.html  中的 (1):公司需要首先要配置公众号微信支付地址和测试白名单(支付的时 ...

  8. javascript快速入门20--Cookie

    Cookie 基础知识 我们已经知道,在 document 对象中有一个 cookie 属性.但是 Cookie 又是什么?“某些 Web 站点在您的硬盘上用很小的文本文件存储了一些信息,这些文件就称 ...

  9. uber shader

    shader 合在一起 用一些宏来控制 选哪部分编成一个想要的shader https://docs.unity3d.com/Manual/SL-MultipleProgramVariants.htm ...

  10. JavaScript获取table中某一列的值的方法

    1.实现源码 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www. ...