<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>

          記錄一次生產(chǎn)環(huán)境中Redis內(nèi)存增長異常排查全流程!

          共 3806字,需瀏覽 8分鐘

           ·

          2020-10-19 14:14


          ??

          作者:z小趙

          ★?

          一枚用心堅(jiān)持寫原創(chuàng)的“無趣”程序猿,在自身受益的同時(shí)也讓朋友們?cè)诩夹g(shù)上有所提升。


          最近 DBA 反饋線上的一個(gè) Redis 資源已經(jīng)超過了預(yù)先設(shè)計(jì)時(shí)的容量,并且已經(jīng)進(jìn)行了兩次擴(kuò)容,內(nèi)存增長還在持續(xù)中,希望業(yè)務(wù)方排查一下容量增長是否正常,若正常則期望重新評(píng)估資源的使用情況,若不正常請(qǐng)盡快查明問題并給出解決方案進(jìn)行處理。

          問題現(xiàn)象

          下面是當(dāng)時(shí)資源容量使用和 key 數(shù)量的監(jiān)控情況:

          從監(jiān)控可以看出,6.1 號(hào)開始容量和 keys 的增長陡增。首先懷疑是有惡意刷量的操作導(dǎo)致 key 數(shù)量增加比較多,經(jīng)過代碼排查后發(fā)現(xiàn),確實(shí)有代碼漏洞導(dǎo)致可以惡意刷量,后經(jīng)過 bug 修復(fù)后上線。你以為到這里就完事了?天真,要不然寫這篇文章還有什么用?

          但是從接口的請(qǐng)求量來看的話,刷量的情況并沒有那么明顯,下面是接口請(qǐng)求的 qps:

          繼續(xù)排查存儲(chǔ)設(shè)計(jì)發(fā)現(xiàn),存儲(chǔ)使用了 Set 結(jié)構(gòu)(由于產(chǎn)品最開始沒有明確說明一個(gè) key 下存儲(chǔ)多少元素,所以采用了 Set,這也為后續(xù)容量異常增長打下了 堅(jiān)實(shí)的基礎(chǔ) ),實(shí)際每個(gè) Set 中只存儲(chǔ)了一個(gè)元素,但實(shí)際容量卻增長了 30M,根據(jù)容量計(jì)算公式 9w * 14(key 長度) * 1(元素個(gè)數(shù)) * 10(元素長度)= 8.4M,實(shí)際容量卻超出了近 4 倍。

          到此,產(chǎn)生了兩個(gè)懷疑點(diǎn),其一:實(shí)際存儲(chǔ)的數(shù)據(jù)占用內(nèi)存大小有問題,其二:容量評(píng)估公式有問題

          疑點(diǎn)一:實(shí)際存儲(chǔ)的數(shù)據(jù)占用內(nèi)存大小有問題

          查看 Redis 的 Set 底層存儲(chǔ)結(jié)構(gòu)發(fā)現(xiàn),Set 集合采用了整數(shù)集合和字典兩種方式來實(shí)現(xiàn)的,當(dāng)滿足如下兩個(gè)條件的時(shí)候,采用整數(shù)集合實(shí)現(xiàn);一旦有一個(gè)條件不滿足時(shí)則采用字典來實(shí)現(xiàn)。

          • Set 集合中的所有元素都為整數(shù)
          • Set 集合中的元素個(gè)數(shù)不大于 512(默認(rèn) 512,可以通過修改 set-max-intset-entries 配置調(diào)整集合大小)

          關(guān)于詳細(xì)內(nèi)容,可以查看作者寫過的這篇內(nèi)容:連底層存儲(chǔ)你都不知道,敢說 Redis 用的很溜

          排查到這里,按照正常情況的話,其應(yīng)該采用的是整數(shù)集合存儲(chǔ)的才對(duì)啊,但是登陸到機(jī)器上,使用 memory usage key 命令查看內(nèi)存的使用情況發(fā)現(xiàn),單個(gè) key 的內(nèi)存占用竟然達(dá)到了 218B :

          是不是覺得不可思議,只是存儲(chǔ)一個(gè) 10 位的數(shù)據(jù)且內(nèi)容是數(shù)字,為什么會(huì)占用這么大的內(nèi)存呢?

          到此我們開始懷疑是不是序列化的方式有問題導(dǎo)致實(shí)際寫入 Redis 的內(nèi)容不是一個(gè)數(shù)字,所以接著查數(shù)據(jù)實(shí)際寫入時(shí)用的序列化方式。經(jīng)過排查發(fā)現(xiàn)寫入的 value 被序列化以后變成了一個(gè)十六進(jìn)制的數(shù)據(jù),到這里這個(gè)疑點(diǎn)基本就真相大白了,因?yàn)榇藭r(shí) Redis 認(rèn)為存儲(chǔ)的內(nèi)容已經(jīng)不適用于整數(shù)集合存儲(chǔ)了,而改為字典存儲(chǔ)。將序列化方式修改以后,測(cè)試添加一個(gè)元素進(jìn)去,發(fā)現(xiàn)實(shí)際內(nèi)存占用只有 72B,縮減為原來的 1/3:

          疑點(diǎn)二:容量評(píng)估公式有問題

          容量評(píng)估公式忽略了 Redis 對(duì)于不同的情況下內(nèi)存的占用情況,統(tǒng)一按照元素的大小去計(jì)算,導(dǎo)致實(shí)際內(nèi)容占用過小。

          到此,整個(gè) Redis 內(nèi)存容量增長異常基本上可以告一段路了,接下來就是修改正確的序列化和反序列化方式,然后進(jìn)行洗庫操作,經(jīng)業(yè)務(wù)調(diào)查發(fā)現(xiàn),目前的實(shí)際使用方式可以改為 KV 結(jié)構(gòu),所以將底層存儲(chǔ)進(jìn)行了改造。

          洗庫流程介紹

          1. 上線雙寫邏輯
          2. 同步歷史數(shù)據(jù)
          3. 切換讀取新數(shù)據(jù)源
          4. 觀察線上業(yè)務(wù)是否正常
          5. 關(guān)閉舊存儲(chǔ)的寫入
          6. 刪除舊資源
          7. 下線舊的讀寫邏輯

          關(guān)于新數(shù)據(jù)的存儲(chǔ)位置有兩種選擇,

          • 第一種方式是:舊數(shù)據(jù)正常寫舊資源,新數(shù)據(jù)寫到新部署的資源下。此種方式的優(yōu)點(diǎn)是,將舊數(shù)據(jù)全量洗入新資源后,然后下線舊資源就可以了;缺點(diǎn)是,需要在代碼層重新寫一套到資源的配置,DBA 也需要新部署一個(gè)資源。
          • 第二種方式是:新舊數(shù)據(jù)都寫到舊資源里面,然后將舊數(shù)據(jù)映射到新數(shù)據(jù)結(jié)構(gòu)上,然后全量洗入舊資源。此種方式的優(yōu)點(diǎn)是,不需要重新寫一套到資源的配置,DBA 也不需要新部署資源,只需要將舊資源的內(nèi)存進(jìn)行擴(kuò)容操作即可;缺點(diǎn)是,全量數(shù)據(jù)洗入完成后,需要手動(dòng)剔除舊數(shù)據(jù)。

          兩種方案都可行,可以根據(jù)自己的喜好來選擇,我們最終選擇了第二種方案進(jìn)行數(shù)據(jù)清洗操作。

          上線雙寫邏輯

          在資源存儲(chǔ)層,對(duì)上下行讀寫操作分別增加 switcher(開關(guān)),然后增加讀寫新存儲(chǔ)的邏輯,代碼測(cè)試通過后上線。

          這一步的流程在于開關(guān),可以選擇熱部署的任何方式來修改標(biāo)志位,從而控制代碼流程的執(zhí)行,另外需要注意的一點(diǎn)是:開關(guān)狀態(tài)的修改不能被工程上下線所影響

          同步歷史數(shù)據(jù)

          上線完成后,導(dǎo)出線上庫的 RDB 文件,解析出所有 key(關(guān)于 RDB 文件的解析,如果有專門的 DBA 同事,可以讓 DBA 同事給解析好,如果沒有的話,可以自己在網(wǎng)上查查 RDB 文件解析的工具,也不是很難);依次遍歷解析出來的 key,查詢 key 對(duì)應(yīng)的舊數(shù)據(jù),將舊數(shù)據(jù)映射到新數(shù)據(jù)結(jié)構(gòu)下,最后寫入到新的存儲(chǔ)下。

          關(guān)于同步歷史數(shù)據(jù),需要根據(jù)自身實(shí)際的業(yè)務(wù)場(chǎng)景去做適當(dāng)?shù)恼{(diào)整,這里只提供一個(gè)思路。下面是洗數(shù)據(jù)可以使用的小工具,需要的朋友可以適當(dāng)調(diào)整代碼邏輯就可以使用了:

          public class fixData {
          public static void main(String[] args) {
          String fileName = "test.txt";
          int rate = 500;
          int size = 200;
          if (args != null) {
          fileName = args[0];
          rate = Integer.parseInt(args[1]);
          size = Integer.parseInt(args[2]);
          }
          RateLimiter rateLimiter = RateLimiter.create(rate);
          ThreadPoolExecutor executorService = new ThreadPoolExecutor(size, size, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue());
          executorService.prestartAllCoreThreads();

          try {
          FileReader fr = new FileReader(fileName);
          LineNumberReader br = new LineNumberReader(fr);
          String line;
          while ((line = br.readLine()) != null) {
          try {
          rateLimiter.acquire();
          executorService.submit(() -> {
          // TODO 編寫自己的數(shù)據(jù)處理邏輯
          });
          } catch (Exception e) {
          e.printStackTrace();
          }
          }
          } catch (Exception e) {
          e.printStackTrace();
          }
          System.exit(0);
          }
          }

          切換讀取新數(shù)據(jù)源

          歷史所以數(shù)據(jù)同步完成后,將讀操作的開關(guān)關(guān)閉,讓其走讀新存儲(chǔ)的邏輯

          這一步需要注意的是,此時(shí)只修改下行讀取數(shù)據(jù)的開關(guān)狀態(tài),讓其讀取新數(shù)據(jù)源,上行寫入數(shù)據(jù)開關(guān)不動(dòng),依舊讓其進(jìn)行雙寫操作,防止下行切到新數(shù)據(jù)源有問題需要回滾導(dǎo)致新舊數(shù)據(jù)不一致的尷尬情況發(fā)生。

          觀察線上業(yè)務(wù)是否正常

          切到讀新存儲(chǔ)的邏輯下,觀察線上業(yè)務(wù),有無用戶投訴數(shù)據(jù)異常的情況

          關(guān)閉就存儲(chǔ)的寫入

          線上業(yè)務(wù)無異常情況,將寫操作也切到只寫新存儲(chǔ)的邏輯下,停止舊資源的寫入

          刪除舊資源

          將寫上所有舊 key 全部剔除,剔除舊數(shù)據(jù)的操作方式可以復(fù)用洗數(shù)據(jù)的流程即可。

          下線舊的讀寫邏輯

          將線上就的讀寫邏輯代碼全部下線,最終完成整個(gè)數(shù)據(jù)清洗的全流程

          總結(jié)

          以上,是一次完整的真實(shí)生產(chǎn)環(huán)境中問題排查及數(shù)據(jù)清洗全過程,通過本次問題的排查,也進(jìn)一步加深了對(duì) Redis 的底層實(shí)現(xiàn)的認(rèn)識(shí)和理解;

          同時(shí),透過本次事故需要我們反思的是,面對(duì)親手寫下的每一行代碼,都需要懷著一顆敬畏心理,永遠(yuǎn)不要輕視,有可能一個(gè)不小心就會(huì)釀成一個(gè)災(zāi)難性的事故。文章到此就先告一段落了,但是排查問題的腳步還在繼續(xù),歡迎關(guān)注我學(xué)習(xí)更多有干貨的知識(shí)。



          瀏覽 64
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  777影院无码中文字幕 | 国产淫伦久久久久久久 | ai欧美高清无码一区二区三区 | 亚洲日韩一区二区三区四区丨高清 | 日韩精品一区二区三区在线观看 |