4
2
0
2

Redis发布订阅实例

1、概念

Redis 发布订阅 (pub/sub) 是一种消息通信模式:

  • 发送者 (pub) 发送消息
  • 订阅者 (sub) 接收消息

Redis 客户端可以订阅任意数量的频道。

Redis的发布订阅模式本质和传统的MQ的发布订阅类似,但是相对于其它几款MQ产品来说,redis的使用更加便捷,也更加轻量化,不需要单独去搭建集成一套繁重的MQ框架。但缺点也很明显,redis发布的消息不会持久化,所以当某一台服务器出现问题的时候,这个消息会丢失,所以在考虑使用之前要慎重,当前的业务是否对数据一致性要求很高,如果要求很高,还是建议使用MQ产品。

​ 在发布者订阅者模式下,发布者将消息发布到指定的 channel 里面, 凡是监听该 channel 的消费者都会收到同样的一份消息,这种模式类似于是收音机模式,即凡是收听某个频道的听众都会收到主持人发布的相同的消息内容。 此模式常用于群聊天、 群通知、群公告等场景。

发布订阅模式下的几个概念:

  • Publisher: 发布者
  • Subscriber:订阅者
  • Channel: 频道

2、本地redis演示

1. 首先远程启动redis服务启动

2. 启动4个客户端 redis-cli

redis-cli.exe -h ip地址 -p 端口 -a 密码

3. 将其中三个客户端设置监听频道 testChannel

subscribe testChannel

4. 将第四个客户端作为消息发布的客户端,向频道 testChannel发布消息

publish testChannel 'test message'

5. 可以看到另外三个客户端都收到了消息

3、Springboot演示

1、导入spring-boot-starter-data-redis依赖

<!--redis-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 要用redis连接池 必须有pool依赖-->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

2、编写消息监听器,作为消息的订阅者

import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.stereotype.Component;

//普通类--》reids监听类
@Slf4j
@Component
public class MyListener implements MessageListener {

    // 接受到 redis消息的时候,编写对应业务逻辑
    @Override
    public void onMessage(Message message, byte[] bytes) {
        log.info("接收到了消息:{}", message);
    }
}

3、编写订阅发布模式的容器配置

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;

/**
 * 订阅发布模式的容器配置
 */
@Configuration
@AutoConfigureAfter({MyListener.class})
public class SubscriberConfig {

    // 当前监听器为,上方编写的自定义监听器
    @Autowired
    private MyListener myListener;

    /**
     * 创建消息监听容器
     *
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    public RedisMessageListenerContainer getRedisMessageListenerContainer(RedisConnectionFactory redisConnectionFactory) {
        RedisMessageListenerContainer redisMessageListenerContainer = new RedisMessageListenerContainer();
        redisMessageListenerContainer.setConnectionFactory(redisConnectionFactory);

        //可以添加多个监听订阅频道
        //当前监听的是通道:MYTOPIC
        redisMessageListenerContainer.addMessageListener(new MessageListenerAdapter(myListener), new PatternTopic("MYTOPIC"));

        return redisMessageListenerContainer;
    }
}

4、编写消息发布者

1、通过redis客户端发送消息

192.168.1.101:16380> publish MYTOPIC 'redis-cli testMessage'

2、通过Java代码发送消息

public class RedisTest {
    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    public void testSendToRedis(){
        redisTemplate.convertAndSend("MYTOPIC", "redis-cli testMessage123");
    }
}

4、案例

需求

  1. 为了保证短信发送服务的可用性,在短信发送服务启动时会自动生成当前服务实例的一个uuid作为服务标识保存到redis中,并且每隔3分钟上报服务信息证明服务状态正常
  2. 短信发送服务启动后会每隔10分钟检查redis中的服务上报信息,如果某个实例超过5分钟没有上报则认为此服务下线,就会从redis中将此服务实例信息删除
  3. 短信发送服务在启动时会从数据库中查询出可用通道列表并按照优先级排序,然后缓存到redis中

代码

在短信通道管理中,优先级顺序改变后,调用下面的方法

//  发送消息,通知短信发送服务更新(redis)内存中的通道优先级
public void sendUpdateMessage() {
    // 1. 获取存活的发送端
    Map map = redisTemplate.opsForHash().entries("SERVER_ID_HASH");
    log.info("全部发送端有:", map);
    // 获取当前时间
    long currentTimeMillis = System.currentTimeMillis();
    for (Object key : map.keySet()) {
        Object value = map.get(key);
        long lastTimeMillis = Long.parseLong(value.toString());
        // 如果一个实例没有超过5分钟上报,则认为此服务还在线
        if (currentTimeMillis - lastTimeMillis < (1000 * 60 * 5)) {
            // 2. 删除已经缓存的通道优先级
            redisTemplate.delete("listForConnect");
            // 3. 通知发送端 优先级变了,重新查询mysql
            ServerTopic serverTopic = ServerTopic.builder().option(ServerTopic.INIT_CONNECT).value(key.toString()).build();
            redisTemplate.convertAndSend("TOPIC_HIGH_SERVER", serverTopic.toString());
            return;
        }
    }
}

ServerTopic代码

import com.alibaba.fastjson.JSON;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ServerTopic {
    public static final String INIT_CONNECT = "INIT_CONNECT";
    public static final String USE_NEW_CONNECT = "USE_NEW_CONNECT";

    private String option;
    private String value;

    public static ServerTopic load(String deserialize) {
        return JSON.parseObject(deserialize, ServerTopic.class);
    }

    public String toString() {
        return JSON.toJSONString(this);
    }
}
posted @ 2021-09-29 11:48  CoderTL  阅读(212)  评论(0编辑  收藏  举报