1、PubSub订阅【生产者,channel,消费者】
一个生产者可以向1个或者多个频道推送消息,消费者可以订阅一个或者是多个频道。
发送消息:publish channel1 msg; 例如:publish order.queue mymsg
订阅消息:subscrib channel1; 例如:subscrib order.queue
订阅所有匹配频道:Psubscrib pattern 例如:psubscrib order.* 【这两个是天生阻塞的,只要已发布消息,他们只要满足订阅条件,就能马上收到。】
优点:支持多生产者,多消费者;
缺点:不支持持久化消息;
无法避免消息丢失【频道如果无人订阅,那么发送到频道里边的数据则会马上丢失】
堆积有上限,超出时,数据丢失【处理数据时,如果有新的数据传送过来,则丢失】
=========================================================================
2、Stream
redis5.0以后,新引入的数据类型,支持持久化。
XADD key名字[队列不存在会自动创建] [设置消息数量:count 1,每次读取一条数据] [唯一ID,一般设置为*] [消息体 entry(key value),一般是 age 26 sex man]
例如:XADD users * name jack age 26 向users队列中添加一条消息。
Xread [count 1:每次读取消息的max数量] [Block 2000 没有消息时,阻塞2秒,2秒后返回nil] [streams 队列名称=key名称] [ID起始消息ID] [0代表从头开始 $代表最新一条]
例如:XRead count 1 【BLOCK 0 永久阻塞读】 streams s1 0
(1)"消费者1客户端运行可以读取到s1队列的消息"
(2)"消费者2客户端运行可以读取到s1队列的消息"
(3)"消费者1客户端【再次】运行还是可以读取到s1队列的消息"
(4)"如果最后的0,换成了$,再次运行该语句,返回nil;因为$读取最新的未读的消息,0的数据已经读过了,所以返回nil"
永久阻塞的代码:
while(true){
Object msg = redis.execute("XRead count 1 block 2000 streams users $");
if(msg == null){
continue; //继续
}
handleMsg(msg);
}
//注意:当我们在处理时,就不存在阻塞等待了,如果此时来了多条数据,下一次读取时,只能读取最新的一条数据,其余中间的数据丢失。
优点:消息可回溯;一个消息可以被多个消费者读取;可以阻塞读取;
缺点:就是$有漏读的风险;【PubSub是没人订阅就丢失了,这个没人订阅的话,还存在队列里边】
=============================这一块就是类似于pubsub===================
3、 消费者组:
【里边有多个消费者,监听同一个队列,但是多个队列是竞争的关系,读取队列快了,所以能给队列分流。】
(1)消息分流:分为组内不同的消费者,不能重复消费,加快队列处理速度。如果想要消息被多个消费者消费,添加多个消费者组即可。
(2)消息标识:记录消息队列读取的页签,就是读到哪里都要记录下,如果宕机之后,从页签处开始读,而不是从最新的地方开始读,确保每个消息被消费。
(3)消息确认:读取了,但是未确认的消息会放到pending-list中,处理完成后,需要进行ACK操作,才能从pengding-list移除。
创建消费者组:
XGroup create key groupName ID[0/$] [mkstream:队列不存在时,自动创建]
例如:XGroup create s1 g1
给消费者组添加操作,离不开队列,所以我重点标识了【key groupName】
删除指定的消费者组:
XGroup destory 【key groupName】
例如:XGroup destory s1 g1
给指定消费者组添加消费者:
XGroup createconsumer 【key groupName】 cousermerName
删除指定消费者组中的指定消费者:
XGroup delconsumer 【key groupName】consumerName
消费者从消费者组中读取消息:
XReadGroup group groupName consumerName [count 1] [Block time] [NoAck] streams key[key..] ID[...,一般采用">"这个符号,来表示从下一个未消费的开始]
例如:
XReadGroup group g1 c1 count 1 block 2000 streams s1 >
确认消息指令:
XACK keyName groupName 消费者消息ID
例如:
"XACK" s1 g1 "fakdfafad-0"
读取Pending-list,已消费,但是未确认的消息;
"XPending s1 g1 - + 0"
伪代码:
while(true){
Object msg = redis.call("XReadGroup group g1 c1 block 2000 streams s1 >")
if(msg == null){
continue;
}
try{
handlerMsg(msg); //处理完成后,记得ACK
}catchException(Exception e){
while(true){
Object msg = redis.call("XReadGroup group g1 c1 count 1 streams s1 0") //没有阻塞了,从0开始读取
}
if(msg == null){ //null说明没有异常消息,所以消息都已经确认过了,结束循环。
break;
}
try{
//说明有异常消息,再次处理
handlerMsg(msg); //处理完成后,记得ACK
}catchException(Exception e){
//再次出现异常,记录日志,持续循环。
continue;
}
}
}
特点:
可以回溯;
同组竞争,加快队列处理速度;
阻塞读取 Block 0
没有漏读的风险
能确认pending-list的消息
秒杀任务:
stream.orders 只创建这一个队列。
使用lua脚本进行下单,【确认有秒杀资格后,想队列里边添加消息(coucherID,userID,orderID)】
XGroup group g1 c1 count 1 block 2000 streams streams.order >
List<MapRecord<String,Object,Object>> list = StringRedisTemplate.opsForStream().read(
Cousumer.from("g1","c1"),
StreamReadOptions.empty().count(1).block(Duration.ofSeconds(2)),
StreamOffset.create("stream.orders",ReadOffset.lastConsumed())
)