Redis实现发布订阅功能


前言

本文通过Redis实现类似于消息中间件MQ的消息队列功能,生产者消费者Topic(消息通道)
生产者生产消息,消费者通过订阅的Topic去消费消息。


一、生产者、消费者、消息通道?

  • 生产者:用于发送消息到消息中介。
  • 消费者:用于从消息中介获得消息并交给业务系统使用。
  • 消息通道:可以理解为Topic,两者之间的中介,生产者生产消息给对应的消息通道,消费者通过订阅相应的消息通道来消费消息

示例:

【生产者】A:生产了一段消息,对应消息通道为 8
【消费者】B:订阅了 8 消息通道
【消费者】C:订阅了 9 消息通道

此时:这条消息只有订阅了消息通道 8 的消费者B可以正常消费,而消费者C无法消费此消息

二、代码实现

1、引入依赖

<!-- redis 缓存操作 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- pool 对象池 -->
<!-- 使用lettuce客户端需要引入commons-pool2依赖 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

<!-- json 序列化操作 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>2.0.25</version>
</dependency>

2、yml配置

spring:
  # redis 配置
  redis:
    # 地址
    host: 127.0.0.1
    # 端口,默认为6379
    port: 6379
    # 密码
    password: ''
    # 连接超时时间
    timeout: 10s
    lettuce:
      pool:
        # 连接池中的最小空闲连接
        min-idle: 0
        # 连接池中的最大空闲连接
        max-idle: 8
        # 连接池的最大数据库连接数
        max-active: 8
        # #连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: -1ms

3、消息接收配置类创建(消费者)

3.1 消息监听器(实现MessageListener方式)

注意MessageListener引入包为:import org.springframework.data.redis.connection.MessageListener;

/**
 * 实现MessageListener接口方式
 */
@Component
public class DemoListener implements MessageListener {
    
    

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     *
     * @param message:消息对象
     *          获取消息通道:message.getChannel()
     *          获取消息体:message.getBody()
     * @param pattern:消息体,实际就是message对象里的getChannel
     */
    @Override
    public void onMessage(Message message, byte[] pattern) {
    
    
        System.err.println("实现MessageListener接口。。。。。");
//        System.out.println("消息通道:"+new String(pattern));
        //获取通道名称
        String channel = (String) redisTemplate.getStringSerializer().deserialize(message.getChannel());
        String body = (String) redisTemplate.getStringSerializer().deserialize(message.getBody());
        Map<String,Object> parse = (Map<String, Object>) JSON.parse(body);
        System.out.println("消息通道为:"+channel);
        System.out.println("收到的消息为:"+parse);
    }
}

3.2 创建消息适配器(MessageListenerAdapter)

/**
 * Redis消息适配器
 */
@Component
public class DemoAdapter {
    
    

    /**
     * AA通道反射方法
     * @param message:消息体
     */
    public void onMessage(String message,String channel){
    
    
        Map<String,Object> map = (Map<String, Object>) JSON.parse(message);
        Object name = map.get("name");
        System.out.println("onMessage通道为:" + channel);
        System.out.println("onMessage监听到消息:" + map );
    }

    /**
     * BB通道反射方法
     * @param message:消息体
     */
    public void onMessage2(String message,String channel){
    
    
        System.out.println("onMessage2通道为:" + channel);
        System.out.println("onMessage2监听到消息:" + message);
    }

}

4、RedisConfig配置

/**
 * RedisConfig配置
 */
@Configuration
public class RedisConfig {
    
    

    /**
     * 自定义配置RedisTemplate
     * @param factory
     * @return
     */
    @Bean
    public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){
    
    
        RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(factory);
        //String的序列化
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        //key采用string的序列化
        redisTemplate.setKeySerializer(stringRedisSerializer);
        //hash的key采用string的序列化
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        //value序列化采用jackson
        redisTemplate.setValueSerializer(stringRedisSerializer);
        //将 Hash 的值序列化为字符串类型,注意存入的时候转成JSON字符串
        redisTemplate.setHashValueSerializer(stringRedisSerializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }


    /**
     *
     * @param factory:redis工厂类
     * @param listenerAdapter1:消息适配器1
     * @param listenerAdapter2:消息适配器2
     *         -此处的设配器名称需要跟下方@Bean注入的适配器名称相同,多个适配器的话会出现注入多个Bean的情况
     *         -或者使用@Qualifier("messageListenerAdapter1")注解来确定要将哪一个 Bean 注入到方法中。
     * @param demoListener:消息监听器(实现MessageListener接口的类)
     * @return
     */
    @Bean
    public RedisMessageListenerContainer container(RedisConnectionFactory factory,
                                                   MessageListenerAdapter listenerAdapter1,
                                                   MessageListenerAdapter listenerAdapter2,
                                                   DemoListener demoListener){
    
    
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(factory);
        container.addMessageListener(listenerAdapter1,new PatternTopic("AA"));
        container.addMessageListener(listenerAdapter2,new PatternTopic("BB"));
        container.addMessageListener(demoListener,new PatternTopic("CC"));
//        container.setTopicSerializer(geterializer());//不需要设置序列化方式,在上方redisTemplate配置中已经设置了
        return container;
    }

    /**
     * TODO 注意:此处不能设置序列化对象,否则无法收到消息
     * 设置Redis消息接收设配器1
     * @param adapter:消息监听的类
     * @param onMessage:消息监听类中的方法名称
     * @return
     */
    @Bean
    public MessageListenerAdapter listenerAdapter1(DemoAdapter adapter){
    
    
        MessageListenerAdapter listenerAdapter = new MessageListenerAdapter(adapter,"onMessage");
        return listenerAdapter;
    }

    /**
     * TODO 注意:此处不能设置序列化对象,否则无法收到消息
     * 设置Redis消息接收设配器1
     * @param adapter:消息监听的类
     * @param onMessage:消息监听类中的方法名称
     * @return
     */
    @Bean
    public MessageListenerAdapter listenerAdapter2(DemoAdapter adapter){
    
    
        MessageListenerAdapter listenerAdapter = new MessageListenerAdapter(adapter,"onMessage2");
        return listenerAdapter;
    }

    /**
     * TODO 设置序列化对象
     * @return
     */
//    public Jackson2JsonRedisSerializer geterializer(){
    
    
//        Jackson2JsonRedisSerializer seria = new Jackson2JsonRedisSerializer(Object.class);
//        ObjectMapper objectMapper = new ObjectMapper();
//        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
//        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
//        seria.setObjectMapper(objectMapper);
//        return seria;
//    }

}

4、消息推送测试(生产者)

生产者非常简单,只需要通过RedisTemplate.convertAndSend()这个方法向某个通道(参数1)推送一条消息(第二个参数)

@Component
public class RedisTest {
    
    

    @Autowired
    RedisTemplate redisTemplate;

    /**
     * 初始化线程
     */
    @Bean
    public void init(){
    
    
        new Thread(new MyRun(redisTemplate)).start();
    }

    /**
     * 创建线程
     */
    class MyRun implements Runnable{
    
    

        private RedisTemplate redisTemplate;

        public MyRun(RedisTemplate redisTemplate) {
    
    
            this.redisTemplate = redisTemplate;
        }

        @Override
        public void run() {
    
    
            System.err.println("服务已启动。");
            while (true){
    
    
                Scanner scanner = new Scanner(System.in);
                System.out.println("输入1发送到AA,输入2发送到BB,输入3发送到CC,输入99退出");
                int i = scanner.nextInt();
                if (i==1){
    
    
                    HashMap<Object, Object> map = new HashMap<>();
                    map.put("name","张三");
                    redisTemplate.convertAndSend("AA",JSON.toJSONString(map));
                }else if (i == 2){
    
    
                    HashMap<Object, Object> map = new HashMap<>();
                    map.put("name","李四");
                    redisTemplate.convertAndSend("BB",JSON.toJSONString(map));
                }else if (i == 3){
    
    
                    HashMap<Object, Object> map = new HashMap<>();
                    map.put("name","王五");
                    redisTemplate.convertAndSend("CC",JSON.toJSONString(map));
                }else if (i==99){
    
    
                    break;
                }

            }
        }
    }
}

5、测试结果

  1. AA通道绑定的是适配器1
  2. BB通道绑定的是适配器2
  3. CC通道绑定的是实现MessageListener接口的类(文中的DemoListener)

测试结果就不放了,大家可以去试试,自己去实践一遍会发现很容易,有错误请指出。


结束

猜你喜欢

转载自blog.csdn.net/SmallCat0912/article/details/129201041