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

Redis负载优化了,应用响应速度也跟着快起来了

(根据知乎专栏“技术夜未眠”中一篇关于缓存优化的实践分享整理)

我们团队最近遇到了一件挺让人头疼的事儿,就是每天到了下午两三点钟,特别是电商平台搞秒杀活动那几天,整个应用的响应速度就变得特别慢,用户那边老是反馈说页面刷不出来,下单要转半天圈圈,客服电话都快被打爆了,技术这边看监控后台,发现应用服务器的CPU和内存其实都还好,没到撑不住的地步,但数据库的压力却异常的大,尤其是某些核心商品信息表和用户订单表的查询,慢查询日志一下子多了好多,我们初步判断,瓶颈很可能出在数据库的读取上,大量的请求直接涌向了数据库,导致它不堪重负。

(信息源自团队内部问题排查会议记录及APM监控系统数据)

Redis负载优化了,应用响应速度也跟着快起来了

问题定位了,接下来就是想办法解决,我们用的主要缓存工具就是Redis,但之前的用法可能比较粗放,我们开了一个紧急的“诸葛亮会”,大家七嘴八舌地讨论开了,有同事说,是不是我们Redis的缓存命中率太低了?就是很多请求其实没从Redis里拿到数据,还是跑去查数据库了,还有同事怀疑,是不是我们缓存的数据结构设计得不合理,比如把一个很复杂的对象整个存成一个大的JSON字符串,每次读取哪怕只要其中一个字段,也得把整个字符串都拿出来解析,效率不高,也有同事提到,是不是缓存Key的过期时间设置得太短了,数据刚缓存没多久就失效了,又得重新查库。

(思路启发自《Redis设计与实现》一书中关于缓存策略的章节)

讨论下来,我们觉得不能头疼医头脚疼医脚,得系统地搞一次Redis的负载优化,我们分了几步走:

Redis负载优化了,应用响应速度也跟着快起来了

第一步,先好好清理一下“库存”,我们用了Redis自带的命令,仔细分析了现在Redis里都存了些什么东西,哪些Key是经常被访问的“热数据”,哪些是放了很久几乎没人用的“冷数据”,结果发现,确实有很多早已下架的商品信息、过期的会话数据还占着地方,我们制定了一个清理策略,比如对于不重要的临时数据,适当缩短过期时间;对于一些肯定不再用的历史数据,果断手动清理掉,这就好比给Redis做了个“大扫除”,腾出了不少内存空间,也让Redis自身管理Key的效率更高了。

第二步,重点优化缓存策略,努力提高缓存命中率,我们重新审视了哪些数据是应该被缓存的,对于那些查询非常频繁、但更新不频繁的数据,比如商品分类信息、城市列表、配置参数等,我们设置了更长的过期时间,甚至考虑在某些极端场景下使用“永不过期”策略,然后通过后台更新机制来同步数据,对于那些更新比较频繁的数据,比如商品库存(特别是秒杀商品),我们采用了更精细化的缓存更新方案,不是在数据库更新后就立刻删除缓存(这可能导致后续请求瞬间压垮数据库),而是谨慎地更新缓存值,或者使用分布式锁来避免缓存击穿,我们还引入了“缓存预热”的机制,在预估会有大流量来临之前(比如秒杀开始前十分钟),就提前把关键数据加载到Redis里,避免活动一开始所有请求都同时去数据库查。

第三步,在数据结构上动了点“小手术”,之前图省事,经常把对象序列化成JSON字符串存进去,现在我们针对不同的查询需求,更合理地使用Redis原生的数据结构,要存储和查询一个用户的多种信息,我们可能会用Hash结构,这样可以直接获取某个字段,而不用读取整个对象,需要维护一个排行榜,就直接使用ZSET(有序集合),这些原生数据结构的操作效率远高于在应用层解析JSON字符串。

Redis负载优化了,应用响应速度也跟着快起来了

(具体操作细节参考了Redis官方文档关于数据类型的性能说明)

第四步,我们也没忘记Redis本身,检查了一下配置,比如是否设置了最大内存限制,以及内存满了之后的淘汰策略(是报错还是淘汰一些旧数据),我们根据实际情况调整了策略,确保在内存紧张时能平滑地淘汰掉不重要的数据,而不是直接拒绝服务。

这么一顿操作下来,效果是立竿见影的,最直观的变化就是监控图上,数据库的负载曲线平稳了很多,之前那种瞬间冲高的“毛刺”几乎不见了,应用服务器的响应时间也有了非常明显的下降,平均响应时间从原来的几百毫秒降低到了几十毫秒,最开心的当然是用户,页面加载飞快,下单流程顺畅,客服那边反馈说相关的投诉电话少了一大半,我们自己也松了口气,终于不用再提心吊胆地盯着监控屏幕了。

这次经历让我们深刻体会到,Redis这个好东西,用好了是神器,能极大提升应用性能;但要是用不好,或者疏于管理,它也可能成为瓶颈,定期对缓存系统进行“体检”和“优化”,就像给汽车做保养一样,是非常有必要的事情,应用的响应速度上来了,用户体验好了,业务发展才能更顺畅。