Redis延迟队列

Redis的消息队列并不是很专业的消息队列,他并没有很多的高级特性,没有ack保证(注:ack即确认字符),没有办法保证对信息可靠性的极高要求。

异步消息队列

我们之前说数据结构的时候说过,list可以作为栈和队列使用,队列的效果就是右进左出,需要的命令也就是rpush和lpop两条命令,Redis可以支持多个生产者和消费者并发进出信息,每个消费者拿到的消息都是不同的列表元素。举个例子:

rpush queue banana apple peach pear 
lpop queue
llen queue
lpop queue
llen queue

队列空了的问题

客户端通过对于队列pop的操作来获取消息,然后对其进行处理,处理完之后再次获取新的消息,然后继续处理,这样的一个周期便是消费者的生命周期。但是如果队列空了,pop便会陷入死循环,这样不断的死循环查询不仅会提高客户端的cpu占用率,而且会使Redis的QPS拉高。我们平时的操作一般都是用睡眠的方式,让线程歇一会儿,这样cpu使用率和QPS都会有明显的改善,睡得时间不用很久1s就可以了。

time.sleep(1)

但是这种睡眠会导致消息延迟,如果只有一个消费者,那么延迟就是1s,如果有多个消费者那么延迟会下降,因为每个消费者的睡眠时间是岔开的

阻塞读---blpop/brpop

这种阻塞读在队列没有数据的时候,会立即进入休眠状态,一旦出现数据,就立即醒过来,消息的延迟几乎为零。但是阻塞读依然有问题,因为如果消息到来很慢,线程一直都阻塞在那里,redis的连接变成了闲置连接,闲置太久,服务器一般会断开连接,这时候的blpop和brpush会出现异常。

锁冲突处理

之前我们说过分布式锁,分布式锁的有能抢到锁的,当然也有抢不到锁的,那么没有抢到锁的可怜人咋办呢,我们有三种策略来处理加锁失败。
①直接抛出异常,通知用户过一会儿再试。这种情况的确可以起到延时的效果,用户发起了直接的请求,用户看到了错误的对话框以及点击重试的操作,的确可以起到延时的作用,但在考虑用户体验的情况下,我们可以通过前端的代码替代用户进行延时重试控制,它的本质是放弃本次请求,等待下次的时机。
②sleep一会儿,这种方式是让线程沉睡,以阻塞的方式来进行延时,但是会影响队列的后续信息出现延迟,如果出现的是死锁,那么线程会被完全堵死,后续信息永远也无法得到处理。
③将请求转移至延时队列,过一会儿再试,这种方式是将当前冲突的请求扔到另一个队列延后处理以避开冲突。

延时队列的实现

延时队列是使用zset实现的,我们将消息序列化成字符串存储于zset的value,这个消息到期处理时间作为score,然后用多个线程轮询zset到期任务的处理,多个线程是为了保障可用性,万一挂了还有其他线程可以继续处理,因为有多个线程,所以要考虑争抢任务的事情,确保任务不会被多次执行。实现代码如下:

def delay (msg):
     msg.id = str(uuid.uuid4()) 
     #uuid是一种通用唯一标识符,它是通过MAC地址, 时间戳, 命名空间, 随机数, 伪随机数来保证生成ID的唯一性, 有着固定的大小(128bit)
     value = json.dumps(msg)
     #将消息内容序列化
     retry_ts = time.time()+5
     #设置重试时间,5秒之后重试
     redis.zadd("delay-queue",retry_ts,value)
     #添加队列,score为retry_ts+5
def loop():
     while True:
            values = redis.zrangebyscore("delay-queue",0,time.time(),start=0,num=1)
            #最多只能取到一条
            if no values:
                    time.sleep(1)
                    #如果队列里面没有数据,就睡一秒
                    continue
            value = value[0]
            #只有一条数据,也只取第一条
            success = redis.zrem("delay-queue",value)
            #将已经执行的语句移除队列,并且以此来判断是否成功抢到
            if success:
                   msg = json.loads(value)
                   handle_msg(msg)  

优化方式

因为是多线程多进程的抢任务的关键是zrem,zrem是判断是否争抢到的重要条件,但是因为不是原子操作,这就好比恋爱都谈一半了,你突然告诉我你要和别的男人结婚的感觉,这个线程也受不了啊,浪费了许多的时间,这种情况就又要利用我们的LUA来优化了,在服务器端将zrangebyscore和zrem原子化操作,这样多线程争抢任务就不会出现这种浪费了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,794评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,050评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,587评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,861评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,901评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,898评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,832评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,617评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,077评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,349评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,483评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,199评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,824评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,442评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,632评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,474评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,393评论 2 352

推荐阅读更多精彩内容