发布订阅模式使用

Redis提供了发布订阅功能,可以用于消息的传输

Redis的发布订阅机制包括三个部分,publisher,subscriber和Channel

redis 订阅 重新连接处理_缓存


发布者和订阅者都是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数据成功.]