中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Redis做數據持久化的解決方案及底層原理是什么

發布時間:2021-07-14 13:43:10 來源:億速云 閱讀:115 作者:chen 欄目:開發技術

本篇內容介紹了“Redis做數據持久化的解決方案及底層原理是什么”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

目錄
  • 數據持久化

  • RDB

  • 生成方法

    • save

    • bgsave

    • 優點

    • 缺點

    • AOF

    • AOF記錄過程

  • ServerCron

    • 作用

    • server.hz

  • 寫入策略

    • End

      之前的文章介紹了Redis的簡單數據結構的相關使用和底層原理,這篇文章我們就來聊一下Redis應該如何保證高可用。

      數據持久化

      我們知道雖然單機的Redis雖然性能十分的出色, 單機能夠扛住10w的QPS,這是得益于其基于內存的快速讀寫操作,那如果某個時間Redis突然掛了怎么辦?我們需要一種持久化的機制,來保存內存中的數據,否則數據就會直接丟失。

      Redis有兩種方式來實現數據的持久化,分別是RDB(Redis Database)和AOF(Append Only File),你可以先簡單的把RDB理解為某個時刻的Redis內存中的數據快照,而AOF則是所有記錄了所有修改內存數據的指令的集合(也就是Redis指令的集合),而這兩種方式都會生成相應的文件落地到磁盤上,實現數據的持久化,方便下次恢復使用。

      接下來就分別來聊聊這兩種持久化方案。

      RDB

      在redis中生成RDB快照的方式有兩種,一種是使用save,另一種是bgsave,但是底層實現上,其調用的是同一個函數,叫rdbsave,只是其調用的方式不同而已。

      生成方法

      save

      save命令直接調用rdbsave方法,此時會阻塞Redis主進程,直至快照文件生成。

      void saveCommand(client *c) {
          if (server.rdb_child_pid != -1) {
              addReplyError(c,"Background save already in progress");
              return;
          }
          rdbSaveInfo rsi, *rsiptr;
          rsiptr = rdbPopulateSaveInfo(&rsi);
          if (rdbSave(server.rdb_filename,rsiptr) == C_OK) {
              addReply(c,shared.ok);
          } else {
              addReply(c,shared.err);
          }
      }

      bgsave

      bgsave命令會fork出一個子進程,由fork出來的子進程調用rdbsave。父進程會繼續響應來自客戶端的讀寫請求。子進程完成RDB文件生成之后會給父進程發送信號,通知父進程保存完成。

      /* BGSAVE [SCHEDULE] */
      void bgsaveCommand(client *c) {
          int schedule = 0;
      
          /* The SCHEDULE option changes the behavior of BGSAVE when an AOF rewrite
           * is in progress. Instead of returning an error a BGSAVE gets scheduled. */
          if (c->argc > 1) {
              if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"schedule")) {
                  schedule = 1;
              } else {
                  addReply(c,shared.syntaxerr);
                  return;
              }
          }
      
          rdbSaveInfo rsi, *rsiptr;
          rsiptr = rdbPopulateSaveInfo(&rsi);
      
          if (server.rdb_child_pid != -1) {
              addReplyError(c,"Background save already in progress");
          } else if (hasActiveChildProcess()) {
              if (schedule) {
                  server.rdb_bgsave_scheduled = 1;
                  addReplyStatus(c,"Background saving scheduled");
              } else {
                  addReplyError(c,
                  "Another child process is active (AOF?): can't BGSAVE right now. "
                  "Use BGSAVE SCHEDULE in order to schedule a BGSAVE whenever "
                  "possible.");
              }
          } else if (rdbSaveBackground(server.rdb_filename,rsiptr) == C_OK) {
              addReplyStatus(c,"Background saving started");
          } else {
              addReply(c,shared.err);
          }
      }

      這也就是為什么Redis是單線程的,但卻能夠在生成RDB文件的同時對外提供服務。fork是unix系統上創建進程的主要方法,會把父進程的所有數據拷貝到子進程中,父子進程共享內存空間。

      fork之后,操作系統內核會把父進程中的所有內存設置為只讀,只有當發生寫數據時,會發生頁異常中斷,內核會把對應的內存頁拷貝一份,父子進程各持有一份,所以在生成RDB過程中,由于使用了COW,內存臟頁會逐漸和子進程分開。

      那么有沒有可能在調用bgsave的過程中,我再調用save命令呢,這個時候豈不是會生成兩份RDB文件?

      實際上在調用save命令時,Redis會判斷bgsave是否正在執行,如果正在執行服務器就不能再調用底層的rdbsave函數了,這樣做可以避免兩個命令之間出現資源競爭的情況。

      例如,在save命令中,有如下的判斷:

      if (server.rdb_child_pid != -1) {
        addReplyError(c,"Background save already in progress");
        return;
      }

      而在bgsave中又有如下的判斷:

      if (server.rdb_child_pid != -1) {
        addReplyError(c,"Background save already in progress");
      } else if (hasActiveChildProcess()) {
        ...
      }

      可以看到都是對同一個變量的判斷,如下:

      pid_t rdb_child_pid; /* PID of RDB saving child */

      換句話說,在調用save、bgsave命令的時候,會提前去判斷bgsave是否仍然在運行當中,如果在運行當中,則不會繼續執行bgsave命令。而save命令本身就是阻塞的,如果此時有其他的命令過來了都會被阻塞, 直到save執行完畢,才會去處理。

      那我把RDB文件生成了之后怎么使用呢?

      Redis在啟動服務器的時候會調用rdbLoad函數,會把生成的RDB文件給加載到內存中來,在載入的期間,每載入1000個鍵就會處理一次已經到達的請求,但是只會處理publish、subscribe、psubscribe、unsubscribe、punsubscribe這個五個命令。其余的請求一律返回錯誤,直到載入完成。

      你吹的這么好,RDB的優缺點分別是啥?

      優點

      RDB策略可以靈活配置周期,取決于你想要什么樣的備份策略。例如:

      • 每小時生成一次最近24小時的數據

      • 每天生成最近一周的數據

      • 每天生成最近一個月的數據

      基于這個策略,可以快速的恢復之前某個時間段的數據。

      其次,RDB非常的適合做冷備份,你可以把RDB文件存儲后轉移到其他的存儲介質上。甚至可以做到跨云存儲,例如放到OSS上的同時,又放到S3上,跨云存儲讓數據備份更加的健壯。

      而且,基于RDB模式的恢復速度比AOF更快,因為AOF是一條一條的Redis指令,RDB則是數據最終的模樣。數據量大的話所有AOF指令全部重放要比RDB更慢。

      缺點

      RDB作為一個數據持久化的方案是可行的,但是如果要通過RDB做到Redis的高可用,RDB就不那么合適了。

      因為如果Redis此時還沒有來得及將內存中的數據生成RDB文件,就先掛了,那么距離上次成功生成RDB文件時新增的這部分數據就會全部丟失,而且無法找回。

      而且,如果內存的數據量很大的話,RDB即使是通過fork子進程來做的,但是也需要占用到機器的CPU資源,也可能會發生很多的也異常中斷,也可能造成整個Redis停止響應幾百毫秒。

      AOF

      上面提到過RDB不能滿足Redis的高可用。因為在某些情況下,會永久性的丟失一段時間內的數據,所以我們來聊聊另一種解決方案AOF。首先我們得有個概念,那就是RDB是對當前Redis Server中的數據快照,而AOF是對變更指令的記錄(所有的獲取操作不會記錄,因為對當前的Redis數據沒有改變)。

      但是也正因為如此,AOF文件要比RDB文件更大。下面聊一下一個Redis命令請求從客戶端到AOF文件的過程。

      AOF記錄過程

      首先Redis的客戶端和服務器之間需要通信,客戶端發送的不是我們寫入的字符串,而是專門的協議文本。如果你可以熟悉Thrift或者Protobuf的話應該就能理解這個協議。

      例如執行命令 SET KEY VALUE,傳到服務器就變成了"*3\r\n$3\r\nSET\r\n$3\r\nKEY\r\n$5\r\nVALUE\r\n"

      然后Redis服務器就會根據協議文本的內容,選擇適當的handler進行處理。當客戶端將指令發送到Redis服務器之后,只要命令成功執行,就會將這個命令傳播到AOF程序中。

      注意,傳播到AOF程序中之后不會馬上寫入磁盤,因為頻繁的IO操作會帶來巨大的開銷,會大大降低Redis的性能,協議文本會被寫到Redis服務器中的aof_buf中去,也叫AOF的寫入緩沖區。

      你這全部都寫到緩沖區去了,啥時候落地?

      每當serverCron(先有一個定時任務的概念,下面馬上就會講serverCron是啥)被執行的時候,flushAppendOnlyFile 這個函數就被調用。

      這個命令會調用 write將寫入緩沖區的數據寫入到AOF文件中,但是這個時候還是沒有真正的落到磁盤上。這是OS為了提高寫入文件的效率,會將數據暫時寫入到OS的內存的緩沖區內,等到緩沖區被填滿了或超過了指定的時間,才會調用fsync或者sdatasync真正的將緩沖區的內容寫入到磁盤中。

      但是如果在這期間機器宕了,那么數據仍然會丟失。所以如果想要真正的將AOF文件保存在磁盤上,必須要調用上面提到的兩個函數才行。

      ServerCron

      作用

      現在我們就來具體聊一下serverCron函數,它主要是用于處理Redis中的常規任務。

      什么叫常規任務?

      就比如上面提到的AOF寫入緩沖區,每次serverCron執行的時候就會把緩沖區內的AOF寫入文件(當然,OS會寫入自己的buffer中)。其余的就像AOF和RDB的持久化操作,主從同步和集群的相關操作,清理失效的客戶端、過期鍵等等。

      那這個cron間隔多久執行一次?

      很多博客是直接給出的結論,100ms執行一次,口說無憑,我們直接擼源碼。下面是serverCron的函數定義。

      /* This is our timer interrupt, called server.hz times per second.
       * .............
       */
      int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
        ...
        server.hz = server.config_hz;
      }

      為了避免影響大家的思路,我省略了暫時對我們沒用的代碼和注釋。可以看到注釋中有called server.hz times per second。意思就是serverCron這個函數將會在每一秒中調用server.hz次,那這個server.hz又是啥?

      server.hz

      相信大家都知道HZ(赫茲)這個單位,它是頻率的國際單位制單位,表示每一條周期性事件發生的次數。所以,我們知道這個配置項是用于控制周期性事件發生的頻率的。

      其賦值的地方在上面的函數中已經給出,可以看到其初始值是來源于redis.conf的配置文件。那讓我們看一下具體的配置。

      # Redis calls an internal function to perform many background tasks, like
      # closing connections of clients in timeout, purging expired keys that are
      # never requested, and so forth.
      #
      # Not all tasks are performed with the same frequency, but Redis checks for
      # tasks to perform according to the specified "hz" value.
      #
      # By default "hz" is set to 10. Raising the value will use more CPU when
      # Redis is idle, but at the same time will make Redis more responsive when
      # there are many keys expiring at the same time, and timeouts may be
      # handled with more precision.
      #
      # The range is between 1 and 500, however a value over 100 is usually not
      # a good idea. Most users should use the default of 10 and raise this up to
      # 100 only in environments where very low latency is required.
      hz 10

      簡單的提取一下有用的信息,Redis會在內部調用函數來執行很多后臺的任務,而調用這些函數的頻率就由這個hz來決定的,其默認值為10。那也就是說,上面提到的 serverCron函數會在一秒鐘執行10次,這樣平均下來就是每100ms(1000ms/10)調用一次。

      寫入策略

      上面說到,如果Redis的AOF已經位于OS的緩沖中,如果此時宕機,那么AOF的數據同樣會丟失。

      你這不行啊,那你這個持久化有什么意義?怎么樣數據才能不丟失?

      這得聊一下AOF日志的寫入策略,它有三種策略,分別如下:

      • always 每個命令都會寫入文件并且同步到磁盤

      • everysec 每秒鐘同步一次數據到磁盤

      • no 不強制寫,等待OS自己去決定什么時候寫

      很明顯always這種策略在真正的生產環境上是不可取的,每個命令都去寫文件,會造成極大的IO開銷,會占用Redis服務器的很多資源,降低Redis的服務效率。

      而如果使用everysec策略的話,即使發生了斷電,機器宕機了,我最多也只會丟失一秒鐘的數據。

      no則完全交與操作系統去調度,可能會丟失較多的數據。

      666,那這AOF文件咋用的,怎么恢復?

      上面提到過,AOF文件是記錄了來自客戶端的所有寫命令,所以服務器只需要讀入并重放一遍即可將Redis的狀態恢復。

      但是,Redis的命令只能在客戶端中的上下文才能夠執行,所以Redis搞了一個沒有網絡連接的偽客戶端來執行命令,直到命令執行完畢。

      老鐵,你這不行啊,萬一AOF日志數據量很大,你這豈不是要恢復很長時間,那服務豈不是不可用了?

      的確,隨著服務器的運行,AOF的數據量會越來越大,重放所需要的時間也會越來越多。所以Redis有一個重寫(AOF Rewrite)機制,來實現對AOF文件的瘦身。

      雖然名字叫對AOF文件的瘦身,但是實際上要做的操作跟之前已經生成的AOF文件沒有一毛錢的關系。

      所謂瘦身是通過讀取Redis服務器當前的數據狀態來實現的,當然,這里的當前是在服務器正常運行的時候。其實你也可以理解為快照,只不過不是實打實的二進制文件了,而是直接設置快照值的命令。

      用人話舉個例子,假設你Redis中有個鍵叫test,它的值的變化歷史是1 -> 3 -> 5 -> 7 -> 9這樣,那么如果是正常的AOF文件就會記錄5條Redis指令。而AOF Rewrite此時介入,就只會記錄一條test=9這樣的數據。

      而之前的AOF文件還是照常的寫入,當新的AOF文件生成后替換即可。

      你tm在逗我?你在rewrite的同時,服務器仍然在處理正常的請求,此時如果對服務器的狀態做了更改,你這個瘦身之后的AOF文件數據不就不一致了?

      這種情況的確會出現,但是Redis通過一個AOF重寫緩沖區來解決了這個問題。

      當rewrite開始后,Redis會fork一個子進程,讓子進程來實現AOF的瘦身操作,父進程則可以正常處理請求。AOF重寫緩沖區會在rewrite開始創建了子進程之后開始使用,此時Redis服務器會把寫的指令同時發送到兩個地方:

      • aof_buf,也就是上面提到的AOF文件的寫入緩沖區

      • AOF重寫緩沖區

      你可能會問,為啥要記錄到兩個地方?上面提到過,Redis執行瘦身操作時,常規的AOF文件仍然是正常生成的,所以新的Redis指令一定會發送到寫入緩沖區。

      而發送到AOF重寫緩沖區是為了重放在瘦身操作進行當中對Redis狀態進行的更改,這樣瘦身之后的AOF文件狀態才能保證與Redis的狀態一致。總的來說,就是為了保證瘦身的AOF文件中的數據狀態與Redis當時的內存狀態保持數據上的一致性。

      “Redis做數據持久化的解決方案及底層原理是什么”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!

      向AI問一下細節

      免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

      AI

      兴安盟| 武汉市| 雅江县| 东海县| 天气| 五家渠市| 墨脱县| 理塘县| 卢龙县| 二手房| 邓州市| 晋中市| 宜州市| 高安市| 凤庆县| 中江县| 民乐县| 苗栗县| 右玉县| 齐河县| 崇信县| 平罗县| 贵南县| 乌鲁木齐县| 缙云县| 平阳县| 顺平县| 闵行区| 大城县| 寻甸| 卓尼县| 安顺市| 仁寿县| 福州市| 永吉县| 潞西市| 大悟县| 长治县| 涪陵区| 广灵县| 柘荣县|