发布订阅模式使用
Redis提供了发布订阅功能,可以用于消息的传输
Redis的发布订阅机制包括三个部分,publisher,subscriber和Channel
发布者和订阅者都是Redis客户端,Channel则为Redis服务器端。
发布者将消息发送到某个的频道,订阅了这个频道的订阅者就能接收到这条消息。
1.1指令详情
- SUBSCRIBE/PSUBSCRIBE: 订阅,精确或者按匹配订阅
- UNSUBSCRIBE/PUNSUBSCRIBE: 退订,精确、或者按匹配符
- PUBLISH: 发送
- PUBSUB : 查看消息列表
频道/模式的订阅与退订
subscribe:订阅 subscribe channel1 channel2 …
Redis客户端1订阅频道1和频道2
192.168.139.187:0>SUBSCRIBE channel1 channel2
切换到推送/订阅模式,关闭标签页来停止接收信息。
1) "subscribe"
2) "channel1"
3) "1"
publish:发布消息 publish channel message
Redis客户端2将消息发布在频道1和频道2上
192.168.139.187:0>PUBLISH channel1 channel1msg
"1"
192.168.139.187:0>PUBLISH channel2 channel2msg
"1"
订阅端显示发送的信息
1) "message"
2) "channel1"
3) "channel1msg"
1) "message"
2) "channel2"
3) "channel2msg"
unsubscribe: 退订 channel
Redis客户端1退订频道1
192.168.139.187:0>UNSUBSCRIBE channel1
1) "unsubscribe"
2) "channel1"
3) "0"
psubscribe : 模式匹配 psubscribe +模式
Redis客户端1订阅所有以ch开头的频道
192.168.139.187:0>PSUBSCRIBE ch*
切换到推送/订阅模式,关闭标签页来停止接收信息。
1) "psubscribe"
2) "ch*"
3) "1"
Redis客户端2发布信息在频道5上
192.168.139.187:0>PUBLISH ch5 channelall
"1"
Redis客户端1收到频道5的信息
1) "pmessage"
2) "ch*"
3) "ch5"
4) "channelall"
punsubscribe 退订模式
192.168.139.187:0>punsubscribe ch*
1) "punsubscribe"
2) "ch*"
3) "0"
使用场景
在Redis哨兵模式中,哨兵通过发布与订阅的方式与Redis主服务器和Redis从服务器进行通信
Redisson是一个分布式锁框架,在Redisson分布式锁释放的时候,是使用发布与订阅的方式通知的
注:重业务的消息,推荐用消息队列
整合springboot 使用订阅频道
引入pom 我这里使用了jedis 作为redis链接池, springboot 2.0以后默认使用了lettuce
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<!--这里排除了 lettuce-->
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
监听器代码
/**
* @Author : 清风冷影
* @Description: redis消息队列配置-订阅者
* @Date : 2021/11/14 20:48
*/
@Slf4j
@Configuration
public class RedisMessageListener {
ThreadPoolTaskScheduler taskScheduler;
/**
* 创建连接容器
* @param connectionFactory 链接工厂在spring容器中已经存在 如果是自己配置spring data
* 与redis整合是需要自己配置连接工厂,但是spring boot已经帮我们配置好了,所以可以直接注入。
* @param listenerAdapter 使用消息监听适配器 这里我创建了两个 分别监听两个通道
* @return
*/
@Bean
public RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
MessageListenerAdapter listenerAdapter,
MessageListenerAdapter listenerAdapter2) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.setTaskExecutor(redisTaskScheduler());
//接受消息的key
container.addMessageListener(listenerAdapter, new PatternTopic("channel:message1"));
container.addMessageListener(listenerAdapter2, new PatternTopic("channel:message2"));
return container;
}
/**
* 绑定消息监听者和接收监听的方法
* @param receiver
* @return
*/
@Bean
public MessageListenerAdapter listenerAdapter(ReceiverRedisMessage receiver){
// 这里对应ReceiverRedisMessage 的方法
return new MessageListenerAdapter(receiver,"receiveMessage1");
}
/**
* 绑定消息监听者和接收监听的方法
* @param receiver
* @return
*/
@Bean
public MessageListenerAdapter listenerAdapter2(ReceiverRedisMessage receiver){
return new MessageListenerAdapter(receiver,"receiveMessage2");
}
/**
* 注册订阅者
* @param latch
* @return
*/
@Bean
ReceiverRedisMessage receiver(CountDownLatch latch) {
return new ReceiverRedisMessage(latch);
}
/**
* 计数器,用来控制线程
* @return
*/
@Bean
public CountDownLatch latch(){
return new CountDownLatch(1);//指定了计数的次数 1
}
/**
* 非常关键不添加线程一直往上累加 不销毁
* @return
*/
@Bean
public ThreadPoolTaskScheduler redisTaskScheduler(){
if(taskScheduler != null){
return taskScheduler;
}
log.info("初始化线程池");
taskScheduler = new ThreadPoolTaskScheduler();
//设置线程数
taskScheduler.setPoolSize(10);
return taskScheduler;
}
}
/**
* @Author : 清风冷影
* @Description: 注入消息的接受类
* @Date : 2021/11/14 20:52
*/
@Slf4j
public class ReceiverRedisMessage {
private CountDownLatch latch;
@Autowired
public ReceiverRedisMessage(CountDownLatch latch) {
this.latch = latch;
}
/**
* 队列消息接收方法
*
* @param jsonMsg
*/
public void receiveMessage1(String jsonMsg) {
log.info("[开始消费REDIS消息队列channel:message1数据...]");
try {
System.out.println(jsonMsg);
log.info("[消费REDIS消息队列channel:message1数据成功.]");
} catch (Exception e) {
log.error("[消费REDIS消息队列channel:message1数据失败,失败信息:{}]",
e.getMessage());
}
latch.countDown();
}
/**
* 队列消息接收方法
*
* @param jsonMsg
*/
public void receiveMessage2(String jsonMsg) {
log.info("[开始消费REDIS消息队列channel:message2数据...]");
try {
System.out.println(jsonMsg);
/**
* 此处执行自己代码逻辑 例如 插入 删除操作数据库等
*/
log.info("[消费REDIS消息队列channel:message2数据成功.]");
} catch (Exception e) {
log.error("[消费REDIS消息队列channel:message2数据失败,失败信息:{}]",
e.getMessage());
}
latch.countDown();
}
}
启动项目,测试订阅模式数据接受情况
使用客户端发布消息
发布数据
192.168.139.187:0>PUBLISH channel:message1 message1
"2"
接收到数据
2021-11-14 21:43:45.136 INFO 8332 --- [e8-d53abaebe5ae] c.a.n.client.config.impl.ClientWorker : get changedGroupKeys:[]
2021-11-14 21:44:06.284 INFO 8332 --- [TaskScheduler-4] c.s.g.p.listener.ReceiverRedisMessage : [开始消费REDIS消息队列channel:message1数据...]
message1
2021-11-14 21:44:06.285 INFO 8332 --- [TaskScheduler-4] c.s.g.p.listener.ReceiverRedisMessage : [消费REDIS消息队列channel:message1数据成功.]
使用程序发布消息
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest(classes = RedisTestApplication.class)
public class PublishTest {
@Test
public void testPublish() {
stringRedisTemplate.convertAndSend("channel:message2","message2");
}
}
订阅到消息
2021-11-14 21:48:36.554 INFO 8332 --- [TaskScheduler-8] c.s.g.p.listener.ReceiverRedisMessage : [开始消费REDIS消息队列channel:message2数据...]
message2
2021-11-14 21:48:36.554 INFO 8332 --- [TaskScheduler-8] c.s.g.p.listener.ReceiverRedisMessage : [消费REDIS消息队列channel:message2数据成功.]