
由于有一條業務線不理想,高層決定下架業務。對于我們技術團隊而言,其對應的所有服務器資源和其他相關資源都要釋放。
釋放了 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 的代碼。
具體步驟如下:
- 通過這些代碼統計出 Key 的前綴并錄入到文本中。
- 通過 Python 腳本把載入文中中的的 Key 并在后面加上“*”通配符。
- 通過 Python 腳本通過 Scan 命令掃描出這些 Key。
- 為了便于檢查,我們并沒有直接使用 Del 命令刪除 Key,在刪除 Key 之前,先通過 debug object key 的方式得到其序列化的長度,再執行刪除并返回序列化長度。這樣,我們就可以統計出所有 Key 的序列化長度來得到我們釋放的空間大小。
關鍵代碼如下:
def get_key(rdbConn,start):try:keys_list = rdbConn.scan(start,count=2000)return keys_listexcept 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_listexcept 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;}@Overridepublic 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);}@Overridepublic 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: truekey: E00P01
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();redisTemplate.setConnectionFactory(redisConnectionFactory);// 支持key前綴設置的key SerializerredisTemplate.setKeySerializer(new PrefixStringKeySerializer());redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());return redisTemplate;
}
通過以上方式,我們至少可以從項目維度來區分出 Key,避免了多個項目之間共用同一個集群時而導致重復 Key 的問題。
從項目維度對 Key 進行了劃分。更方便管理和運維。如果對于 Key 的管理粒度要求更細,我們甚至可以細化到具體業務維度。
我們在測試環境進行了壓測,增加 Key 前綴對 Redis 性能幾乎沒有影響。性能方面能接受。
總結
通過本次事情,我發現對于大多數開發者而言,差距其實不在于智力,而是在于態度。
比如這次事件暴露出來的問題:大家都知道要遵循開發規范,然而到了真正“打仗”的時候,負責這個項目的開發者卻沒有幾個人能始終如一的做好這些細微之事。
另外,Reviewer 的工作其實是極其重要的,他就像那“紀檢委”,如果“紀檢委”都放水睜一只眼閉一只眼,那麻煩可就大了!千里之提,毀于日常的點滴松懈啊!
經過這次事件之后,如果上天再給一次這樣的機會,我一定會對項目經理說:接著奏樂,接著舞!
編輯:浪漫先生