用Redis搞链表拼接,效率提升还能这么简单试试看吧
- 问答
- 2025-12-24 12:06:47
- 4
(引用来源:知乎专栏文章《用Redis搞链表拼接,效率提升还能这么简单试试看吧》作者:某技术博主)
记得以前刚工作那会儿,处理一个用户的消息列表,可把我给愁坏了,那时候用的老一套,就是关系型数据库,每个用户的消息都是一条条记录存在表里,用户一多,消息量蹭蹭往上涨,每次用户打开消息页面,我们就要去数据库里“SELECT * FROM messages WHERE user_id = ? ORDER BY create_time DESC LIMIT 20”,看着好像没啥问题,对吧?但架不住并发高啊,尤其是那种热点用户,消息好几万条,每次分页查询,越到后面越慢,数据库的CPU眼看着就上去了,页面加载那个圈圈转得人心慌。
后来我们想了好多办法,比如加索引、做读写分离、甚至把历史数据归档到别的表,折腾是折腾了,效果是有一些,但总觉得是治标不治本,每次分页那个深分页的问题,就像个牛皮癣,特别难搞,直到有一次团队里来了个新同事,看了我们的代码直嘬牙花子,说:“这事儿用Redis的链表来干,不是很简单嘛?”
我当时一听,Redis我知道啊,不就是个缓存吗?放点键值对,顶多弄个过期时间,链表?还能这么玩?结果他给我一讲,我算是开了眼了,原来Redis不光能存简单的字符串,它内置了好几种数据结构,其中就有链表(List),这个链表功能还挺全,能从头从尾插入元素,也能按范围读取。

他是这么设计的,我一说你就明白了,我们不是要给每个用户维护一个消息列表吗?好,那就在Redis里给每个用户分配一个唯一的Key,user_messages:{user_id},这个Key对应的值就是一个链表,每当这个用户收到一条新消息,我们不用再去数据库里插一条记录,而是直接用一条Redis命令 LPUSH user_messages:123 "这里是消息内容或者消息ID",你看,LPUSH 的意思就是从左边(头部)把新消息塞进链表,这一步操作,Redis处理起来飞快,几乎是瞬间完成。
这样一来,这个用户最新的消息永远在链表的最前面,当用户打开消息列表第一页的时候,我们根本不用碰数据库,直接对Redis执行 LRANGE user_messages:123 0 19,这条命令的意思就是,取出这个链表里从索引0开始到索引19的20条元素,因为是最新消息在头,所以取出来的自然就是最新的20条,翻第二页就是 LRANGE user_messages:123 20 39,以此类推。
我的天,这个操作实在是太直接了!你想啊,链表这种结构,按索引范围取数据,它的效率是非常高的,不像数据库分页,越往后越要扫描大量无用数据,Redis内部实现得很高效,LRANGE 的时间复杂度是 O(S+N),其中S是起始偏移量,N是指定范围内的元素数量,在实际分页场景下(比如每页20条),这个N是很小的常数,所以速度非常可观。

这样做还有个天大的好处,就是天然支持了列表的排序,因为我们总是用 LPUSH 把新消息放头部,所以整个链表自然就是按照时间倒序排列的,完全不需要 ORDER BY 这种操作,这不正是我们想要的效果吗?
当然啦,我们当时也讨论了几个问题,消息内容全放在Redis里,会不会占太多内存?我们的解决方案是,链表里不存完整的消息正文,只存消息的ID和一些核心摘要,详细内容在需要展示的时候再去数据库里查一次,这样既利用了Redis链表的高效排序和分页,又控制了内存占用,我们也设置了过期策略,比如只保留最近三个月的消息ID在Redis链表里,更早的就去数据库归档表查,虽然慢点,但访问频率也低嘛。
自从用了这个方案,那个消息列表页面的加载速度简直是鸟枪换炮,数据库的压力也一下子降下来了,我以前总觉得提升性能就得搞什么复杂的架构、深奥的算法,没想到有时候换个思路,用对工具,一个简单的数据结构就能带来这么大的改变,所以后来我逢人就说,有些场景下,用Redis搞链表拼接,这效率提升,真的就是这么简单,你不试试看都不知道能这么爽。
(引用来源:知乎专栏文章《用Redis搞链表拼接,效率提升还能这么简单试试看吧》中的核心设计思路和部分实现细节)
本文由歧云亭于2025-12-24发表在笙亿网络策划,如有疑问,请联系我们。
本文链接:http://waw.haoid.cn/wenda/67536.html
