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

Redis到底怎么用才靠谱,项目里那些实操经验和坑你得知道

(引用来源:某电商平台后端开发团队的技术分享会记录)

Redis这东西,现在做项目几乎离不开,但用得好和用得差,效果天差地别,说白了,它就是个速度超快的内存字典,但你千万别真把它当个简单的字典来用,我们项目里踩过的坑,可以说是一部血泪史。

最关键的,你得想清楚用Redis主要干什么,就三件事:缓存、计数、存会话,别动不动就把什么数据都往Redis里塞,它内存贵啊,而且数据丢了有时候没那么容易恢复,我们一开始有个哥们儿,图快,把一些本该存数据库的复杂业务关系数据也塞进了Redis,结构设计得乱七八糟,后来服务器宕机重启,这部分数据没了,逻辑全乱套了,差点出线上事故。Redis应该是加速的帮手,而不应该是数据的唯一家,核心的、不能丢的数据,老老实实落数据库,Redis里只放一份副本。

(引用来源:团队一次因Redis内存爆满导致的线上服务雪崩事故复盘)

说到内存,这是Redis最大的成本,也是最容易出问题的地方,你一定要给你的Redis实例设置最大内存上限(maxmemory),并且配置一个淘汰策略(maxmemory-policy),我们吃过一次大亏,当时没设上限,业务跑着跑着,Redis内存使用量悄无声息地涨到了90%以上,最后直接把机器内存撑爆了,Redis进程被系统杀掉了,这还不是最糟的,更糟的是Redis重启后,缓存全部失效,所有请求像海啸一样直接打到后方数据库上,数据库瞬间被打垮,整个服务雪崩,瘫痪了半个多小时,教训就是,必须设内存上限,常用的策略比如allkeys-lru(最近最少使用的键被淘汰)就比较稳妥,至少能保证缓存不会彻底挂掉,只是淘汰掉一些不常用的数据。

然后是数据结构的选择,别只会用最简单的key-value字符串(String),比如要存一个用户的信息,包括姓名、年龄、城市,你别用三个独立的key(user:123:name, user:123:age, user:123:city),这效率低,网络开销也大,应该用一个哈希(Hash)结构,key是user:123,里面存多个字段,这样一次操作就能取回所有信息,省时省力,再比如,要做排行榜,用有序集合(Zset)是天作之合,它自带排序功能,比你从数据库查出来再排序快无数倍,还有列表(List)可以做简单的消息队列,集合(Set)可以做好友关系、标签系统。用对数据结构,事半功倍。

(引用来源:某次促销活动前,压力测试中发现的热点Key问题)

还有一个高级一点的坑,叫“热点Key”,就是某一个Key的访问量特别大,比如某个秒杀商品的信息,所有用户都来查询它,这个Key所在的Redis服务器节点就会成为瓶颈,压力巨大,可能拖慢整个实例,解决方法是,要么把这个热点Key拆分成多个子Key,分散到不同节点(如果用了集群模式),比如把商品信息拆成product:10001:info1, product:10001:info2,然后客户端随机读一个,更常见的做法是在客户端做本地缓存,比如用Guava Cache,应用服务器第一次从Redis读到这个热点数据后,在自己内存里存一份(设置很短的过期时间,比如2秒),后续的请求直接读本地,极大减轻Redis压力,这个技巧在搞大促活动时非常救命。

(引用来源:长期运行项目中发现的内存缓慢泄漏排查)

键名(Key)的设计也有讲究,我们曾经有个问题,Redis的内存占用总是缓慢增长,即使业务量稳定,查了好久才发现,是有些临时性的Key设置过期时间(TTL)时,用了相同的值,比如都设3600秒(1小时),这会导致这些Key很可能在同一秒内集中过期,而过期删除是Redis主线程做的,瞬间大量Key过期会引发短暂的延迟毛刺,更可怕的是,如果这些Key永远不再被访问,而只是依赖过期删除,Redis的过期策略是惰性+定期,可能不会及时清理,造成内存的“幽灵”占用。给Key设置过期时间时,最好加个随机数,比如3600 + random(300),让过期时间分散开,避免集中失效和潜在的清理不及时问题。

连接管理也是个小事儿但容易忽视,不要每次操作Redis都新建连接,然后用完就关,创建连接的成本很高,一定要用连接池,我们用的Java客户端是Jedis或Lettuce,它们都支持连接池,配置好最大最小连接数,让连接复用,性能会稳定很多,操作完Redis,记得关闭连接,还给连接池,不然会导致连接泄漏,池子里的连接被耗光,新的请求就拿不到连接了,服务就卡死了。

用Redis想靠谱,就得把它当成一个需要精心照料的高速宝贝,而不是一个可以随意折腾的垃圾桶,明确它的定位,管好它的内存,用对数据结构,预防热点问题,注意键的设计和连接管理,这些实操经验能帮你避开大部分深坑。

Redis到底怎么用才靠谱,项目里那些实操经验和坑你得知道