实现发布订阅的三种方式

一、Redis

Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。

Redis 客户端可以订阅任意数量的频道。

redis 127.0.0.1:6379> SUBSCRIBE runoobChat
Reading messages... (press Ctrl-C to quit)
1) "subscribe"    # 返回值的类型:显示订阅成功
2) "runoobChat"    # 订阅的频道
3) (integer) 1    # 目前已订阅的模式的数量

现在,我们先重新开启个 redis 客户端,然后在同一个频道 runoobChat 发布两次消息,订阅者就能接收到消息。

redis 127.0.0.1:6379> PUBLISH runoobChat "Redis PUBLISH test"
(integer) 1
redis 127.0.0.1:6379> PUBLISH runoobChat "Learn redis by runoob.com"
(integer) 1

# 订阅者的客户端会显示如下消息
1) "message"    # 返回值的类型:信息
2) "runoobChat"    # 信息本身的目标频道
3) "Redis PUBLISH test"    # 信息的内容
 1) "message"
2) "runoobChat"
3) "Learn redis by runoob.com"

二、RabbitMQ

RabbitMQ的发布订阅使用交换机类型:Fanout,也称为广播。
声明Exchange,不再声明Queue。发送消息到Exchange,不再发送到Queue。

private static String EXCHANGE_NAME="hello111";
    @Autowired
    AmqpTemplate amqpt;

    @RequestMapping(value="test")
    public void test() throws InterruptedException{

        for(int i=0;i<10;i++){
            Thread.sleep(i*20);
            amqpt.convertAndSend(EXCHANGE_NAME,"fanout", "hello word"+i);
        }
    System.err.println("消息发送成功");
    }

在生产者声明Exchange,EXCHANGE_NAME="hello111",使用Fanout类型的交换机,这里不声明Queue。
在消费者指定接收的交换器名字。

@Configuration
public class Confi {
      private static String EXCHANGE_NAME="hello111";
      //声明两个队列
    @Bean
    public Queue queue(){
        return new Queue("hello");
    }

    @Bean
    public Queue queue1(){
        return new Queue("hello1");
    }
    //声明一个fanout的交换机
    @Bean
    public FanoutExchange fanoutExchange(){
        return new FanoutExchange(EXCHANGE_NAME);   
    }
    //将队列和交互机进行绑定
    @Bean
    public Binding bindfanout(Queue queue,FanoutExchange fanoutExchange){
        return BindingBuilder.bind(queue).to(fanoutExchange);

    }

    @Bean
    public Binding bindfanout1(Queue queue1,FanoutExchange fanoutExchange){
        return BindingBuilder.bind(queue1).to(fanoutExchange);

    }
}

@Service
@RabbitListener(queues="hello") //这个队列就是我们之前定义的队列名称,监听hello队列的消息
public class Receive1 {

    @RabbitHandler 
    public void receive(String message) throws InterruptedException{

        System.err.println("1:收到消息"+message);

    }
}

@Service
@RabbitListener(queues="hello1")
public class Receive2 {

    @RabbitHandler
    public void receive(String message) throws InterruptedException{
        System.err.println("2收到消息:"+message);
    }
}

三、ZooKeeper

ZooKeeper本身有发布订阅机制,当某个Znode发生变化时,所有监听这个Znode的客户端都会受到消息。

先创建一个Znode:

然后启动两个ZooKeeper客户端,主要监听代码如下:

import java.util.concurrent.CountDownLatch;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.Watcher.Event.EventType;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
 
/**
 * 分布式配置中心demo
 * @author 
 *
 */
public class ZooKeeperProSync implements Watcher {
 
    private static CountDownLatch connectedSemaphore = new CountDownLatch(1);
    private static ZooKeeper zk = null;
    private static Stat stat = new Stat();
 
    public static void main(String[] args) throws Exception {
        //zookeeper配置数据存放路径
        String path = "/username";
        //连接zookeeper并且注册一个默认的监听器
        zk = new ZooKeeper("192.168.31.100:2181", 5000, //
                new ZooKeeperProSync());
        //等待zk连接成功的通知
        connectedSemaphore.await();
        //获取path目录节点的配置数据,并注册默认的监听器
        System.out.println(new String(zk.getData(path, true, stat)));
 
        Thread.sleep(Integer.MAX_VALUE);
    }
 
    public void process(WatchedEvent event) {
        if (KeeperState.SyncConnected == event.getState()) {  //zk连接成功通知事件
            if (EventType.None == event.getType() && null == event.getPath()) {
                connectedSemaphore.countDown();
            } else if (event.getType() == EventType.NodeDataChanged) {  //zk目录节点数据变化通知事件
                try {
                    System.out.println("配置已修改,新值为:" + new String(zk.getData(event.getPath(), true, stat)));
                } catch (Exception e) {
                }
            }
        }
    }
}

两个程序启动后都正确的读取到了zookeeper的/username目录节点下的数据'qingfeng'。

然后在zookeeper里修改下目录节点/username下的数据:

修改完成后,我们看见两个监听客户端都及时收到了他们监听的目录节点数据变更后的值,如下所示:

四、三种发布订阅的对比

Redis的PUBLISH和SUBSCRIBE的缺陷在于客户端必须一直在线才能接收到消息,断线可能会导致客户端丢失消息,除此之外,旧版的redis可能会由于订阅者消费不够快而变的不稳定导致崩溃,甚至被管理员杀掉。
Redis订阅发布简单,轻量级,延迟比较低,适合业务量不大非核心的一些订阅功能。

RabbitMQ应该是最合适也是最可靠的发部订阅方案。
RabbitMQ有发送方确认模式、接收方确认机制、消息持久化等机制确保方案的可行性。

ZooKeeper不适宜做发部订阅,虽然本质上zookeeper=文件系统+监听通知机制,但是主要应用场景如下图:

转载文章Redis 发布订阅rabbitmq-----发布订阅模式Zookeeper入门看这篇就够了Zookeeper典型应用场景篇

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值