Redis 发布订阅(Pub/Sub)

 发布/订阅 Pub/Sub

  发布订阅 的特点是订阅者(listener)负责订阅频道(channel),发送者(publisher)负责向频道发送二进制字符串消息(binary string message)。每当有消息被发送至给定频道时,频道的所有订阅者都会收到消息。(订阅者可以订阅多个频道,发送者可以在任何频道发送消息)

  发布订阅 依赖于即时消息的广播(即,如果没有听,则错过一条消息),没有对消息持久化。

  在org.springframework.data.redis.connection和org.springframework.data.redis.listener软件包提供了对Redis的消息的核心功能

  

 1、发布Pub:

  发布消息,可以使用底层的 RedisConnection ,也可使用高级的 RedisTemplateRedisConnection需要原始数据(字节数组),RedisTemplate可以让任意对象作为消息发布,如:

// send message through connection RedisConnection con = ...
byte[] msg = ...
byte[] channel = ...
con.publish(msg, channel);

 // send message through RedisTemplate
RedisTemplate template = ...
template.convertAndSend("hello!", "world");

  RedisTemplate 的 converAndSend 还是调用的 RedisConnection publish方法

    @Override
    public void convertAndSend(String channel, Object message) {

        Assert.hasText(channel, "a non-empty channel is required");

        byte[] rawChannel = rawString(channel);
        byte[] rawMessage = rawValue(message);

        execute(connection -> {
            connection.publish(rawChannel, rawMessage);
            return null;
        }, true);
    }
View Code

  RedisUtil 工具类发布方法:

 /**
     * 发布消息
     *
     * @param channel
     * @param message
     */
    public static void publish(String channel, String message) {
        try {
            redisTemplate.convertAndSend(channel, message);
        } catch (Exception e) {
            LOGGER.error("publish error:" + e.getMessage(), e);
        }
    }

  2、订阅Sub:

  在接收方,可以通过直接命名一个频道(channel) 来订阅单个channel,也可通过 模式匹配(pattern matching)的方式同时订阅多个channel。通过模式匹配的方式比较有用,因为不仅允许使用一个命令创建多个订阅,而且还可以监听在订阅时尚未创建的频道(只要模式匹配就行)

  底层的 RedisConnection 提供 subscribe 和 pSubscribe 映射Redis命令以分别按频道或模式进行订阅的方法。可以同时传多个频道或多个匹配模式。 RedisConnection还提供了 getSubscription 来修改连接的订阅,isSubscribed 来查询是否在订阅中。

  为了订阅消息,需要实现 MessageListener 回调接口(org.springframework.data.redis.connection.MessageListener),每当消息到达时,都会调用回调,并运行 onMessage 方法。此接口不仅提供对实际消息的访问,还提供对接收消息的频道(channel)或用于匹配频道的模式的访问。

   注意:Spring Data Redis中的订阅命令是阻塞的。也就是说,在连接上调用subscribe会导致当前线程在开始等待消息时阻塞。只有在取消订阅时才会释放线程,如何取消订阅呢?需要另一个线程在同一连接上调用unsubscribepUnsubscribe

  

  Message Listener Containers (消息订阅者容器)

  由于订阅命令的阻塞性,因此每个订阅者(监听器)都需要进行连接和线程管理。因此Spring提供了 Message Listener Container

  RedisMessageListenerContainer 充当消息订阅者容器,它用于从Redis channel 接收消息并驱动注入到该channel的 MessageListener 实例。

  消息订阅容器 负责所有接收消息的线程,并将消息分发到 listener 中进行处理。消息分发时使用java.util.concurrent.Executor (or Spring’s TaskExecutor) 实现异步分发。

  

  使用消息订阅者容器订阅消息:

    1)定义消息订阅者(消息监听器),必须实现 MessageListener 接口

/**
 * 商品更新订阅
 */
public class ProductUpdateListener implements MessageListener {

    @Override
    public void onMessage(Message message, byte[] pattern) {
        String body = new String(message.getBody());
        String channel = new String(message.getChannel());
        String pattern_ = new String(pattern);
    }

}

  

    2)RedisConfig 中添加下面的配置,将 TopicMessageListener 注册到 RedisMessageListenerContainer 中,这样,当 Topic 上有消息时,由 RedisMessageListenerContainer 通知 MessageListener,客户端通过 onMessage 拿到消息后,进行处理。

@Bean
    public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory redisConnectionFactory) {
        // redis 消息订阅(监听)者容器
        RedisMessageListenerContainer messageListenerContainer = new RedisMessageListenerContainer();
        messageListenerContainer.setConnectionFactory(redisConnectionFactory);
        // pattern matching
        messageListenerContainer.addMessageListener(new ProductUpdateListener(), new PatternTopic("*.product.update"));
        // channel name
        // messageListenerContainer.addMessageListener(new ProductUpdateListener(), new ChannelTopic("redis.mishop.ott.product.update"));
        return messageListenerContainer;
    }

   MessageListenerAdapter (消息订阅适配器,非必须,不建议使用)

  消息订阅适配器不是必须的,它用于 适配没有实现 MessageListener的消息订阅者 和消息订阅者容器的绑定。

  通过消息订阅适配器我们可以自定义消息订阅者 不必实现 MessageListener ,及自定义消息处理方法。

   1)定义消息订阅者(消息监听器)

public class ProductChangeListener {
    /**
     * 自定义消息处理方法
     * 注意message参数的类型为String
     *
     * @param message
     */
    public void handleMessage(Map message, String channel) {
        // do something
    }
}

  2)RedisConfig 中添加下面的配置

@Bean
    public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory redisConnectionFactory) {
        // redis 消息订阅(监听)者容器
        RedisMessageListenerContainer messageListenerContainer = new RedisMessageListenerContainer();
        messageListenerContainer.setConnectionFactory(redisConnectionFactory);
        // messageListenerContainer.addMessageListener(messageListenerAdapter(), new ChannelTopic("redis.mishop.ott.product.update"));
        messageListenerContainer.addMessageListener(messageListenerAdapter(), new PatternTopic("*.product.update"));
        return messageListenerContainer;
    }

    /**
     * 消息订阅者适配器:绑定消息监听者和接收监听的方法 (必须注入此适配器,否则报错)
     * ProductChangeListener:消息订阅者;handleMessage:订阅者自定义的消息处理方法
     */
    @Bean
    public MessageListenerAdapter messageListenerAdapter() {
        return new MessageListenerAdapter(new ProductChangeListener(), "handleMessage");
    }

适用场景:

  由于没有消息持久化与 ACK 的保证,所以,Redis 的发布订阅功能并不可靠(建议和定时任务配合使用)。这也就导致了它的应用场景很有限,建议用于实时与可靠性要求不高的场景。 例如:

    消息推送
    内网环境的消息通知
    ...

   又由于当消息发送到指定channel时,所有的订阅者都会收到消息通知。在集群环境下,一般我们只想集群其中一个节点收到通知,除非每个节点都使用了JVM缓存,订阅的消息用于更新JVM缓存(如:static final ConcurrentHashMap)

END.

原文地址:https://www.cnblogs.com/yangyongjie/p/14355833.html