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 ,也可使用高级的 RedisTemplate。RedisConnection需要原始数据(字节数组),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); }
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会导致当前线程在开始等待消息时阻塞。只有在取消订阅时才会释放线程,如何取消订阅呢?需要另一个线程在同一连接上调用unsubscribe或pUnsubscribe。
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 中添加下面的配置,将 Topic 和 MessageListener 注册到 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.