当前位置: 首页 > 科技观察

熬了一夜,终于把里德斯的7000万个key给删了,今天脑子里嗡嗡作响!_0

时间:2023-03-18 11:48:31 科技观察

前言因为一个业务线不理想,高层决定撤掉这个业务。对于我们的技术团队来说,所有对应的服务器资源和其他相关资源都必须释放。发布了8个应用服务器;1个es服务器;删除了分布式定时任务中心相关的业务任务;备份并删除MySQL数据库;删除了Redis中的相关业务缓存数据。CTO点名给我,让我带头冲锋,结果扣了我的业绩……好吧,冲锋~其他都没事,很快就解决了。唯一删除Redis中的数据,让我又熬了一夜,真是气死我了!难点分析共享Redis服务集群由于该业务线的数据在Redis中有3G左右,所以不需要单独搭建Redis服务集群。本着节约的态度,我们决定和其他项目共享一个集群(这个集群配置:16个节点,128G内存,相当豪华~)集群配置如下:在这个共享集群的情况下,不能简单粗暴地释放。因此,唯一的选择就是删除Key。按键命名不规范。删除一个key,首先要准确定位到需要删除哪些key。如果不删除key,会影响其他服务的正常运行!如果密钥本身设置了过期时间,则需要持久化一些数据。然而该死的项目经理却一直催促项目进度,导致开发者在开发过程中很多地方都没有设计好,比如RedisKey散落在项目代码的各个角落;例如,命名不是很规范。我真的不知道如何审查代码!哦,肯定是没时间审核,那该死的项目经理……我在支付服务中随便点了个Key:how?是不是觉得我们开发人员写的代码很low啊~别笑,实际工作中,还有比这更低的代码!希望大家不要遇到,不然真的很痛苦~解决思路经过上面的分析,我们简单总结如下:我们真正关心的是没有设置过期时间的Key不能被误删除,否则下个月的性能将因键的命名和使用极其不规范而导致键定位非常困难。看来通过scan命令扫描匹配key的方式不行。只能靠人肉搜索了~还好Idea的搜索方式不错。本项目使用spring-boot-starter-data-redis。因此,我搜索了RedisTemplate和StringRedisTemplate,定位所有操作redis的代码。具体步骤如下:1.使用这些代码统计出Key的前缀,并输入到文本中;2、使用python脚本加载文本中的Key,并在其后添加“*”通配符;3、使用scan命令通过python脚本扫描出这些key;4.为了检查方便,我们没有直接使用del命令删除key。在删除key之前,我们先通过debug对象key获取到它的序列化长度,然后执行删除并返回序列化长度。这样我们就可以统计所有key的序列化长度,从而得到我们释放的空间大小。关键代码如下:defget_key(rdbConn,start):try:keys_list=rdbConn.scan(start,count=2000)returnkeys_listexceptException,e:printe'''RedisDEBUGOBJECTcommandgotkeyinfo'''defget_key_info(rdbConn,keyName):try:rpiple=rdbConn。管道()rpiple.type(keyName)rpiple.debug_object(keyName)rpiple.ttl(keyName)key_info_list=rpiple.execute()returnkey_info_listexceptException,e:print"INFO:",edefredis_key_static(key_info_list):keyType=key_info_list[0]keySize=key_info_list[1]['serializedlength']keyTtl=key_info_list[2]key_size_static(keyType,keySize,keyTtl)通过上面的方法可以统计释放了多少内存。因为这个集群有将近7000万个key:所以,第二天天亮的时候,睡眼惺忪的看着,终于在07:13删除了。。早高峰来了。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。从未有过因业务下线而清理资源的经历。这件事真的让我感受到了真功夫是在细微之处见真情的。如果能够一开始就按照开发规范来使用和设计rediskey,就不会浪费那么多时间了。为了让key的命名和使用更加规范,也为了避免以后再次出现这种情况,下午醒来后,在redis公共组件库中添加了一个配置和自定义key序列化。代码如下:@ConfigurationProperties(prefix="spring.redis.prefix")publicclassRedisKeyPrefixProperties{privateBooleanenable=Boolean.TRUE;privateStringkey;publicBooleangetEnable(){returnenable;}publicvoidsetEnable(Booleanenable){this.enable=enable;}publicStringgetKey(){returnkey;}publicvoidsetKey(Stringkey){this.key=key;}}/***@desc为字符串序列化添加前缀*@authorcreatebylimingsunon2020-07-2114:09:51*/publicclassPrefixStringKeySerializerextendsStringRedisSerializer{privateCharsetcharset=StandardCharsets.UTF_8;privateRedisKeyPrefixPropertiesprefix;publicRedisKeyPrefixPropertiesprefix;publicKeySerializerProfixRedisKeySerializer){super();this.prefix=prefix;}@OverridepublicStringdeserialize(@Nullablebyte[]bytes){StringsaveKey=newString(bytes,charset);if(prefix.getEnable()!=null&&prefix.getEnable()){StringprefixKey=spliceKey(prefix.getKey());intindexOf=saveKey.indexOf(prefixKey);if(indexOf>0){saveKeyysaveKey=saveKey.substring(indexOf);}}return(saveKey.getBytes()==null?null:saveKey);}@Overridepublicbyte[]serialize(@NullableStringkey){if(prefix.getEnable()!=null&&prefix.getEnable()){key=spliceKey(prefix.getKey())+key;}return(key==null?null:key.getBytes(charset));}privateStringspliceKey(StringprefixKey){if(StringUtils.isNotBlank(prefixKey)&&!prefixKey.endsWith(":")){prefixKeyprefixKey=prefixKey+"::";}returnprefixKey;}}使用效果为了避免这种低效的工作不得不重做,我们在开发规范中规定,在新项目中使用redis必须设置这个配置,前缀设置为:项目编号另外,一个模块中的key必须在二方库的RedisKeyConstant类中统一定义。配置如下:spring:redis:prefix:enable:truekey:E00P01@BeanpublicRedisTemplateredisTemplate(RedisConnectionFactoryredisConnectionFactory){RedisTemplateredisTemplate=newRedisTemplate<>();redisTemplate.setConnectionFactory(redisConnectionFactory);//支持key前缀设置keySerializerredisTemplate.setKeySerializer(newPrefixStringKeySerializer());redisTemplate.setValueSerializer(newGenericJackson2JsonRedisSerializer());returnredisTemplate;}通过上面的方法,我们至少可以从项目维度区分key,避免多个项目共享同一个集群有时会导致key重复的问题。关键是从项目维度来划分。更易于管理和维护。如果对密钥管理的粒度要求更细,我们甚至可以细化到具体的业务维度。我们在测试环境进行了压力测试,添加key前缀对redis性能影响不大。性能是可以接受的。