Redis - 消息發佈訂閱機制

Redis 發佈訂閱(pub/sub)是一種消息通信模式:發送者(pub)發送消息,訂閱者(sub)接收消息。發佈者生產消息放到隊列裏,多個監聽隊列的消費者都會收到同一份消息。
Redis客戶端可以訂閱任意數量的頻道

訂閱/發佈消息圖

在這裏插入圖片描述
下圖展示了頻道 channel1 , 以及訂閱這個頻道的三個客戶端 —— client2 、 client5 和 client1 之間的關係:
在這裏插入圖片描述
當有新消息通過 PUBLISH 命令發送給頻道 channel1 時, 這個消息就會被髮送給訂閱它的三個客戶端:
在這裏插入圖片描述

相關命令

在這裏插入圖片描述

發佈訂閱命令 - 測試

1、開啓一個客戶端進行監聽頻道信息 - 接收端

127.0.0.1:6379> SUBSCRIBE lcy          # 監聽名爲lcy的頻道
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "lcy"
3) (integer) 1

2、開啓另外一個客戶端發佈消息 - 發送端

127.0.0.1:6379> PUBLISH lcy "publish in lcy"   # 在lcy頻道發佈"publish in lyc"消息
(integer) 1

3、查看第一個客戶端接收到的消息 - 接收端

1) "message"
2) "lcy"
3) "publish in lcy"

原理

Redis是使用C實現的,通過分析 Redis 源碼裏的pubsub.c文件,瞭解發佈和訂閱機制的底層實現,籍此加深對 Redis 的理解。
Redis 通過 PUBLISH 、SUBSCRIBE 和 PSUBSCRIBE 等命令實現發佈和訂閱功能。

通過SUBSCRIBE命令訂閱某頻道後,redis-server 裏維護了一個字典,字典的鍵就是一個個 頻道。, 而字典的值則是一個鏈表,鏈表中保存了所有訂閱這個 channel 的客戶端。SUBSCRIBE 命令的關鍵, 就是將客戶端添加到給定 channel 的訂閱鏈表中。

通過PUBLISH命令向訂閱者發送消息,redis-server會使用給定的頻道作爲鍵,在它所維護的 channel 字典中查找記錄了訂閱這個頻道的所有客戶端的鏈表,遍歷這個鏈表,將消息發佈給所有訂閱者。

Pub/Sub 從字面上理解就是發佈(Publish)與訂閱(Subscribe),在Redis中,你可以設定對某一個 key值進行消息發佈及消息訂閱,當一個key值上進行了消息發佈後,所有訂閱它的客戶端都會收到相應 的消息。這一功能明顯的用法就是用作實時消息系統,比如普通的即時聊天,羣聊等功能。

使用場景

1、實時聊天系統
2、訂閱、關注系統
其實更多時候都是使用MQ(消息中間件)來做的。

SpringBoot + SpringDataRedis實現發佈/訂閱

1、引入Redis的依賴

 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-data-redis</artifactId>
 </dependency>

2、配置文件

@Configuration
public class RedisSubListenerConfig {
    @Bean
    public RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
                                            MessageListenerAdapter listenerAdapter) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        // 這個container 可以添加多個 messageListener
        // 訂閱了一個叫lcy的頻道(通道)
        container.addMessageListener(listenerAdapter, new PatternTopic("lcy"));
//        container.addMessageListener(listenerAdapter, new PatternTopic("jyqc"));
        return container;
    }

    /**
     * 利用反射來創建監聽到消息之後的執行方法
     */
    @Bean
    public MessageListenerAdapter listenerAdapter(RedisConsumer redisReceiver) {
        // 這個地方 是給messageListenerAdapter 傳入一個消息接受的處理器,利用反射的方法調用“receiveMessage”
        // 也有好幾個重載方法,默認調用處理器的方法 叫handleMessage
        return new MessageListenerAdapter(redisReceiver, "receiveMessage");
    }

    /**
     * RedisTemplate模板
     * @param factory Redis連接工廠
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory){
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        // 使用Jackson進行序列化配置
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
//        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        // 如果enableDefaultTyping過期(SpringBoot後續版本過期了),則使用下面這個代替
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        // String序列化
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key採用String的序列方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key也採用String的序列方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value序列化採用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化也採用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
}

3、創建訂閱者 - 消息處理類

@Service
public class RedisConsumer {

    public void receiveMessage(Object message) {
        System.out.println("收到消息:");
        System.out.println(message);
    }
}

4、創建發佈者 - 消息發送類

@Service
public class RedisSender {
    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 向頻道發送消息的方法
     * @param channel 頻道名
     * @param message 消息內容
     */
    public void sendChannelMess(String channel, Object message) {
        redisTemplate.convertAndSend(channel, message);
    }
}

5、測試類

@Autowired
private RedisSender redisSender;

@Test
public void test(){
    User user = new User("柳成蔭","男");
    redisSender.sendChannelMess("lcy",user);
}

結果:
在這裏插入圖片描述
我沒有使用過Redis的這個功能,一般都用MQ來完成這個功能。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章