经验之谈:我为什么选择了这样一个激进的缓存大Key治理方案

一、引言

本文将结合我的一次Redis大Key的治理经验,来浅谈一下缓存大Key的治理方案选择。文中主要包括缓存大Key基础知识大Key治理方案选择大Key治理案例等,适合有一定开发经验的开发者阅读,希望对大家有帮助。

二、缓存大Key基础知识

2.1 大Key的标准

集合类型元素数量>5000或者单个value大于1M。当然这个标准不是绝对的,而是需要根据具体的业务场景和Redis集群的实际情况来灵活调整

2.2 大Key带来的风险

  • 大key发生热点访问,响应积压在服务端后内存暴涨,最终导致服务端被杀造成数据丢失
  • 严重影响 QPS、TP99等指标,对大Key进行的慢操作会导致后续的命令被阻塞,从而导致一系列慢查询。
  • hgetall、smembers 等时间复杂度O(N)的命令使用不当,容易造成CPU使用率过高。
  • 集群各分片内存使用不均。某个分片占用内存较高或OOM,发送缓存区增大等,导致该分片其他Key被逐出,同时也会造成其他分片的资源浪费。
  • 集群各分片的带宽使用不均。某个分片被流控,其他分片则没有这种情况,且影响宿主机上的其它应用。
  • 数据迁移失败 过大的Key(如超过1G),在迁移、缩容、扩容,主从全量同步在序列化过程中,内存上涨,数据同步失败,且存在数据丢失风险。

三、缓存大Key治理方案

3.1事前预防

事前预防的基本思路是在缓存设计的时候,缓存中只存储必要的数据,且需要考虑将存储空间变小,具体的手段有:

  1. 对于JSON类型,可以删除不使用的Field;或者使用@JsonProperty 注解让 FiledName 字符集缩小;
  2. 采用Protobuf等压缩算法,利用时间换空间;
  3. 对于集合类型,设计上尽量避免整存整取;

3.2 事中监控

事中做好监控,对于缓存大Key,早发现,早治理;

3.3 事后

缓存大Key已经存在,那么应该怎么办?分2种情况进行考虑:

  1. 评估这些大Key是否还有存在的意义和价值,是否可以删除,对于可以删除的可以使用SCAN命令进行循序渐进式删除大Key;对于不可以直接删除的这种,是否可以将数据转移到其他的介质进行存储;
  2. 对于不能直接删除的大Key,进行"分而治之",再通俗一点就是"拆",将大Key进行拆分
  • String类型的大Key:可以尝试将对象分拆成几个Key-Value,使用MGET或者多个GET组成的pipeline获取值,分拆单次操作的压力,对于集群来说可以将操作压力平摊到多个分片上,降低对单个分片的影响。
  • 集合类型的大Key,每次只需操作部分元素:将集合类型中的元素分拆。以Hash类型为例,可以在客户端定义一个分拆Key的数量N,每次对HGET和HSET操作的field计算哈希值并取模N,确定该field落在哪个Key上。

四、缓存大Key治理案例

4.1 系统架构和业务场景说明

有这样一个电商活动管理系统,系统有2个应用:后台管理端、运营端;(后台管理端是管理人员进行操作;运营端可以创建营销活动)存储使用的是MySQL和Redis;

系统中有一个这样的场景,简单来说就是,后台管理端可以添加sku白名单,这个白名单是以"商家ID + skuId"为一条记录写入MySQL中的;运营端在创建营销活动,需要上传sku,上传的时候需要读取sku白名单进行校验。

系统现状是,在查询sku白名单的时候使用了Redis缓存。缓存模式采用的是旁路缓存,添加SKU白名单的流程是,写入数据库,并删除缓存;读取SKU白名单的流程是,先从缓存读取,缓存中没有则读取数据库,并将结果写入缓存。缓存的数据类型是String,value为数据库中的全量SKU白名单记录,是以JSON字符串存储的。

旁路缓存:读取缓存、读取数据、更新缓存和操作都是在应用程序中完成的。

但是随着业务的发展,添加的SKU白名单逐渐增多,发展成为了缓存大Key。截止治理之前,数据库的配置表中存在1w+条sku白名单记录,也就是说Redis的一个String结果存储了1w+条的白名单记录。

这里也许有人问,一张表中存储1w+条数据,也是没有压力的啊,其实这张配置表中不仅有商家SKU白名单配置,还包括系统中其他所有的配置信息存储,数据量还是可观的。

4.2 一种激进的治理方案

该业务场景中,缓存大Key是不可以直接进行删除处理的,处理思路是将其拆分为一些小的Key。

分析场景,商家可以登录运营端进行创建营销活动,在创建营销活动和上传SKU的过程中,只需要查询该商家的SKU白名单,那么存储和查询全部都是不必要的,那么可以将全量白名单数据按照商家维度进行拆分存储和查询。

关于缓存结构的选择,我使用的是Hash结构(新缓存),key与原缓存String结构 key 相同,field为商家ID,value为sku白名单列表。大概流程图如下:

新缓存Hash结构key与原缓存String结构 key 完全相同,意味着新的Hash结构缓存和旧的String缓存是无法共存的,只能选择其一,意味着不存在百分比切量的过程,一步到位切量,你就说是否激进?

这样激进的方案是基于什么的考量呢?

  1. 夜间流量几乎为0,且缓存切换操作秒级完成,风险可控。运营端系统,商家创建促销活动基本都是工作时间,晚上和凌晨几乎没有流量;而且,新旧缓存切换可以秒级完成:①运营端添加测试SKU白名单,删除缓存;②将开关切到新缓存;风险可控。
  2. 方案支持秒级回滚.①运营端添加测试SKU白名单,删除缓存;②将开关切到旧缓存。
  3. 代码改动和上线部署工作量小。这个方案可以保持管理后端删除缓存的逻辑不变,也就是在添加SKU白名单的时候还是删除所有的白名单缓存;只需要改动运营后端代码,且上线部署只需要部署运营端。
  4. 虽然管理后端删除缓存为全量删除,存在管理端添加商家A的SKU白名单,导致缓存中的商家B、商家C等的白名单数据也被删除(其实是无需删除的),综合评估可以暂时保持现状,后续优化

4.3 更加通用的治理方案

4.3.1 三个阶段

更加通用的缓存大key治理方案,包括双写、双读对比和读写新key等阶段:

  1. 双写阶段

在不影响现有业务的情况下,将新数据同时写入旧key和新key;

  1. 双读对比阶段

验证新key的数据与旧key的数据是否一致,并准备切换读操作到新key;

  1. 读写新key阶段

将所有读写操作都切换到新key,并废弃旧key;需要注意删除大key时要避免阻塞Redis服务。

4.3.2 注意事项

  • 数据一致性:在整个治理过程中,需要确保数据的一致性。特别是在双写和双读对比阶段,要仔细验证数据是否一致。
  • 性能影响:在双写和双读对比阶段,由于需要同时操作旧key和新key,可能会对性能产生一定影响。因此,需要在业务低峰期进行这些操作,并监控系统的性能指标。
  • 错误处理:在治理过程中,可能会遇到各种错误情况(如数据不一致、网络问题等)。需要制定完善的错误处理机制来确保系统的稳定性和可用性。
  • 备份和恢复:在执行治理操作之前,建议对Redis数据进行备份。以便在出现问题时可以快速恢复数据。

五、小结

本文是一个真实的线上缓存大Key治理案例,区别于通用的治理方案,我选择了一种激进和简单的治理策略。对于缓存大Key进行简单总结一下,对于使用缓存,需要考虑缓存大Key问题,设计和开发过程中尽量避免;如果已经出现,考虑删除,如果不可以删除,考虑拆分。可以在权衡之下,选择最适合自己的方案。

一起学习

欢迎各位在评论区或者私信我一起交流讨论,或者加我主页weixin,备注技术渠道(如博客园),进入技术交流群,我们一起讨论和交流,共同进步!

也欢迎大家关注我的博客园、公众号(码上暴富) ,点赞、留言、转发。你的支持,是我更文的最大动力!

相关推荐
黄名富3 小时前
Redis 附加功能(二)— 自动过期、流水线与事务及Lua脚本
java·数据库·redis·lua
G_whang4 小时前
centos7下docker 容器实现redis主从同步
redis·docker·容器
.生产的驴4 小时前
SpringBoot 对接第三方登录 手机号登录 手机号验证 微信小程序登录 结合Redis SaToken
java·spring boot·redis·后端·缓存·微信小程序·maven
我叫啥都行7 小时前
计算机基础复习12.22
java·jvm·redis·后端·mysql
阿乾之铭8 小时前
Redis四种模式在Spring Boot框架下的配置
redis
on the way 12310 小时前
Redisson锁简单使用
redis
科马10 小时前
【Redis】缓存
数据库·redis·spring·缓存
mxbb.12 小时前
单点Redis所面临的问题及解决方法
java·数据库·redis·缓存
weisian1511 天前
Redis篇--常见问题篇3--缓存击穿(数据查询上锁,异步操作,熔断降级,三种缓存问题综合优化策略)
数据库·redis·缓存
HEU_firejef1 天前
redis——布隆过滤器
数据库·redis·缓存