<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          熬了一個通宵,終于把 7 千萬個 Key 刪完了

          共 5094字,需瀏覽 11分鐘

           ·

          2020-09-05 07:07

          程序員的成長之路
          互聯(lián)網(wǎng)/程序員/技術(shù)/資料共享?
          關(guān)注


          閱讀本文大概需要 5 分鐘。

          來自:https://juejin.im/post/5f18423fe51d453493113f5c


          釋放了 8 臺應(yīng)用服務(wù)器;1 臺 ES 服務(wù)器;刪除分布式定時任務(wù)中心相關(guān)的業(yè)務(wù)任務(wù);備份并刪除 MySQL 數(shù)據(jù)庫;刪除 Redis 中相關(guān)的業(yè)務(wù)緩存數(shù)據(jù)。

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

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

          難點分析


          共用 Redis 服務(wù)集群


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


          集群配置如下:

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


          Key 命名不規(guī)范


          要刪除 Key,首先就要精準(zhǔn)的定位出哪些 Key 需要刪除,如果勿刪 Key,會影響到其他服務(wù)正常運轉(zhuǎn)!


          如果 Key 本身設(shè)置了過期時間,但有些數(shù)據(jù)需是持久化的。然而那該死的項目經(jīng)理一直催項目進(jìn)度,導(dǎo)致開發(fā)人員在開發(fā)過程中很多地方都沒有設(shè)計到位。


          比如 Redis Key 散落在項目代碼的每個角落;比如命名不是很規(guī)范。


          真不知道是怎么 Review 代碼!哦,想必是沒有時間 Review,那該死的項目經(jīng)理……


          我隨便截個支付服務(wù)中的 Key 命名:

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


          解決思路


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

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

          • 不能誤刪除 Key,否則下個月績效也沒了。

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


          看來,通過 Scan 命令掃描匹配 Key 的方式行不通了。只能通過人肉搜索了。

          幸而 Idea 的搜索大法好,這個項目中使用的是 spring-boot-starter-data-redis。

          因此我通過搜索 RedisTemplate 和 StringRedisTemplate 定位所有操作 Redis 的代碼。

          具體步驟如下:
          • 通過這些代碼統(tǒng)計出 Key 的前綴并錄入到文本中。

          • 通過 Python 腳本把載入文中中的的 Key 并在后面加上“*”通配符。

          • 通過 Python 腳本通過 Scan 命令掃描出這些 Key。

          • 為了便于檢查,我們并沒有直接使用 Del 命令刪除 Key,在刪除 Key 之前,先通過 debug object key 的方式得到其序列化的長度,再執(zhí)行刪除并返回序列化長度。這樣,我們就可以統(tǒng)計出所有 Key 的序列化長度來得到我們釋放的空間大小。


          關(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)

          通過以上方式,能夠統(tǒng)計出究竟釋放了多少內(nèi)存了。由于這個集群是有特么接近 7 千萬個 Key:
          因此,等到了第二天天亮,我睡眼朦朧的看了一下,終于刪除完畢了,時間 07:13,早高峰即將來臨……

          知恥而后勇


          從來沒有經(jīng)歷過因業(yè)務(wù)下線而清除資源的經(jīng)驗。這次事情真心讓我覺得細(xì)微之處見真功夫的道理。


          如果一開始我們就能夠遵循開發(fā)規(guī)范來使用和設(shè)計 Redis Key,也不至于浪費這么多時間。


          為了讓 Key 的命名和使用更加規(guī)范,以及今后避免再次遇到這種情況,下午睡醒之后,我就在 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;
          ????}
          }

          使用效果:為了避免再次發(fā)生這種工作低效而又不得不做的事情,我們在開發(fā)規(guī)范中規(guī)定,新項目中 Redis 的使用必須設(shè)置此配置,前綴就設(shè)置為:項目編號。


          另外,一個模塊中的 Key 必須統(tǒng)一定義在二方庫的 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;
          }

          通過以上方式,我們至少可以從項目維度來區(qū)分出 Key,避免了多個項目之間共用同一個集群時而導(dǎo)致重復(fù) Key 的問題。

          從項目維度對 Key 進(jìn)行了劃分。更方便管理和運維。如果對于 Key 的管理粒度要求更細(xì),我們甚至可以細(xì)化到具體業(yè)務(wù)維度。

          我們在測試環(huán)境進(jìn)行了壓測,增加 Key 前綴對 Redis 性能幾乎沒有影響。性能方面能接受。

          總結(jié)


          通過本次事情,我發(fā)現(xiàn)對于大多數(shù)開發(fā)者而言,差距其實不在于智力,而是在于態(tài)度。


          比如這次事件暴露出來的問題:大家都知道要遵循開發(fā)規(guī)范,然而到了真正“打仗”的時候,負(fù)責(zé)這個項目的開發(fā)者卻沒有幾個人能始終如一的做好這些細(xì)微之事。


          另外,Reviewer 的工作其實是極其重要的,他就像那“紀(jì)檢委”,如果“紀(jì)檢委”都放水睜一只眼閉一只眼,那麻煩可就大了!千里之提,毀于日常的點滴松懈?。?/span>


          經(jīng)過這次事件之后,如果上天再給一次這樣的機會,我一定會對項目經(jīng)理說:接著奏樂,接著舞!

          推薦閱讀:

          騷操作:不重啟 JVM,如何替換掉已經(jīng)加載的類?

          哈哈哈,Chrome 被黑的最慘一次

          5T技術(shù)資源大放送!包括但不限于:C/C++,Linux,Python,Java,PHP,人工智能,單片機,樹莓派,等等。在公眾號內(nèi)回復(fù)「2048」,即可免費獲?。?!

          微信掃描二維碼,關(guān)注我的公眾號

          寫留言

          朕已閱?

          瀏覽 26
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  青娱乐亚洲精品视频线 | 免费黄色成人视频网址 | 精品嫩草AV片导航 | 日韩无码黄色 | 人妻中文一区在线 |