因 Redis Key 命令不规范,导致熬了一个通宵才把Key删完了!

https://mp.weixin.qq.com/s/7FL0nUTk6aFmAb2J__5Mtw

 

 

 

因 Redis Key 命令不规范,导致熬了一个通宵才把Key删完了!

点击关注 👉 Java面试那些事儿 9月3日

# 前言

 

由于有一条业务线不理想,高层决定下架业务。对于我们技术团队而言,其对应的所有服务器资源和其他相关资源都要释放。释放了8台应用服务器;

 

  • 1台ES服务器;

  • 删除分布式定时任务中心相关的业务任务;

  • 备份并删除MySQL数据库;

  • 删除Redis中相关的业务缓存数据。

 

CTO指名点姓让我带头冲锋,才扣了我绩效……好吧,冲~

 

其他都还好,不多时就解决了。唯独这删除Redis中的数据,害得我又熬了一个通宵,真是折煞我也!

 

# 难点分析

 

共用Redis服务集群

 

由于这条业务线的数据在Redis大概在3G左右,完全没必要单独建一个Redis服务集群,本着能节约就节约的态度,当初就决定和其他项目共享一个集群(这个集群配置:16个节点,128G内存,还算豪华吧~)集群配置如下:

在这种共用集群的情况下,导致无法简单粗暴的释放。因此只能选择删除Key的方式。

 

Key命名不规范

 

要删除Key,首先就要精准的定位出哪些Key需要删除,如果勿删Key,会影响到其他服务正常运转!如果Key本身设置了过期时间,但有些数据需是持久化的。然而那该死的项目经理一直催项目进度,导致开发人员在开发过程中很多地方都没有设计到位,比如Redis Key散落在项目代码的每个角落;比如命名不是很规范。真不知道是怎么review代码!哦,想必是没有时间review,那该死的项目经理……  
 

我随便截个支付服务中的Key命名:

怎么样?是不是觉得我们开发人员写的代码很low~别笑,在实际工作中,还有比这更low的!希望你别遇到,不然真的很痛苦~

 

解决思路

 

经过以上的分析,我们简单归纳如下:

 

  • 我们真正关心的是那些未设置过期时间的Key

  • 不能误删除Key,否则下个月绩效也没了

  • 由于Key的命名及使用及其不规范,导致Key的定位难度很大

 

看来,通过scan命令扫描匹配Key的方式行不通了。只能通过人肉搜索了~  


幸而Idea的搜索大法好,这个项目中使用的是spring-boot-starter-data-redis.因此我通过搜索RedisTemplate和StringRedisTemplate定位所有操作redis的代码,具体步骤如下:

 

  1. 通过这些代码统计出Key的前缀并录入到文本中; 

  2. 通过python脚本把载入文中中的的Key并在后面加上“*”通配符; 

  3. 通过python脚本通过scan命令扫描出这些key; 

  4. 为了便于检查,我们并没有直接使用del命令删除key,在删除key之前,先通过debug object

 

key的方式得到其序列化的长度,再执行删除并返回序列化长度。这样,我们就可以统计出所有key的序列化长度来得到我们释放的空间大小。关键代码如下:

 
  •  
  •  
  •  
    def get_key(rdbConn,start):        try:        keys_list = rdbConn.scan(start,count=2000)        return keys_list        except Exception,e:        print e
    ''' Redis DEBUG OBJECT command got key info '''    def get_key_info(rdbConn,keyName):        try:        rpiple = rdbConn.pipeline()        rpiple.type(keyName)        rpiple.debug_object(keyName)        rpiple.ttl(keyName)        key_info_list = rpiple.execute()        return key_info_list        except Exception,e:        print "INFO : ",e
    def redis_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)

 

通过以上方式,能够统计出究竟释放了多少内存了。

 

由于这个集群是有这么接近7千万个key:

 

因此,等到了第二天天亮,我睡眼朦胧的看了一下,终于删除完毕了,时间07:13…早高峰即将来临……

 

知耻而后勇

 

从来没有经历过因业务下线而清除资源的经验。这次事情真心让我觉得细微之处见真功夫的道理。如果一开始我们就能够遵循开发规范来使用和设计redis key,也不至于浪费这么多时间。为了让key的命名和使用更加规范,以及今后避免再次遇到这种情况,下午睡醒之后,我就在redis公共组件库里面添加了一个配置和自定义了key序列化,代码如下:

 
  •  
  •  
  •  
    @ConfigurationProperties(prefix = "spring.redis.prefix")    public class RedisKeyPrefixProperties {        private Boolean enable = Boolean.TRUE;        private String key;        public Boolean getEnable() {            return enable;        }        public void setEnable(Boolean enable) {            this.enable = enable;        }        public String getKey() {            return key;        }        public void setKey(String key) {            this.key = key;        }    }    /**     * @desc 对字符串序列化新增前缀     * @author create by liming sun on 2020-07-21 14:09:51     */    public class PrefixStringKeySerializer extends StringRedisSerializer {        private Charset charset = StandardCharsets.UTF_8;        private RedisKeyPrefixProperties prefix;
        public PrefixStringKeySerializer(RedisKeyPrefixProperties prefix) {            super();            this.prefix = prefix;        }
        @Override        public String deserialize(@Nullable byte[] bytes) {            String saveKey = new String(bytes, charset);            if (prefix.getEnable() != null && prefix.getEnable()) {                String prefixKey = spliceKey(prefix.getKey());                int indexOf = saveKey.indexOf(prefixKey);                if (indexOf > 0) {                    saveKey = saveKey.substring(indexOf);                }            }            return (saveKey.getBytes() == null ? null : saveKey);        }
        @Override        public byte[] serialize(@Nullable String key) {            if (prefix.getEnable() != null && prefix.getEnable()) {                key = spliceKey(prefix.getKey()) + key;            }            return (key == null ? null : key.getBytes(charset));        }
        private String spliceKey(String prefixKey) {            if (StringUtils.isNotBlank(prefixKey) && !prefixKey.endsWith(":")) {                prefixKey = prefixKey + "::";            }            return prefixKey;        }    }
 

 

使用效果

 

为了避免再次发生这种工作低效而又不得不做的事情,我们在开发规范中规定,新项目中redis的使用必须设置此配置,前缀就设置为:项目编号。另外,一个模块中的key必须统一定义在二方库的RedisKeyConstant类中。配置如下:

 
  •  
  •  
    spring:         redis:             prefix:                enable: true                key: E00P01    @Bean    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();        redisTemplate.setConnectionFactory(redisConnectionFactory);        // 支持key前缀设置的key Serializer        redisTemplate.setKeySerializer(new PrefixStringKeySerializer());        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());        return redisTemplate;    }
 

通过以上方式,我们至少可以从项目维度来区分出key,避免了多个项目之间共用同一个集群时而导致重复key的问题。从项目维度对key进行了划分。更方便管理和运维。如果对于key的管理粒度要求更细,我们甚至可以细化到具体业务维度。

 

我们在测试环境进行了压测,增加key前缀对redis性能几乎没有影响。性能方面能接受。

 

# 总结

 

通过本次事情,我发现对于大多数开发者而言,差距其实不在于智力,而是在于态度。比如这次事件暴露出来的问题:大家都知道要遵循开发规范,然而到了真正“打仗”的时候,负责这个项目的开发者却没有几个人能始终如一的做好这些细微之事。另外,reviewer的工作其实是极其重要的,他就像那“纪检委”,如果“纪检委”都放水睁一只眼闭一只眼,那麻烦可就大了!千里之提,毁于日常的点滴松懈啊~~~

 

经过这次事件之后,如果上天再给一次这样的机会,我一定会对项目经理说:接着奏乐,接着舞!

来源:juejin.im/post/6854573215726075917

 

 

 

 

 

 

 

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/327347.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用 CefSharp 在 C# App 中嵌入 Chrome 浏览器

介绍 以前曾试过在app中整合一个可靠又快速的web浏览器吗&#xff1f; 在本文中&#xff0c;你会学到如何轻松地将奇妙的CefSharp网页浏览器组件&#xff08;基于Chromium&#xff09;集成到你的C# app中。 然后&#xff0c;你可以使用此web浏览器&#xff1a; 给用户提供一个集…

从入门到熟悉 HTTPS 的 9 个问题

转载自 从入门到熟悉 HTTPS 的 9 个问题 Q1: 什么是 HTTPS&#xff1f; BS: HTTPS 是安全的 HTTP HTTP 协议中的内容都是明文传输&#xff0c;HTTPS 的目的是将这些内容加密&#xff0c;确保信息传输安全。最后一个字母 S 指的是 SSL/TLS 协议&#xff0c;它位于 HTTP 协议…

Jexus针对Asp.net core应用程序的六大不可替代的优势

Jexus 是一款运行于 Linux 平台&#xff0c;以支持 ASP.NET、PHP 为特色的集高安全性和高性能为一体的 WEB 服务器和反向代理服务器。 1&#xff0c;配置简便&#xff1a;在Jexus上&#xff0c;Asp.net core只是Jexus上的一个“站点”&#xff0c;因此&#xff0c;只需在Jexus…

安装docker遇到的坑 Could not resolve host: download.docker.com;

我写的 1.编辑网卡 vim /etc/sysconfig/network-scripts/ifcfg-ens33 2.增加这三行 DNS18.8.8.8 DNS2114.114.114.114 PEERDNSno 3.最后重启网络service network restart 即可。不行就重启虚拟机 4.设置稳定的源yum-config-manager \ --add-repo \ https://download…

EasyExcel(笔记)

常用场景 1、将用户信息导出为excel表格&#xff08;导出数据…&#xff09; 2、将Excel表中的信息录入到网站数据库&#xff08;习题上传…&#xff09; 开发中经常会设计到excel的处理&#xff0c;如导出Excel&#xff0c;导入Excel到数据库中&#xff01; 操作Excel目前比…

细说Redis监控和告警

对于任何应用服务和组件&#xff0c;都需要一套完善可靠谱监控方案。尤其redis这类敏感的纯内存、高并发和低延时的服务&#xff0c;一套完善的监控告警方案&#xff0c;是精细化运营的前提。本文分几节&#xff0c;细说Redis的监控和告警&#xff1a;1.Redis监控告警的价值2.R…

curl和wget的区别和使用

https://www.cnblogs.com/wyaokai/p/11947379.html https://blog.csdn.net/IT_hejinrong/article/details/79361095 curl和wget的区别和使用 curl和wget基础功能有诸多重叠&#xff0c;如下载等。 非要说区别的话&#xff0c;curl由于可自定义各种请求参数所以在模拟web请求…

vmware启动多个虚拟机

启动顺序 这样4个虚拟机就启动了 也够使用了 (如果出现某个虚拟机不能启动貌似多重复全套流程就可以了) CentOS 64 位5swarm20201006_3_配合第三章register CentOS 64 位6 CentOS 64 位5swarm20201007_5_第三章学完的镜像 CentOS 64 位5swarm20200927 0&#xff09;关闭…

怎样在Redis通过StackExchange.Redis 存储集合类型List

StackExchange 是由StackOverFlow出品&#xff0c; 是对Redis的.NET封装&#xff0c;被越来越多的.NET开发者使用在项目中。绝大部分原先使用ServiceStack的开发者逐渐都转了过来&#xff0c;由于SS在其新版中不再开源&#xff0c;并对免费版本有所限制。 实际问题 那么用.NET的…

Java爬虫小测---ElasticSearch

项目搭建 1、启动ES&#xff0c;和head-master&#xff0c;用head-master建立索引 不建立也没事&#xff0c;添加数据的时候会自动创建 2、导入SpringBoot需要的依赖 注意&#xff1a;elasticsearch的版本要和自己本地的版本一致&#xff01;所以还要在pom里面添加自定义版本…

Gradle的安装与配置

https://www.cnblogs.com/NyanKoSenSei/p/11458953.html Gradle的安装与配置 1. Gradle简介 Gradle是源于Apache Ant和Apache Maven概念的项目自动化构建开源工具&#xff0c;它使用一种基于Groovy的特定领域语言(DSL)来声明项目设置&#xff0c;抛弃了基于XML的各种繁琐配置面…

使用 Roslyn 编译器服务

.NET Core和 .NET 4.6中 的C# 6/7 中的编译器Roslyn 一个重要的特性就是"Compiler as a Service"&#xff0c;简单的讲&#xff0c;就是就是将编译器开放为一种可在代码中调用的服务&#xff0c; 通常在工作流引擎 或是规则引擎中都需要一项功能是计算表达式&#xf…

Rest风格---ElasticSearch

Rest风格 5.1 简介 RESTful是一种架构的规范与约束、原则&#xff0c;符合这种规范的架构就是RESTful架构。 操作 methodurl地址描述PUTlocalhost:9100/索引名称/类型名称/文档id创建文档&#xff08;指定id&#xff09;POSTlocalhost:9100/索引名称/类型名称创建文档&…

IntelliJ IDEA如何导入Gradle项目

https://blog.csdn.net/wangdong5678999/article/details/70255451 IntelliJ IDEA如何导入Gradle项目 栋先生 2017-04-20 10:14:03 95942 收藏 分类专栏&#xff1a; 其他 文章标签&#xff1a; idea intellij idea gradle 版权 最近学习Gradle&#xff0c;本文来重点介绍…

加密货币的本质

转载自 加密货币的本质 去年&#xff0c;比特币暴涨&#xff0c;其他币也像雨后春笋一样冒出来&#xff0c;已经有1000多种了。很多人都在问&#xff0c;加密货币&#xff08;cryptocurrency&#xff09;的时代&#xff0c;真的来临了吗&#xff1f;将来会不会人类不再使用美元…

.net core 源码解析-web app是如何启动并接收处理请求

最近.net core 1.1也发布了&#xff0c;蹒跚学步的小孩又长高了一些&#xff0c;园子里大家也都非常积极的在学习&#xff0c;闲来无事&#xff0c;扒拔源码&#xff0c;涨涨见识。 先来见识一下web站点是如何启动的&#xff0c;如何接受请求,.net core web app最简单的例子,大…

关于文档的基本操作---ElasticSearch

关于文档的基本操作&#xff08;重点&#xff09; 基本操作 添加数据 PUT /psz/user/1 {"name": "psz","age": 22,"desc": "偶像派程序员","tags": ["暖","帅"] }获取数据 GEt psz/user/…

IdentityServer4 使用OpenID Connect添加用户身份验证

使用IdentityServer4 实现OpenID Connect服务端&#xff0c;添加用户身份验证。客户端调用&#xff0c;实现授权。 IdentityServer4 目前已更新至1.0 版&#xff0c;在之前的文章中有所介绍。IdentityServer4 ASP.NET Core的OpenID Connect OAuth 2.0框架学习保护API 。 本文环…

深度优先搜索和广度优先搜索

转载自 深度优先搜索和广度优先搜索 图的应用很广泛&#xff0c;也有很多非常有用的算法&#xff0c;当然也有很多待解决的问题&#xff0c;根据性质&#xff0c;图可以分为无向图和有向图。 图 之所以要研究图&#xff0c;是因为图在生活中应用比较广泛&#xff1a; 图是若…

消息队列 Kafka 的基本知识及 .NET Core 客户端

前言 最新项目中要用到消息队列来做消息的传输&#xff0c;之所以选着 Kafka 是因为要配合其他 java 项目中&#xff0c;所以就对 Kafka 了解了一下&#xff0c;也算是做个笔记吧。 本篇不谈论 Kafka 和其他的一些消息队列的区别&#xff0c;包括性能及其使用方式。 简介 Kafka…