开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 03 天,点击查看活动详情
Redis 支持订阅、发布模式的消息传递,这使得其能够作为简单的消息队列使用。 而且,Redis 是单线程的,能够保证 FIFO 队列特性。 今天,我会通过典型的生产者-消费者模型,来演示 Redis 是如何作为消息队列的。
01-生产者
在生产者消费者模型中,生产者负责生产消息对象,并放入到消息队列中。 在前面的文章 Spring Boot「18」Lettuce 原生 API 与 RedisTemplate 对比 中,我介绍了如何使用 SpringRedisTemplate 向某个 channel 上发送消息,即调用 SpringRedisTemplate#convertAndSend 方法。
生产者的代码如下:
@Component
public class MessageProducer {
private static final Logger LOGGER = LoggerFactory.getLogger(MessageProducer.class);
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private ChannelTopic topic; // 消息队列
// 向消息队列中发送消息
public void produce(Object obj) {
redisTemplate.convertAndSend(topic.getTopic(), obj);
}
public Thread startProduceThread() {
// 每 30s 产生一个 MessageObject 对象,并通过 produce 发送到消息队列中
Runnable producerTask = () -> {
while (true) {
try {
produce(MessageObjectFactory.getObject());
TimeUnit.SECONDS.sleep(30);
} catch (InterruptedException ie) {
ie.printStackTrace();
LOGGER.warn("producer exception: ", ie);
}
}
};
Thread produceThread = new Thread(producerTask, "producer");
produceThread.start(); // 启动生产者线程,不断地产生消息对象
return produceThread;
}
}
注:我在生产者中单独创建了一个生产线程,每隔 30s 就产生一个 MessageObject 对象。
02-消费者
在生产者-消费者模型中,消费者负责从消息队列中读取消息。 当 Redis 作为消息队列时,通过 MessageListener 接受消息。
消费者代码如下:
@Component
public class MessageConsumer implements MessageListener {
private final static Logger LOGGER = LoggerFactory.getLogger(MessageConsumer.class);
@Autowired
private RedisSerializer serializer; // 对象序列、反序列化器,用来从消息队列中消费消息,并反序列化为对象
@Override
public void onMessage(Message message, byte[] pattern) {
LOGGER.info("从消息队列[{}]收到消息", pattern);
LOGGER.info("从消息队列[{}]收到消息", message.getChannel());
LOGGER.info("消息内容消息:{}", message.getBody());
Object object = serializer.deserialize(message.getBody());
LOGGER.info("反序列化后的消息:{}", object);
}
}
装配
定义好生产者、消费者之后,这个模型中的主要元素就凑齐了。 接下来就是把他们组装在一起。
首先,Redis 是通过订阅、发布模式来实现消息队列的,所以我们需要定义一个 topic 作为消息队列。 其次,我们向消息队列中存放的时 MessageObject 对象,所以,需要一个序列化器将对象序列化。 而且,在消费者中,还需要序列化器将收到的内容反序列化为 MessageObject 对象。 之后,就是设置 RedisTemplate 对象,使用我们定义好的序列化器。 最后,就是将消费者订阅到消息队列上。
@Configuration
public class ProducerConsumerConfig {
@Bean
ChannelTopic queue() {
return ChannelTopic.of("mq:pc"); // 消息队列
}
@Bean
RedisSerializer redisSerializer() {
return new GenericJackson2JsonRedisSerializer(); // 对象序列、反序列化器
}
@Bean
RedisTemplate<String, Object> redisTemplate(@Autowired RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
// 设置 template 使用的序列、反序列化器
template.setDefaultSerializer(redisSerializer());
template.setKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(redisSerializer());
return template;
}
@Bean
RedisMessageListenerContainer redisMessageListenerContainer(@Autowired RedisConnectionFactory connectionFactory,
@Autowired Collection<MessageListener> listeners) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
listeners.forEach(e -> container.addMessageListener(e, queue())); // 向消息队列上注册监听器
return container;
}
}
装配好后,我们就可以启动程序了:
@SpringBootApplication
public class ProducerConsumerApplication implements CommandLineRunner {
@Autowired
private MessageProducer producer;
public static void main(String[] args) {
SpringApplication.run(ProducerConsumerApplication.class);
}
@Override
public void run(String... args) throws Exception {
Thread produceThread = producer.startProduceThread();
produceThread.join(); // 主要是为了保证 main 线程不退出
}
}
04-总结
今天,我用生产者-消费者模型演示了 Redis 如何作为消息队列使用。 不过,Redis 并不是一个成熟的消息队列,消费者不在线时,生产者产生的消息有可能会丢失。 如果要使用完备消息队列功能,建议使用 AMQ、RocketMQ 等成熟的消息队列中间件。