当前位置:首页 > 问答 > 正文

Redis里那个List对象,怎么用来快速存取数据,效率还挺不错的感觉

说到Redis里的List对象,你可以把它想象成一个特别能干的“排队管理员”,它管理着一条队伍,但这队伍不一般,它两头都有门,你既可以从队伍后面加人,也可以从队伍前面加人,非常灵活,这种结构在计算机世界里叫“链表”,而Redis的List就是基于这种思想实现的,这也是它速度快的关键。

(来源:Redis官方文档对List类型的介绍,其底层实现之一为链表)

具体怎么用它来快速存取数据呢?咱们不看枯燥的理论,就看几个最常用的场景,你就能感受到它的“不错”了。

第一个场景:消息队列。 这是List最经典的应用,你有一个网站,用户注册成功后,需要给他发一封欢迎邮件,发邮件这个动作比较慢,你不能让用户傻等着邮件发送成功才显示注册成功页面,这时候就用上List了,当用户注册成功后,你只需要做一件非常快的事情:把他的用户ID(user123”)像塞纸条一样,从队伍的右边推进去一个叫“待发送邮件队列”的List里,这个操作命令叫RPUSH,速度极快,因为Redis是在内存里操作,一眨眼就完成了,你就可以立刻告诉用户“注册成功”了。

另一边,你有一个或多个专门发邮件的程序(消费者),它们啥也不干,就盯着这个“待发送邮件队列”,它们从队伍的左边不断地尝试取出一条数据,命令叫LPOP,如果取到了“user123”,它就去找这个用户的信息,然后吭哧吭哧发邮件,如果队伍是空的,它就等一会儿再试,这样一来,注册请求和发邮件这个慢活儿就被分开了,网站响应速度飞快,而且即使一时有很多人注册,邮件也会在队伍里排好队,一个个被处理,不会丢失,这种模式就是典型的“生产者-消费者”模型。

(来源:众多技术博客和书籍中关于使用Redis List实现简单消息队列的实践方案)

但这里有个小问题:发邮件的程序要不停地用LPOP问“有数据吗?有数据吗?”,这很浪费资源,Redis想到了这一点,提供了一个更聪明的命令BLPOP(阻塞式的左边弹出),这个命令的意思是:你去List左边取数据,如果现在有,立刻给你;如果现在没有,那你就安安静静地在那儿等着,直到有数据被推进来,或者等到一个你设定的超时时间,这样,发邮件的程序就可以“睡一会儿”,等有活干了再被叫醒,非常高效。

第二个场景:最新消息列表。 比如微博、朋友圈的时间线,或者新闻网站的最新文章列表,我们通常只想看最新的N条,比如最新的20条,用List做这个也非常合适,每产生一条新微博,我们就用LPUSH命令,从队伍的左边把它塞进一个叫“最新动态”的List里,这样,最新的内容永远在队伍的最前面。

当用户打开App想查看最新动态时,我们不需要去庞大的数据库里翻找,只需要用一个LRANGE命令,从这个List的左边开始,取出0到19号位置的元素(也就是最新的20条),瞬间就返回给用户了,因为数据都在内存里,这个操作的速度是微秒级别的,用户感觉不到任何延迟,为了不让这个List无限膨胀,我们可以在每次LPUSH后,检查一下长度,如果超过1000条了,就用LTRIM命令把1000条以后的老数据都修剪掉,只保留最新的1000条。

(来源:常见于社交类应用架构设计,利用List的LTRIM命令实现固定长度的最新列表)

第三个场景:充当一个轻量级的、可回滚的“任务历史记录”。 比如一个文档编辑器的撤销操作,用户每做一步操作(输入文字、改变格式),我们除了执行操作,还可以用LPUSH把操作前的状态快照存到一个以用户ID命名的List里,当用户点击撤销时,我们只需要用LPOP从列表头部取出最近一次的操作快照,然后恢复到那个状态即可,因为List是有序的,先进行的操作在队伍的更深处,后进行的操作在队伍头部,所以撤销的顺序天然就是正确的,虽然完整的撤销/重做功能可能更复杂,但List提供了一个非常简洁高效的核心思路。

(来源:对数据结构在应用场景中灵活运用的普遍认知,并非特指某一出处)

Redis的List之所以感觉效率不错,主要得益于几点:一是数据在内存中,读写本身就是高速的;二是它的双向链表结构,使得在头部和尾部的增删操作都非常快,是固定时间的操作,跟List里有多少个元素没关系;三是它提供了一组原子性命令(比如LPUSHLPOP),一个命令执行时不会被其他命令打断,这保证了在并发环境下数据不会错乱,通过上面这些活生生的例子,你应该能体会到,用这个简单的“队伍”概念,确实能解决很多实际问题,而且速度杠杠的。

Redis里那个List对象,怎么用来快速存取数据,效率还挺不错的感觉