使用Redis Topic广播模式,实现类似mq消息的发布和订阅

一、背景

不同的mq中间件,不仅支持发送队列消息,也都可以发送广播消息。
有时候,我们的业务逻辑需要发送广播消息,却不想引入mq中间件时,推荐你使用redis的topic机制来实现。

原因是:它比较轻量级,大多数的项目都会依赖redis数据库,对部署的依赖开销变小。

本文就如何实现redis发布和订阅广播消息作一个示例,希望可以帮助到你。

二、典型的生产者/消费者

在这里插入图片描述
所以,我们有以下几个角色:

  • 消息体/事件
  • 生产者
  • 消费者

三、代码实现

1、配置监听


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
import org.springframework.data.redis.serializer.RedisSerializer;


/**
 * @author xxx
 */
@Configuration
public class RedisConfiguration {

    @Bean
    public RedisTemplate<String, String> stringRedisTemplate(RedisConnectionFactory factory) {
        StringRedisTemplate template = new StringRedisTemplate(factory);
        template.setKeySerializer(RedisSerializer.string());
        template.setValueSerializer(RedisSerializer.string());
        template.setHashKeySerializer(RedisSerializer.string());
        template.setHashValueSerializer(RedisSerializer.string());
        template.afterPropertiesSet();
        return template;
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);

        template.setKeySerializer(RedisSerializer.string());
        template.setValueSerializer(RedisSerializer.json());
        template.setHashKeySerializer(RedisSerializer.string());
        template.setHashValueSerializer(RedisSerializer.json());
        template.afterPropertiesSet();

        return template;
    }

    @Bean
    RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
                                            MessageListenerAdapter listenerAdapter) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        // 可以添加多个 messageListener,配置不同的交换机
        // REDIS_TOPIC_REMOVE_DELAY_TASK是一个常量,定义你的队列名称
        container.addMessageListener(listenerAdapter, new PatternTopic(REDIS_TOPIC_REMOVE_DELAY_TASK));
        return container;
    }

    // RemoveDelayTaskEventListener是消息监听者类
    @Bean
    MessageListenerAdapter listenerAdapter(RemoveDelayTaskEventListener removeDelayTaskEventListener) {
        MessageListenerAdapter listenerAdapter = new MessageListenerAdapter(removeDelayTaskEventListener, "onMessage");
        return listenerAdapter;
    }

}

这里我们定义了两个RedisTemplate对象,在发送消息的时候有所区别。

  • RedisTemplate<String, String> stringRedisTemplate 要求redis value是一个string对象
  • RedisTemplate<String, Object> redisTemplate 允许redis value是一个Object对象,会对Object对象自动序列化,不用程序关注。

2、消息生产者

这里介绍两种发送方式:

  • 使用stringRedisTemplate
@Autowired
private RedisTemplate<String, String> stringRedisTemplate;
    
// 需要对对象进行Json序列化JSON.toJSONString()
stringRedisTemplate.convertAndSend(REDIS_TOPIC_REMOVE_DELAY_TASK,
        JSON.toJSONString(RemoveDelayTaskEvent.builder()
                .transNo(transNo)
                .ipAddress(ipAddress)
                .build()));
  • 使用redisTemplate
@Autowired
private RedisTemplate<String, Object> redisTemplate;
    
redisTemplate.convertAndSend(REDIS_TOPIC_REMOVE_DELAY_TASK, RemoveDelayTaskEvent.builder()
                .transNo(transNo)
                .ipAddress(ipAddress)
                .build());

3、消息监听/消费者

无论你上面使用哪种方式发送消息,消息监听者都是需要反序列化。


import cn.hutool.core.net.NetUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.stereotype.Service;


/**
 * redis消息监听者.
 *
 * @author xxx
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class RemoveDelayTaskEventListener implements MessageListener {

    @Override
    public void onMessage(Message message, byte[] bytes) {
        final String topic = new String(message.getChannel());
        final String body = new String(message.getBody());
        if (log.isInfoEnabled()) {
            log.info("订阅redis广播消息, topic={}, body={}", topic, body);
        }
        if (StringUtils.isBlank(body)) {
            return;
        }

        final RemoveDelayTaskEvent event = JSONObject.parseObject(body, RemoveDelayTaskEvent.class);

        final String transNo = event.getTransNo();
        final String ipAddress = event.getIpAddress();

        // 逻辑处理(略)
    }
}

输出的日志示例:

订阅redis广播消息, topic=removeDelayTaskTopic, body={"@class":"com.xxx.application.message.RemoveDelayTaskEvent","transNo":"12345633089","ipAddress":""}

4、消息体RemoveDelayTaskEvent


import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
public class RemoveDelayTaskEvent {
    /**
     * 交易流水号
     */
    private String transNo;

    /**
     * ip地址
     */
    private String ipAddress;
}

四、模拟多个监听者

listenerAdapter1和listenerAdapter2是两个消息监听者,它们都监听同一个topi。(两个消息监听者都将消费同一个消息,也就是广播模式下,一个消息为多个监听者消费)

    @Bean
    RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
                                            MessageListenerAdapter listenerAdapter) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        // 添加消息监听者listenerAdapter1,配置同一个交换机REDIS_TOPIC_REMOVE_DELAY_TASK
        container.addMessageListener(listenerAdapter1, new PatternTopic(REDIS_TOPIC_REMOVE_DELAY_TASK));
        // 添加消息监听者listenerAdapter2,配置同一个交换机REDIS_TOPIC_REMOVE_DELAY_TASK
        container.addMessageListener(listenerAdapter2, new PatternTopic(REDIS_TOPIC_REMOVE_DELAY_TASK));
        return container;
    }

    @Bean
    MessageListenerAdapter listenerAdapter1(RemoveDelayTaskEventListener removeDelayTaskEventListener) {
        MessageListenerAdapter listenerAdapter = new MessageListenerAdapter(removeDelayTaskEventListener, "onMessage");
        return listenerAdapter;
    }
    
    @Bean
    MessageListenerAdapter listenerAdapter2(RemoveDelayTaskEventListener removeDelayTaskEventListener) {
        MessageListenerAdapter listenerAdapter = new MessageListenerAdapter(removeDelayTaskEventListener, "onMessage");
        return listenerAdapter;
    }

五、总结

redis本身是绝大数应用已依赖的中间件,所以如果遇到发布广播消息的场景,可以考虑使用redis topic机制,轻松实现。

  • 16
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值