Redis Stream 介绍
Redis Stream 主要用于消息队列(MQ,Message Queue),Redis 本身是有一个 Redis 发布订阅 (pub/sub) 来实现消息队列的功能,但它有个缺点就是消息无法持久化,如果出现网络断开、Redis 宕机等,消息就会被丢弃。
简单来说发布订阅 (pub/sub) 可以分发消息,但无法记录历史消息。
而 Redis Stream 提供了消息的持久化和主备复制功能,可以让任何客户端访问任何时刻的数据,并且能记住每一个客户端的访问位置,还能保证消息不丢失。
Redis Stream 的结构如下所示,它有一个消息链表,将所有加入的消息都串起来,每个消息都有一个唯一的 ID 和对应的内容:
Redis Stream 基本命令
- 使用 XADD命令向队列添加消息,如果指定的队列不存在,则创建一个队列,XADD 语法格式:
XADD luolistream * name luoli
- luolistream 队列名称,如果不存在就创建
- * 消息 id,我们使用 * 表示由 redis 生成,可以自定义,但是要自己保证递增性
- name luoli 发送的消息
- 使用 XGROUP CREATE 创建消费者组
XGROUP CREATE luolistream group-1 0
- 最后一个0表示从头开始消费,也你可以使用$代替表示从尾部开始消费,只接受新消息
- 发送进去的消息需要创建一个消费组来对消息进行消费,一个消费组中可以有多个消费者consumer,但是一个组中只会有一个消费者去消费掉当前的消息,和Kafka很类似。
- 消费消息
XREADGROUP GROUP group-1 consumer-luoli COUNT 1 STREAMS luolistream >
- 会在group-1的消费组中创建一个消费者consumer-luoli来进行消费luolistream中的消息。
整合Spring Boot
第一步:创建消费对象 继承StreamListener 重写onMessage方法即可。
@Component
public class OrderStreamListener implements StreamListener<String, ObjectRecord<String, String>> {
@Autowired
StringRedisTemplate stringRedisTemplate;
@Override
public void onMessage(ObjectRecord<String, String> message) {
// 消息ID
RecordId messageId = message.getId();
// 消息的key和value
String string = JSONUtil.toJsonStr(message.getValue());
Order order = JSONUtil.toBean(string, Order.class);
LOGGER.info("StreamMessageListener1 stream message。messageId={}, stream={}, body={}", messageId, message.getStream(), body);
// 通过RedisTemplate手动确认消息
this.stringRedisTemplate.opsForStream().acknowledge("group-1", message);
}
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.stream.Consumer;
import org.springframework.data.redis.connection.stream.ObjectRecord;
import org.springframework.data.redis.connection.stream.ReadOffset;
import org.springframework.data.redis.connection.stream.StreamOffset;
import org.springframework.data.redis.stream.StreamMessageListenerContainer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.time.Duration;
@Configuration
@Slf4j
public class RedisStream2 {
@Autowired
ThreadPoolTaskExecutor threadPoolTaskExecutor;
@Bean
public StreamMessageListenerContainer<String, ObjectRecord<String, String>> ocrfristMyGroupStreamContainer(
RedisConnectionFactory connectionFactory,
OrderStreamListener streamListener) {
StreamMessageListenerContainer<String, ObjectRecord<String, String>> container =
streamContainer("mystream", connectionFactory, streamListener);
container.start();
return container;
}
private StreamMessageListenerContainer<String, ObjectRecord<String, String>> streamContainer(String mystream ,RedisConnectionFactory connectionFactory,OrderStreamListener streamListener){
StreamMessageListenerContainer.StreamMessageListenerContainerOptions<String, ObjectRecord<String, String>> options = StreamMessageListenerContainer.StreamMessageListenerContainerOptions
.builder()
.pollTimeout(Duration.ofSeconds(1))
.batchSize(10)
.targetType(String.class)
.executor(threadPoolTaskExecutor)
.build();
StreamMessageListenerContainer<String, ObjectRecord<String, String>> container = StreamMessageListenerContainer
.create(connectionFactory, options);
//指定消费最新的消息
StreamOffset<String> offset = StreamOffset.create(mystream, ReadOffset.lastConsumed());
//创建消费者
Consumer consumer = Consumer.from("group-1", "consumer-1");
StreamMessageListenerContainer.StreamReadRequest<String> streamReadRequest = StreamMessageListenerContainer.StreamReadRequest.builder(offset)
.errorHandler((error)->log.error(error.getMessage()))
.cancelOnError(e -> false)
.consumer(consumer)
//关闭自动ack确认
.autoAcknowledge(false)
.build();
//指定消费者对象
container.register(streamReadRequest, streamListener);
return container;
}
}
需要注意的是我们的key和group需要提前先创建好才行。