原文:http://shift-alt-ctrl.iteye.com/blog/1867454

Redis或许已经在很多企业开始推广并试水,本文也根据个人的实践,简单描述一下Redis在实际开发过程中的使用(部署与架构,稍后介绍),程序执行环境为java + jedis,关于spring下如何集成redis-api,稍后介绍吧。

前言:下载redis-2.6.2,安装好redis之后,请在redis.conf文件中,将如下3个配置属性开启(仅供测试使用):

  1. ##客户端链接的端口,也是server端侦听client链接的端口
  2. ##每个client实例,都将和server在此端口上建立tcp长链接
  3. port 6379
  4. ## server端绑定的ip地址,如果一个物理机器有多个网络接口时,可以明确指定为某个网口的ip地址
  5. bind 127.0.0.1
  6. ##链接中io操作空闲时间,如果在指定时间内,没有IO操作,链接将会被关闭
  7. ##此属性和TCP链接中的timeout选项一样,建议设置为0,很多时候,我们一个应用也只会有一个redis实例
  8. ##不过,如果你使用连接池的话,你需要对此参数做额外的考虑。
  9. timeout 0

Pub/Sub: "发布/订阅",对于此功能,我们将会想到很多JMS实现,Redis提供此功能显的“多此一举”;不过这个功能在redis中,被设计的非常轻量级和简洁,它做到了消息的“发布”和“订阅”的基本能力,但是尚未提供JMS中关于消息的持久化/耐久性等各种企业级的特性。

一个Redis client发布消息,其他多个redis client订阅消息,发布的消息“即发即失”,redis不会持久保存发布的消息;消息订阅者也将只能得到订阅之后的消息,通道中此前的消息将无从获得。这就类似于JMS中“非持久”类型的消息。

消息发布者,即publish客户端,无需独占链接,你可以在publish消息的同时,使用同一个redis-client链接进行其他操作(例如:INCR等)

消息订阅者,即subscribe客户端,需要独占链接,即进行subscribe期间,redis-client无法穿插其他操作,此时client以阻塞的方式等待“publish端”的消息;这一点很好理解,因此subscribe端需要使用单独的链接,甚至需要在额外的线程中使用。

一旦subscribe端断开链接,将会失去部分消息,即链接失效期间的消息将会丢失。

如果你非常关注每个消息,那么你应该考虑使用JMS或者基于Redis做一些额外的补充工作,如果你期望订阅是持久的,那么如下的设计思路可以借鉴(如下原理基于JMS):

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

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

3) publish端:每发布一条消息之后,publish端都需要遍历“活跃订阅者集合”,并依次向每个“订阅者消息队列”尾部追加此次发布的消息。

4) 到此为止,我们可以基本保证,发布的每一条消息,都会持久保存在每个“订阅者消息队列”中。

5) subscribe端,每收到一个订阅消息,在消费之后,必须删除自己的“订阅者消息队列”头部的一条记录。

6) subscribe端启动时,如果发现自己的自己的“订阅者消息队列”有残存记录,那么将会首先消费这些记录,然后再去订阅。

--------------------------------------------------------------非持久化订阅-------------------------------------------------------

PrintListener.java:订阅者消息处理器

  1. public class PrintListener extends JedisPubSub{
  2. @Override
  3. public void onMessage(String channel, String message) {
  4. String time = DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss");
  5. System.out.println("message receive:" + message + ",channel:" + channel + "..." + time);
  6. //此处我们可以取消订阅
  7. if(message.equalsIgnoreCase("quit")){
  8. this.unsubscribe(channel);
  9. }
  10. }
  11. ...
  12. }

PubClient.java:消息发布端

  1. public class PubClient {
  2. private Jedis jedis;//
  3. public PubClient(String host,int port){
  4. jedis = new Jedis(host,port);
  5. }
  6. public void pub(String channel,String message){
  7. jedis.publish(channel, message);
  8. }
  9. public void close(String channel){
  10. jedis.publish(channel, "quit");
  11. jedis.del(channel);//
  12. }
  13. }

SubClient.java:消息订阅端

  1. public class SubClient {
  2. private Jedis jedis;//
  3. public SubClient(String host,int port){
  4. jedis = new Jedis(host,port);
  5. }
  6. public void sub(JedisPubSub listener,String channel){
  7. jedis.subscribe(listener, channel);
  8. //此处将会阻塞,在client代码级别为JedisPubSub在处理消息时,将会“独占”链接
  9. //并且采取了while循环的方式,侦听订阅的消息
  10. //
  11. }
  12. }

PubSubTestMain.java:测试引导类

  1. public class PubSubTestMain {
  2. /**
  3. * @param args
  4. */
  5. public static void main(String[] args) throws Exception{
  6. PubClient pubClient = new PubClient(Constants.host, Constants.port);
  7. final String channel = "pubsub-channel";
  8. pubClient.pub(channel, "before1");
  9. pubClient.pub(channel, "before2");
  10. Thread.sleep(2000);
  11. //消息订阅着非常特殊,需要独占链接,因此我们需要为它创建新的链接;
  12. //此外,jedis客户端的实现也保证了“链接独占”的特性,sub方法将一直阻塞,
  13. //直到调用listener.unsubscribe方法
  14. Thread subThread = new Thread(new Runnable() {
  15. @Override
  16. public void run() {
  17. try{
  18. SubClient subClient = new SubClient(Constants.host, Constants.port);
  19. System.out.println("----------subscribe operation begin-------");
  20. JedisPubSub listener = new PrintListener();
  21. //在API级别,此处为轮询操作,直到unsubscribe调用,才会返回
  22. subClient.sub(listener, channel);
  23. System.out.println("----------subscribe operation end-------");
  24. }catch(Exception e){
  25. e.printStackTrace();
  26. }
  27. }
  28. });
  29. subThread.start();
  30. int i=0;
  31. while(i < 10){
  32. String message = RandomStringUtils.random(6, true, true);//apache-commons
  33. pubClient.pub(channel, message);
  34. i++;
  35. Thread.sleep(1000);
  36. }
  37. //被动关闭指示,如果通道中,消息发布者确定通道需要关闭,那么就发送一个“quit”
  38. //那么在listener.onMessage()中接收到“quit”时,其他订阅client将执行“unsubscribe”操作。
  39. pubClient.close(channel);
  40. //此外,你还可以这样取消订阅
  41. //listener.unsubscribe(channel);
  42. }
  43. }

--------------------------------------------------------------持久化订阅-------------------------------------------------------

基本思路:当订阅者订阅消息时,将此订阅者信息添加到一个列表中,此列表为“所有订阅者列表”,同时为每个订阅者都创建一个保存消息(内容或者消息ID)的队列,消息发布者将每条消息都添加到每个订阅者的队列中。

如下实现仅供参考,有很多更优的实现方式。

PPrintListener.java

  1. public class PPrintListener extends JedisPubSub{
  2. private String clientId;
  3. private PSubHandler handler;
  4. public PPrintListener(String clientId,Jedis jedis){
  5. this.clientId = clientId;
  6. handler = new PSubHandler(jedis);
  7. }
  8. @Override
  9. public void onMessage(String channel, String message) {
  10. //此处我们可以取消订阅
  11. if(message.equalsIgnoreCase("quit")){
  12. this.unsubscribe(channel);
  13. }
  14. handler.handle(channel, message);//触发当前订阅者从自己的消息队列中移除消息
  15. }
  16. private void message(String channel,String message){
  17. String time = DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss");
  18. System.out.println("message receive:" + message + ",channel:" + channel + "..." + time);
  19. }
  20. @Override
  21. public void onPMessage(String pattern, String channel, String message) {
  22. System.out.println("message receive:" + message + ",pattern channel:" + channel);
  23. }
  24. @Override
  25. public void onSubscribe(String channel, int subscribedChannels) {
  26. handler.subscribe(channel);
  27. System.out.println("subscribe:" + channel + ";total channels : " + subscribedChannels);
  28. }
  29. @Override
  30. public void onUnsubscribe(String channel, int subscribedChannels) {
  31. handler.unsubscribe(channel);
  32. System.out.println("unsubscribe:" + channel + ";total channels : " + subscribedChannels);
  33. }
  34. @Override
  35. public void onPUnsubscribe(String pattern, int subscribedChannels) {
  36. System.out.println("unsubscribe pattern:" + pattern + ";total channels : " + subscribedChannels);
  37. }
  38. @Override
  39. public void onPSubscribe(String pattern, int subscribedChannels) {
  40. System.out.println("subscribe pattern:" + pattern + ";total channels : " + subscribedChannels);
  41. }
  42. @Override
  43. public void unsubscribe(String... channels) {
  44. super.unsubscribe(channels);
  45. for(String channel : channels){
  46. handler.unsubscribe(channel);
  47. }
  48. }
  49. class PSubHandler {
  50. private Jedis jedis;
  51. PSubHandler(Jedis jedis){
  52. this.jedis = jedis;
  53. }
  54. public void handle(String channel,String message){
  55. int index = message.indexOf("/");
  56. if(index < 0){
  57. return;
  58. }
  59. Long txid = Long.valueOf(message.substring(0,index));
  60. String key = clientId + "/" + channel;
  61. while(true){
  62. String lm = jedis.lindex(key, 0);//获取第一个消息
  63. if(lm == null){
  64. break;
  65. }
  66. int li = lm.indexOf("/");
  67. //如果消息不合法,删除并处理
  68. if(li < 0){
  69. String result = jedis.lpop(key);//删除当前message
  70. //为空
  71. if(result == null){
  72. break;
  73. }
  74. message(channel, lm);
  75. continue;
  76. }
  77. Long lxid = Long.valueOf(lm.substring(0,li));//获取消息的txid
  78. //直接消费txid之前的残留消息
  79. if(txid >= lxid){
  80. jedis.lpop(key);//删除当前message
  81. message(channel, lm);
  82. continue;
  83. }else{
  84. break;
  85. }
  86. }
  87. }
  88. public void subscribe(String channel){
  89. String key = clientId + "/" + channel;
  90. boolean exist = jedis.sismember(Constants.SUBSCRIBE_CENTER,key);
  91. if(!exist){
  92. jedis.sadd(Constants.SUBSCRIBE_CENTER, key);
  93. }
  94. }
  95. public void unsubscribe(String channel){
  96. String key = clientId + "/" + channel;
  97. jedis.srem(Constants.SUBSCRIBE_CENTER, key);//从“活跃订阅者”集合中删除
  98. jedis.del(key);//删除“订阅者消息队列”
  99. }
  100. }
  101. }

PPubClient.java

  1. public class PPubClient {
  2. private Jedis jedis;//
  3. public PPubClient(String host,int port){
  4. jedis = new Jedis(host,port);
  5. }
  6. /**
  7. * 发布的每条消息,都需要在“订阅者消息队列”中持久
  8. * @param message
  9. */
  10. private void put(String message){
  11. //期望这个集合不要太大
  12. Set<String> subClients = jedis.smembers(Constants.SUBSCRIBE_CENTER);
  13. for(String clientKey : subClients){
  14. jedis.rpush(clientKey, message);
  15. }
  16. }
  17. public void pub(String channel,String message){
  18. //每个消息,都有具有一个全局唯一的id
  19. //txid为了防止订阅端在数据处理时“乱序”,这就要求订阅者需要解析message
  20. Long txid = jedis.incr(Constants.MESSAGE_TXID);
  21. String content = txid + "/" + message;
  22. //非事务
  23. this.put(content);
  24. jedis.publish(channel, content);//为每个消息设定id,最终消息格式1000/messageContent
  25. }
  26. public void close(String channel){
  27. jedis.publish(channel, "quit");
  28. jedis.del(channel);//删除
  29. }
  30. public void test(){
  31. jedis.set("pub-block", "15");
  32. String tmp = jedis.get("pub-block");
  33. System.out.println("TEST:" + tmp);
  34. }
  35. }

PPSubClient.java

  1. public class PSubClient {
  2. private Jedis jedis;//
  3. private JedisPubSub listener;//单listener
  4. public PSubClient(String host,int port,String clientId){
  5. jedis = new Jedis(host,port);
  6. listener = new PPrintListener(clientId, new Jedis(host, port));
  7. }
  8. public void sub(String channel){
  9. jedis.subscribe(listener, channel);
  10. }
  11. public void unsubscribe(String channel){
  12. listener.unsubscribe(channel);
  13. }
  14. }

PPubSubTestMain.java

    1. public class PPubSubTestMain {
    2. /**
    3. * @param args
    4. */
    5. public static void main(String[] args) throws Exception{
    6. PPubClient pubClient = new PPubClient(Constants.host, Constants.port);
    7. final String channel = "pubsub-channel-p";
    8. final PSubClient subClient = new PSubClient(Constants.host, Constants.port,"subClient-1");
    9. Thread subThread = new Thread(new Runnable() {
    10. @Override
    11. public void run() {
    12. System.out.println("----------subscribe operation begin-------");
    13. //在API级别,此处为轮询操作,直到unsubscribe调用,才会返回
    14. subClient.sub(channel);
    15. System.out.println("----------subscribe operation end-------");
    16. }
    17. });
    18. subThread.setDaemon(true);
    19. subThread.start();
    20. int i = 0;
    21. while(i < 2){
    22. String message = RandomStringUtils.random(6, true, true);//apache-commons
    23. pubClient.pub(channel, message);
    24. i++;
    25. Thread.sleep(1000);
    26. }
    27. subClient.unsubscribe(channel);
    28. }
    29. }

Redis编程实践【pub/sub】的更多相关文章

  1. 暑假第六周总结(对HBASE进行编程实践并且安装Redis)

    本周主要是根据教程对HBASE进行了编程实践,对于hadoop的编程来说需要用到很多的.jar 包,在进行编程实践的时候需要参照相关的教程将jar包添加至程序当中去.教程上给的代码还是比较详细的,加上 ...

  2. 高性能javascript学习笔记系列(5) -快速响应的用户界面和编程实践

    参考高性能javascript 理解浏览器UI线程  用于执行javascript和更新用户界面的进程通常被称为浏览器UI线程  UI线程的工作机制可以理解为一个简单的队列系统,队列中的任务按顺序执行 ...

  3. 高性能JavaScript 编程实践

    前言 最近在翻<高性能JavaScript>这本书(2010年版 丁琛译),感觉可能是因为浏览器引擎的改进或是其他原因,书中有些原本能提高性能的代码在最新的浏览器中已经失效.但是有些章节的 ...

  4. Method Swizzling和AOP(面向切面编程)实践

    Method Swizzling和AOP(面向切面编程)实践 参考: http://www.cocoachina.com/ios/20150120/10959.html 上一篇介绍了 Objectiv ...

  5. 编程实践中C语言的一些常见细节

    对于C语言,不同的编译器采用了不同的实现,并且在不同平台上表现也不同.脱离具体环境探讨C的细节行为是没有意义的,以下是我所使用的环境,大部分内容都经过测试,且所有测试结果基于这个环境获得,为简化起见, ...

  6. 第二章 C语言编程实践

    上章回顾 宏定义特点和注意细节 条件编译特点和主要用处 文件包含的路径查询规则 C语言扩展宏定义的用法 第二章 第二章 C语言编程实践 C语言编程实践 预习检查 异或的运算符是什么 宏定义最主要的特点 ...

  7. 试读《JavaScript语言精髓与编程实践》

    有幸看到iteye的活动,有幸读到<JavaScript语言精髓与编程实践_第2版>的试读版本,希望更有幸能完整的读到此书. 说来读这本书的冲动,来得很诡异,写一篇读后感,赢一本书,其实奖 ...

  8. Python GUI编程实践

    看完了<python编程实践>对Python的基本语法有了一定的了解,加上认识到python在图形用户界面和数据库支持方面快捷,遂决定动手实践一番. 因为是刚接触Python,对于基本的数 ...

  9. [Java 并发] Java并发编程实践 思维导图 - 第一章 简单介绍

    阅读<Java并发编程实践>一书后整理的思维导图.

随机推荐

  1. Spring MVC框架下 将数据库内容前台页面显示完整版【获取数据库人员参与的事件列表】

    1.书写jsp页面包括要显示的内容[people.jsp] <!-- 此处包括三个方面内容: 1.包含 文本输入框 查询按钮  查询结果显示位置 (paging) 2.包括对按钮(button) ...

  2. spoj p104 Matrix-Tree定理

    这个问题就是经典的生成树记数问题,题目为spoj p104 highway. 首先我们引入Matrix-Tree定理,由kirchhoff证明,定理的概述为,对于图G,我们定义若干个矩阵, D[G], ...

  3. python3,循环,方法练习2

    1:编写for循环,利用索引遍历出每一个字符 msg = 'hello egon 666' msg = 'hello egon 666' i = 0 for i in range(0, len(msg ...

  4. Basic-Paxos协议日志同步应用

    使用Basic-Paxos协议的日志同步与恢复 传统数据库保持服务持续可用通常采用1主N备, 既采取两种日志同步模式: Maximum Availability和Maximum Protection. ...

  5. 【bzoj2242】计算器

    #include<bits/stdc++.h> #define inf 1000000000 using namespace std; typedef long long ll; ?a:g ...

  6. intValue()的用法

    今天看到了Integer的这个方法,有点疑惑,查了下,做下笔记; 1.intValue()是java.lang.Number类的方法,Number是一个抽象类.Java中所有的数值类都继承它.也就是说 ...

  7. 关于ES6(ES2015)开发记坑

    ES2015(以下简称ES6)在开发过程中遇到的问题: 1,必须显示声明变量 //es5中可解释为全局变量 a=5; //es6中报错:a is not defined a=5 2,对于递归调用方式必 ...

  8. KVM(三)I/O 全虚拟化和准虚拟化

    在 QEMU/KVM 中,客户机可以使用的设备大致可分为三类: 1. 模拟设备:完全由 QEMU 纯软件模拟的设备. 2. Virtio 设备:实现 VIRTIO API 的半虚拟化设备. 3. PC ...

  9. 一段js代码的分析

    function foo(a){ console.log(a+b); b=a+2; console.log(a+b); } foo(2); foo(3); var b=3; foo(4); 结果是: ...

  10. 看不到Harbor我也睡不着觉啊

    上午打球,下午陪小孩子看上海科技展,晚上搞定harbor. 完美!!!:) 参考文档: https://www.dwhd.org/20161023_110618.html http://blog.cs ...