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來完成這個功能。