Redis实现消息队列的基本指令

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> list = StringRedisTemplate.opsForStream().read(
    Cousumer.from("g1","c1"),
    StreamReadOptions.empty().count(1).block(Duration.ofSeconds(2)),
    StreamOffset.create("stream.orders",ReadOffset.lastConsumed())
)

你可能感兴趣的:(缓存应用,redis,数据库,缓存)