目录
Redis Streams 介绍
Stream是Redis 5.0版本引入的一个新的数据类型。
Stream是Redis的数据类型中最复杂的,尽管数据类型本身非常简单,它实现了额外的非强制性的特性:提供了一组允许消费者以阻塞的方式等待生产者向Stream中发送的新消息,此外还有一个名为消费者组的概念。消费者组最早是由名为Kafka(TM)的流行消息系统引入的。
Streams命令
1.往stream里写入键值对
> xadd mystream * testKey testValue
"1603249448596-0"
参数值是*
,因为我们希望由Redis服务器为我们自动生成一个新的条目ID。由服务器自动生成条目ID几乎总是我们所想要的,需要显式指定ID的情况非常少见。
条目 ID
条目ID由XADD命令返回,并且可以唯一的标识给定Stream中的每一个条目,由两部分组成:
<millisecondsTime>-<sequenceNumber>
例如"1603249448596-0"
2.stream中的条目数
> xlen mystream
(integer) 2
3.查询stream中的最大条目数
要根据范围查询Stream,我们只需要提供两个条目ID,即start 和 end。返回的区间数据将会包括ID是start和end的元素,因此区间是完全包含的。两个特殊的ID-
和 +
分别表示可能的最小ID和最大ID。
> xrange mystream - +
1) 1) "1603249448596-0"
2) 1) "lgl"
2) "1"
2) 1) "1603249454137-0"
2) 1) "lgl"
2) "1"
3) 1) "1603249814032-0"
2) 1) "testKey"
获取最近一条的条目
> xrevrange mystream + - COUNT 1
1) 1) "1603249814032-0"
2) 1) "testKey"
2) "testValue"
4.监听条目
> xread streams mystream 0
0表示要监听的条目ID必须大于0。
阻塞等待最新的条目
> XREAD BLOCK 0 STREAMS mystream $
指定了新的BLOCK选项,超时时间为0毫秒(意味着永不超时)。
$表示最大条目ID,。
5.消费组与消费者
1)创建一个消费者组
> xgroup create mystream mygroup $
目前只能为存在的Stream创建消费者组。 $表示这个组需要等待新的条目。
2)消费者消费组里的条目
> xreadgroup group mygroup comsumer1 COUNT 1 STREAMS mystream >
表示消费者comsumer1
使用消费者组mygroup
从mystream
中读取条目。
>
表示最新条目且未被消费过的。
3)例子
comsumer1
> xreadgroup group mygroup comsumer1 COUNT 1 block 0 STREAMS mystream >
comsumer2
> xreadgroup group mygroup comsumer2 block 0 COUNT 1 STREAMS mystream >
往流写条目
> xadd mystream * testKey testValue
相同组内只有一个消费者消费到消息。
这种拉模式解决了Redis发布/订阅模式(推模式)中,订阅者可能重复消费的问题。
在SpringBoot中使用
配置类
@Component
public class RedisSubscriberConfig {
@Bean("streamListener")
RedisAuthTicketStreamListener streamListener() {
return new RedisAuthTicketStreamListener();
}
@Bean("containerOptions")
StreamMessageListenerContainer.StreamMessageListenerContainerOptions<string, maprecord<string, string, string>> containerOptions() {
return StreamMessageListenerContainer.StreamMessageListenerContainerOptions.builder()
.pollTimeout(Duration.ofMillis(100)).build();
}
@Bean
StreamMessageListenerContainer<string, maprecord<string, string, string>> streamContainer(RedisConnectionFactory connectionFactory,
@Qualifier("containerOptions")
StreamMessageListenerContainer.StreamMessageListenerContainerOptions<string, maprecord<string, string, string>> containerOptions
,@Qualifier("streamListener") RedisAuthTicketStreamListener streamListener) {
StreamMessageListenerContainer<string, maprecord<string, string, string>> container =
StreamMessageListenerContainer.create(connectionFactory, containerOptions);
container.start();
Subscription //subscription = container.receive(StreamOffset.fromStart(Constant.REDIS_CHANNEL_MAKE_TICKET), streamListener);
subscription = container.receive(Consumer.from("ticket-group", "ticket-customer"),
StreamOffset.create(Constant.REDIS_CHANNEL_MAKE_TICKET, ReadOffset.lastConsumed()),
streamListener);
return container;
}
}
监听类
public class RedisAuthTicketStreamListener implements StreamListener<string, maprecord<string, string, string>> {
@Autowired
RedisTemplate redisTemplate;
@Autowired
AuthTicketDao authTicketDao;
@Override
public void onMessage(MapRecord<string, string, string> record) {
System.out.println("test");
AuthTicketEntity authTicketEntity = new AuthTicketEntity();
try {
Map<string,string> map = record.getValue();
authTicketEntity.setTicketSn(Long.parseLong(map.get("ticketSn")));
authTicketEntity.setTicket(map.get("ticket"));
...
} catch (Exception ex) {
...
}
redisTemplate.opsForStream().acknowledge("ticket-group", record);
}
}