经验之谈:我为什么选择了这样一个激进的缓存大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事前预防
事前预防的基本思路是在缓存设计的时候,缓存中只存储必要的数据,且需要考虑将存储空间变小,具体的手段有:
- 对于JSON类型,可以删除不使用的Field;或者使用@JsonProperty 注解让 FiledName 字符集缩小;
- 采用Protobuf等压缩算法,利用时间换空间;
- 对于集合类型,设计上尽量避免整存整取;
3.2 事中监控
事中做好监控,对于缓存大Key,早发现,早治理;
3.3 事后
缓存大Key已经存在,那么应该怎么办?分2种情况进行考虑:
- 评估这些大Key是否还有存在的意义和价值,是否可以删除,对于可以删除的可以使用SCAN命令进行循序渐进式删除大Key;对于不可以直接删除的这种,是否可以将数据转移到其他的介质进行存储;
- 对于不能直接删除的大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缓存是无法共存的,只能选择其一,意味着不存在百分比切量的过程,一步到位切量,你就说是否激进?
这样激进的方案是基于什么的考量呢?
- 夜间流量几乎为0,且缓存切换操作秒级完成,风险可控。运营端系统,商家创建促销活动基本都是工作时间,晚上和凌晨几乎没有流量;而且,新旧缓存切换可以秒级完成:①运营端添加测试SKU白名单,删除缓存;②将开关切到新缓存;风险可控。
- 方案支持秒级回滚.①运营端添加测试SKU白名单,删除缓存;②将开关切到旧缓存。
- 代码改动和上线部署工作量小。这个方案可以保持管理后端删除缓存的逻辑不变,也就是在添加SKU白名单的时候还是删除所有的白名单缓存;只需要改动运营后端代码,且上线部署只需要部署运营端。
- 虽然管理后端删除缓存为全量删除,存在管理端添加商家A的SKU白名单,导致缓存中的商家B、商家C等的白名单数据也被删除(其实是无需删除的),综合评估可以暂时保持现状,后续优化。
4.3 更加通用的治理方案
4.3.1 三个阶段
更加通用的缓存大key治理方案,包括双写、双读对比和读写新key等阶段:
- 双写阶段
在不影响现有业务的情况下,将新数据同时写入旧key和新key;
- 双读对比阶段
验证新key的数据与旧key的数据是否一致,并准备切换读操作到新key;
- 读写新key阶段
将所有读写操作都切换到新key,并废弃旧key;需要注意删除大key时要避免阻塞Redis服务。
4.3.2 注意事项
- 数据一致性:在整个治理过程中,需要确保数据的一致性。特别是在双写和双读对比阶段,要仔细验证数据是否一致。
- 性能影响:在双写和双读对比阶段,由于需要同时操作旧key和新key,可能会对性能产生一定影响。因此,需要在业务低峰期进行这些操作,并监控系统的性能指标。
- 错误处理:在治理过程中,可能会遇到各种错误情况(如数据不一致、网络问题等)。需要制定完善的错误处理机制来确保系统的稳定性和可用性。
- 备份和恢复:在执行治理操作之前,建议对Redis数据进行备份。以便在出现问题时可以快速恢复数据。
五、小结
本文是一个真实的线上缓存大Key治理案例,区别于通用的治理方案,我选择了一种激进和简单的治理策略。对于缓存大Key进行简单总结一下,对于使用缓存,需要考虑缓存大Key问题,设计和开发过程中尽量避免;如果已经出现,考虑删除,如果不可以删除,考虑拆分。可以在权衡之下,选择最适合自己的方案。
一起学习
欢迎各位在评论区或者私信我一起交流讨论,或者加我主页weixin,备注技术渠道(如博客园),进入技术交流群,我们一起讨论和交流,共同进步!
也欢迎大家关注我的博客园、公众号(码上暴富),点赞、留言、转发。你的支持,是我更文的最大动力!
经验之谈:我为什么选择了这样一个激进的缓存大Key治理方案的更多相关文章
- 哪种缓存效果高?开源一个简单的缓存组件j2cache
背景 现在的web系统已经越来越多的应用缓存技术,而且缓存技术确实是能实足的增强系统性能的.我在项目中也开始接触一些缓存的需求. 开始简单的就用jvm(java托管内存)来做缓存,这样对于单个应用服务 ...
- 闲来无事,用Java的软引用写了一个山寨的缓存
闲来无事,用Java的软引用写了一个山寨的缓存 博客分类: java基础 众所周知java中的引用分为 StrongReference.SoftReference.WeakReference.Phan ...
- Hbase 学习笔记(一) Hbase的物理模型 Hbase为每个值维护了一个多级索引,即<key, column family, column name, timestamp>
比如第一个region 代表 0-100 第二个region 代表 101 -200的 分的越多越不好管理,但同时方便了并行化处理,并发度越高,处理的越快.mapreduce就是按照rowkey的 ...
- 10.我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。 请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?
我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形. 请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法? 是不是发现看不懂,哈哈:编程题就是这样,一定要归纳,手写过程: n ...
- W-TinyLFU——设计一个现代的缓存
缓存设计是个基础架构领域里的重要话题,本号之前也有谈论过相关话题,点击原文可以看之前的介绍. 近日,HighScalability网站刊登了一篇文章,由前Google工程师发明的W-TinyLFU—— ...
- Java实现一个简单的缓存方法
缓存是在web开发中经常用到的,将程序经常使用到或调用到的对象存在内存中,或者是耗时较长但又不具有实时性的查询数据放入内存中,在一定程度上可以提高性能和效率.下面我实现了一个简单的缓存,步骤如下. 创 ...
- java梳理-一个汉字占多大空间
面试题:一个汉字占多大空间. 事实上这个问题我了解不深的,知道结论不知道为什么.借此梳理下认识. 先回想下java基本类型 一基本类型 :简称四类八种,声明变量的同一时候分配了空间.举比例如以下: ...
- NSIS编译报错:您可能有有一个或两个(大)的旧临时文件
一.有时在编译NSIS时会出现如下错误: 注意: 您可能有有一个或两个(大)的旧临时文件 残留在临时目录文件夹中 (通常这种情况只会发生在 Windows 9x 系统中). 二.本人遇到的问题原因: ...
- 在Hibernate中使用Memcached作为一个二级分布式缓存
转自:http://www.blogjava.net/xmatthew/archive/2008/08/20/223293.html hibernate-memcached--在Hibernate ...
- SpringBoot,用200行代码完成一个一二级分布式缓存
缓存系统的用来代替直接访问数据库,用来提升系统性能,减小数据库复杂.早期缓存跟系统在一个虚拟机里,这样内存访问,速度最快. 后来应用系统水平扩展,缓存作为一个独立系统存在,如redis,但是每次从缓存 ...
随机推荐
- 聊聊微信小程序的隐私协议开发
为什么需要隐私协议? 小程序隐私授权弹窗FAQ官方:https://developers.weixin.qq.com/community/develop/doc/00000ebac5c3e042384 ...
- Android为按钮Button添加事件
匿名内部类 1 <!--匿名内部类方式--> 2 <Button 3 android:id="@+id/btn2" 4 android:layout_width= ...
- IPv4地址的结构体与网络字节序
IPv4地址的结构体 /* Fixed-size types, underlying types depend on word size and compiler. */ typedef signed ...
- IT的贵与慢
本文于2019年7月24日完成,发布在个人博客网站上. 考虑个人博客因某种原因无法修复,于是在博客园安家,之前发布的文章逐步搬迁过来. 笔记而已,没有逻辑. 贵与慢,一方面是事实,另一方面是偏见. 流 ...
- OpenHarmony父子组件单项同步使用:@Prop装饰器
@Prop装饰的变量可以和父组件建立单向的同步关系.@Prop装饰的变量是可变的,但是变化不会同步回其父组件. 说明: 从API version 9开始,该装饰器支持在ArkTS卡片中使用. 概述 ...
- C++ 异常和错误处理机制:如何使您的程序更加稳定和可靠
在C++编程中,异常处理和错误处理机制是非常重要的.它们可以帮助程序员有效地处理运行时错误和异常情况.本文将介绍C++中的异常处理和错误处理机制. 什么是异常处理? 异常处理是指在程序执行过程中发生异 ...
- 详讲openGauss 5.0 单点企业版如何部署_Centos7_x86
本文分享自华为云社区<openGauss 5.0 单点企业版部署_Centos7_x86>,本文作者:董小姐 本文档环境:CentOS7.9 x86_64 4G1C40G python2. ...
- 鸿蒙手表定位功能Demo体验,适用儿童、老年和外出旅游安全市场
针对儿童和老人,可穿戴的智能手表用处很大.市场也有许多类似的产品,支持接打电话.支付扫码.定位等功能,属于新兴的商业机会.依托华为品牌,鸿蒙手表也致力为用户打造精品的.产品质量佳.可穿戴的智能体验.对 ...
- cmd中怎么清屏--cls
平时我们在 Linux 系统中清除屏幕 用的命令是: clear 现在在Windows上用的清屏命令是 : cls
- HDC2021技术分论坛:跨端分布式计算技术初探
作者:zhengkai,分布式通信首席技术专家 当今的移动应用都向着智能化和多样化方向发展,例如AI辅助,VR/AR应用,沉浸式游戏等.然而现实中的移动设备,因为便携性要求受限于尺寸.电池容量以及温控 ...