當(dāng)前位置:首頁(yè) > 公眾號(hào)精選 > 架構(gòu)師社區(qū)
[導(dǎo)讀]由于有一條業(yè)務(wù)線不理想,高層決定下架業(yè)務(wù)。對(duì)于我們技術(shù)團(tuán)隊(duì)而言,其對(duì)應(yīng)的所有服務(wù)器資源和其他相關(guān)資源都要釋放。釋放了8臺(tái)應(yīng)用服務(wù)器;其他都還好,不多時(shí)就解決了。唯獨(dú)這刪除Redis中的數(shù)據(jù),害得我又熬了一個(gè)通宵,真是折煞我也!

來(lái)源:http://dwz.date/cmSG

前言

由于有一條業(yè)務(wù)線不理想,高層決定下架業(yè)務(wù)。對(duì)于我們技術(shù)團(tuán)隊(duì)而言,其對(duì)應(yīng)的所有服務(wù)器資源和其他相關(guān)資源都要釋放。釋放了8臺(tái)應(yīng)用服務(wù)器;

  • 1臺(tái)es服務(wù)器;

  • 刪除分布式定時(shí)任務(wù)中心相關(guān)的業(yè)務(wù)任務(wù);

  • 備份并刪除MySQL數(shù)據(jù)庫(kù);

  • 刪除Redis中相關(guān)的業(yè)務(wù)緩存數(shù)據(jù)。

CTO指名點(diǎn)姓讓我?guī)ь^沖鋒,才扣了我績(jī)效……好吧,沖~

其他都還好,不多時(shí)就解決了。唯獨(dú)這刪除Redis中的數(shù)據(jù),害得我又熬了一個(gè)通宵,真是折煞我也!

難點(diǎn)分析

共用Redis服務(wù)集群

由于這條業(yè)務(wù)線的數(shù)據(jù)在Redis大概在3G左右,完全沒(méi)必要單獨(dú)建一個(gè)Redis服務(wù)集群,本著能節(jié)約就節(jié)約的態(tài)度,當(dāng)初就決定和其他項(xiàng)目共享一個(gè)集群(這個(gè)集群配置:16個(gè)節(jié)點(diǎn),128G內(nèi)存,還算豪華吧~)集群配置如下:

因 Redis Key 命令不規(guī)范,導(dǎo)致熬了一個(gè)通宵才把Key刪完了!

在這種共用集群的情況下,導(dǎo)致無(wú)法簡(jiǎn)單粗暴的釋放。因此只能選擇刪除Key的方式。

Key命名不規(guī)范

要?jiǎng)h除Key,首先就要精準(zhǔn)的定位出哪些Key需要?jiǎng)h除,如果勿刪Key,會(huì)影響到其他服務(wù)正常運(yùn)轉(zhuǎn)!如果Key本身設(shè)置了過(guò)期時(shí)間,但有些數(shù)據(jù)需是持久化的。然而那該死的項(xiàng)目經(jīng)理一直催項(xiàng)目進(jìn)度,導(dǎo)致開(kāi)發(fā)人員在開(kāi)發(fā)過(guò)程中很多地方都沒(méi)有設(shè)計(jì)到位,比如Redis
Key散落在項(xiàng)目代碼的每個(gè)角落;比如命名不是很規(guī)范。真不知道是怎么review代碼!哦,想必是沒(méi)有時(shí)間review,那該死的項(xiàng)目經(jīng)理……
我隨便截個(gè)支付服務(wù)中的Key命名:

因 Redis Key 命令不規(guī)范,導(dǎo)致熬了一個(gè)通宵才把Key刪完了!

怎么樣?是不是覺(jué)得我們開(kāi)發(fā)人員寫的代碼很low~別笑,在實(shí)際工作中,還有比這更low的!希望你別遇到,不然真的很痛苦~

解決思路

經(jīng)過(guò)以上的分析,我們簡(jiǎn)單歸納如下:

  • 我們真正關(guān)心的是那些未設(shè)置過(guò)期時(shí)間的Key

  • 不能誤刪除Key,否則下個(gè)月績(jī)效也沒(méi)了

  • 由于Key的命名及使用及其不規(guī)范,導(dǎo)致Key的定位難度很大

看來(lái),通過(guò)scan命令掃描匹配Key的方式行不通了。只能通過(guò)人肉搜索了~
幸而Idea的搜索大法好,這個(gè)項(xiàng)目中使用的是spring-boot-starter-data-redis.因此我通過(guò)搜索RedisTemplate和StringRedisTemplate定位所有操作redis的代碼,具體步驟如下:

  1. 通過(guò)這些代碼統(tǒng)計(jì)出Key的前綴并錄入到文本中;

  2. 通過(guò)python腳本把載入文中中的的Key并在后面加上“*”通配符;

  3. 通過(guò)python腳本通過(guò)scan命令掃描出這些key;

  4. 為了便于檢查,我們并沒(méi)有直接使用del命令刪除key,在刪除key之前,先通過(guò)debug object

key的方式得到其序列化的長(zhǎng)度,再執(zhí)行刪除并返回序列化長(zhǎng)度。這樣,我們就可以統(tǒng)計(jì)出所有key的序列化長(zhǎng)度來(lái)得到我們釋放的空間大小。關(guān)鍵代碼如下:

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)

通過(guò)以上方式,能夠統(tǒng)計(jì)出究竟釋放了多少內(nèi)存了。

由于這個(gè)集群是有這么接近7千萬(wàn)個(gè)key:

因 Redis Key 命令不規(guī)范,導(dǎo)致熬了一個(gè)通宵才把Key刪完了!

因此,等到了第二天天亮,我睡眼朦朧的看了一下,終于刪除完畢了,時(shí)間07:13…早高峰即將來(lái)臨……

知恥而后勇

從來(lái)沒(méi)有經(jīng)歷過(guò)因業(yè)務(wù)下線而清除資源的經(jīng)驗(yàn)。這次事情真心讓我覺(jué)得細(xì)微之處見(jiàn)真功夫的道理。如果一開(kāi)始我們就能夠遵循開(kāi)發(fā)規(guī)范來(lái)使用和設(shè)計(jì)redis key,也不至于浪費(fèi)這么多時(shí)間。為了讓key的命名和使用更加規(guī)范,以及今后避免再次遇到這種情況,下午睡醒之后,我就在redis公共組件庫(kù)里面添加了一個(gè)配置和自定義了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 對(duì)字符串序列化新增前綴
?????* @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;
????????}
????}

使用效果

為了避免再次發(fā)生這種工作低效而又不得不做的事情,我們?cè)陂_(kāi)發(fā)規(guī)范中規(guī)定,新項(xiàng)目中redis的使用必須設(shè)置此配置,前綴就設(shè)置為:項(xiàng)目編號(hào)。另外,一個(gè)模塊中的key必須統(tǒng)一定義在二方庫(kù)的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前綴設(shè)置的key?Serializer redisTemplate.setKeySerializer(new PrefixStringKeySerializer());
????????redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); return redisTemplate;
????}

通過(guò)以上方式,我們至少可以從項(xiàng)目維度來(lái)區(qū)分出key,避免了多個(gè)項(xiàng)目之間共用同一個(gè)集群時(shí)而導(dǎo)致重復(fù)key的問(wèn)題。從項(xiàng)目維度對(duì)key進(jìn)行了劃分。更方便管理和運(yùn)維。如果對(duì)于key的管理粒度要求更細(xì),我們甚至可以細(xì)化到具體業(yè)務(wù)維度。我們?cè)跍y(cè)試環(huán)境進(jìn)行了壓測(cè),增加key前綴對(duì)redis性能幾乎沒(méi)有影響。性能方面能接受。

總結(jié)

通過(guò)本次事情,我發(fā)現(xiàn)對(duì)于大多數(shù)開(kāi)發(fā)者而言,差距其實(shí)不在于智力,而是在于態(tài)度。比如這次事件暴露出來(lái)的問(wèn)題:大家都知道要遵循開(kāi)發(fā)規(guī)范,然而到了真正“打仗”的時(shí)候,負(fù)責(zé)這個(gè)項(xiàng)目的開(kāi)發(fā)者卻沒(méi)有幾個(gè)人能始終如一的做好這些細(xì)微之事。另外,reviewer的工作其實(shí)是極其重要的,他就像那“紀(jì)檢委”,如果“紀(jì)檢委”都放水睜一只眼閉一只眼,那麻煩可就大了!千里之提,毀于日常的點(diǎn)滴松懈啊~~~

經(jīng)過(guò)這次事件之后,如果上天再給一次這樣的機(jī)會(huì),我一定會(huì)對(duì)項(xiàng)目經(jīng)理說(shuō):接著奏樂(lè),接著舞~


如有收獲,點(diǎn)個(gè)在看,誠(chéng)摯感謝

免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問(wèn)題,請(qǐng)聯(lián)系我們,謝謝!

本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
關(guān)閉
關(guān)閉