Redis实现发布订阅功能

本文介绍了如何利用Redis来构建类似消息中间件的功能,包括生产者、消费者和Topic的概念。生产者发送消息到特定Topic,消费者通过订阅Topic来接收和处理消息。文中提供了SpringBoot项目中引入相关依赖、配置Redis以及创建消息监听器和适配器的详细代码示例,展示了如何实现实时的消息推送和接收。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


前言

本文通过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)

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


结束

SSM(Spring+Spring MVC+MyBatis)框架和Redis数据库通常可以结合使用以实现发布订阅功能发布订阅模式是一种面向消息的通信方式,多个接收者监听同一个主题,当主题发布消息时,所有接收者都会接收到通知并执行相应的操作。Redis发布订阅功能Redis提供的一种基于事件驱动的消息通知机制,可以让多个客户端同时接收系统事件的通知。 在SSM框架中,可以使用Spring框架的事件驱动机制来实现发布订阅功能。具体地,可以在Spring框架的配置文件中配置一个事件监听器(Event Listener),该监听器会监听指定的事件。当该事件发生时,监听器会接收到事件通知,并执行相应的业务逻辑。例如,可以将Redis的发布消息事件(PUBLISH)作为Spring事件,然后在事件监听器中实现对该事件的处理逻辑。 在Redis中,可以使用PUBLISH命令向指定的频道(Channel)发布消息。多个客户端可以使用SUBSCRIBE命令来订阅指定的频道,当有消息发布到该频道时,所有订阅该频道的客户端都会接收到该消息。 因此,在SSM和Redis实现发布订阅功能的步骤大致可以分为以下几步: 1. 配置Redis连接信息,以便SSM框架能够正确连接到Redis数据库。 2. 在Spring框架的配置文件中定义一个事件监听器,该监听器将监听Redis的发布事件。 3. 在监听器中编写事件处理逻辑,例如向所有订阅了该频道的客户端发送指定的消息。 4. 在应用程序中使用PUBLISH命令向指定的频道发布消息,或使用SUBSCRIBE命令订阅指定的频道。 通过以上步骤,便可以在SSM框架和Redis数据库实现发布订阅功能。该功能可用于实现实时消息推送、事件通知等功能
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值