亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

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

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

Redis的快照為什么不會阻塞其他請求

發布時間:2021-11-30 09:55:46 來源:億速云 閱讀:128 作者:柒染 欄目:數據庫

今天就跟大家聊聊有關Redis的快照為什么不會阻塞其他請求,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結了以下內容,希望大家根據這篇文章可以有所收獲。

為什么這么設計(Why's THE Design)是一系列關于計算機領域中程序設計決策的文章,我們在這個系列的每一篇文章中都會提出一個具體的問題并從不同的角度討論這種設計的優缺點、對具體實現造成的影響。如果你有想要了解的問題,可以在文章下面留言。

雖然我們經常將 Redis 看做一個純內存的鍵值存儲系統,但是我們也會用到它的持久化功能,RDB 和 AOF 就是 Redis 為我們提供的兩種持久化工具,其中 RDB 就是 Redis 的數據快照,我們在這篇文章想要分析 Redis 為什么在對數據進行快照持久化時會需要使用子進程,而不是將內存中的數據結構直接導出到磁盤上進行存儲。

概述

在具體分析今天的問題之前,我們首先需要了解 Redis 的持久化存儲機制 RDB 究竟是什么,RDB 會每隔一段時間中對 Redis 服務中當下的數據集進行快照,除了 Redis 的配置文件可以對快照的間隔進行設置之外,Redis 客戶端還同時提供兩個命令來生成 RDB 存儲文件,也就是 SAVE 和 BGSAVE,通過命令的名字我們就能猜出這兩個命令的區別。

Redis的快照為什么不會阻塞其他請求

其中 SAVE 命令在執行時會直接阻塞當前的線程,由于 Redis 是 單線程 的,所以 SAVE 命令會直接阻塞來自客戶端的所有其他請求,這在很多時候對于需要提供較強可用性保證的 Redis 服務都是無法接受的。

我們往往需要 BGSAVE 命令在后臺生成 Redis 全部數據對應的 RDB 文件,當我們使用 BGSAVE 命令時,Redis 會立刻 fork 出一個子進程,子進程會執行『將內存中的數據以 RDB 格式保存到磁盤中』這一過程,而 Redis 服務在 BGSAVE 工作期間仍然可以處理來自客戶端的請求。

rdbSaveBackground 就是用來處理在后臺將數據保存到磁盤上的函數:

int rdbSaveBackground(char *filename, rdbSaveInfo *rsi) {      pid_t childpid;      if (hasActiveChildProcess()) return C_ERR;      ...      if ((childpid = redisFork()) == 0) {          int retval;          /* Child */          redisSetProcTitle("redis-rdb-bgsave");          retval = rdbSave(filename,rsi);          if (retval == C_OK) {              sendChildCOWInfo(CHILD_INFO_TYPE_RDB, "RDB");          }          exitFromChild((retval == C_OK) ? 0 : 1);      } else {          /* Parent */          ...      }      ...  }

Redis 服務器會在觸發 BGSAVE 時調用 redisFork 函數來創建子進程并調用 rdbSave 在子進程中對數據進行持久化,我們在這里雖然省略了函數中的一些內容,但是整體的結構還是非常清晰的,感興趣的讀者可以在點擊上面的鏈接了解整個函數的實現。

使用 fork 的目的最終一定是為了不阻塞主進程來提升 Redis 服務的可用性,但是到了這里我們其實能夠發現兩個問題:

  1.  為什么 fork 之后的子進程能夠獲取父進程內存中的數據?

  2.  fork 函數是否會帶來額外的性能開銷,這些開銷我們怎么樣才可以避免?

既然 Redis 選擇使用了 fork 的方式來解決快照持久化的問題,那就說明這兩個問題已經有了答案,首先 fork 之后的子進程是可以獲取父進程內存中的數據的,而 fork 帶來的額外性能開銷相比阻塞主線程也一定是可以接受的,只有同時具備這兩點,Redis 最終才會選擇這樣的方案。

設計

為了分析上一節提出的兩個問題,我們在這里需要了解以下的這些內容,這些內容是 Redis 服務器使用 fork 函數的前提條件,也是最終促使它選擇這種實現方式的關鍵:

  1.  通過 fork 生成的父子進程會共享包括內存空間在內的資源;

  2.  fork 函數并不會帶來明顯的性能開銷,尤其是對內存進行大量的拷貝,它能通過寫時拷貝將拷貝內存這一工作推遲到真正需要的時候;

子進程

在計算機編程領域,尤其是 Unix 和類 Unix 系統中,fork 都是一個進程用于創建自己拷貝的操作,它往往都是被操作系統內核實現的系統調用,也是操作系統在 *nix 系統中創建新進程的主要方法。

Redis的快照為什么不會阻塞其他請求

當程序調用了 fork 方法之后,我們就可以通過 fork 的返回值確定父子進程,以此來執行不同的操作:

  •  fork 函數返回 0 時,意味著當前進程是子進程;

  •  fork 函數返回非 0 時,意味著當前進程是父進程,返回值是子進程的 pid; 

int main() {      if (fork() == 0) {          // child process      } else {          // parent process      }  }

在 fork 的 手冊 中,我們會發現調用 fork 后的父子進程會運行在不同的內存空間中,當 fork 發生時兩者的內存空間有著完全相同的內容,對內存的寫入和修改、文件的映射都是獨立的,兩個進程不會相互影響。

The child process and the parent process run in separate memory spaces.  At the time of fork() both memory spaces have the same content.  Memory writes, file mappings (mmap(2)), and unmappings (munmap(2)) performed by one of the processes do not affect other.

除此之外,子進程幾乎是父進程的完整副本(Exact duplicate),然而這兩個進程在以下的一些方面會有較小的區別:

  •  子進程用于獨立且唯一的進程 ID;

  •  子進程的父進程 ID 與父進程 ID 完全相同;

  •  子進程不會繼承父進程的內存鎖;

  •  子進程會重新設置進程資源利用率和 CPU 計時器;

  •  ...

最關鍵的點在于父子進程的內存在 fork 時是完全相同的,在 fork 之后進行寫入和修改也不會相互影響,這其實就完美的解決了快照這個場景的問題 —— 只需要某個時間點下內存中的數據,而父進程可以繼續對自己的內存進行修改,這既不會被阻塞,也不會影響生成的快照。

寫時拷貝

既然父進程和子進程擁有完全相同的內存空間并且兩者對內存的寫入都不會相互影響,那么是否意味著子進程在 fork 時需要對父進程的內存進行全量的拷貝呢?假設子進程需要對父進程的內存進行拷貝,這對于 Redis 服務來說基本都是災難性的,尤其是在以下的兩個場景中:

  1.  內存中存儲大量的數據,fork 時拷貝內存空間會消耗大量的時間和資源,會導致程序一段時間的不可用;

  2.  Redis 占用了 10G 的內存,而物理機或者虛擬機的資源上限只有 16G,在這時我們就無法對 Redis 中的數據進行持久化,也就是說 Redis 對機器上內存資源的最大利用率不能超過 50%;

如果無法解決上面的兩個問題,使用 fork 來生成內存鏡像的方式也無法真正落地,不是一個工程中真正可以使用的方法。

就算脫離了 Redis 的場景,fork 時全量拷貝內存也是難以接受的,假設我們需要在命令行中執行一個命令,我們需要先通過 fork 創建一個新的進程再通過 exec 來執行程序,fork 拷貝的大量內存空間對于子進程來說可能完全沒有任何作用的,但是卻引入了巨大的額外開銷。

寫時拷貝(Copy-on-Write)的出現就是為了解決這一問題,就像我們在這一節開頭介紹的,寫時拷貝的主要作用就是將拷貝推遲到寫操作真正發生時,這也就避免了大量無意義的拷貝操作。在一些早期的 *nix 系統上,系統調用 fork 確實會立刻對父進程的內存空間進行復制,但是在今天的多數系統中,fork 并不會立刻觸發這一過程:

Redis的快照為什么不會阻塞其他請求

在 fork 函數調用時,父進程和子進程會被 Kernel 分配到不同的虛擬內存空間中,所以在兩個進程看來它們訪問的是不同的內存:

  •  在真正訪問虛擬內存空間時,Kernel 會將虛擬內存映射到物理內存上,所以父子進程共享了物理上的內存空間;

  •  當父進程或者子進程對共享的內存進行修改時,共享的內存才會以頁為單位進行拷貝,父進程會保留原有的物理空間,而子進程會使用拷貝后的新物理空間;

在 Redis 服務中,子進程只會讀取共享內存中的數據,它并不會執行任何寫操作,只有父進程會在寫入時才會觸發這一機制,而對于大多數的 Redis 服務或者數據庫,寫請求往往都是遠小于讀請求的,所以使用 fork 加上寫時拷貝這一機制能夠帶來非常好的性能,也讓 BGSAVE 這一操作的實現變得非常簡單。

總結

Redis 實現后臺快照的方式非常巧妙,通過操作系統提供的 fork 和寫時拷貝的特性輕而易舉的就實現了這個功能,從這里我們就能看出作者對于操作系統知識的掌握還是非常扎實的,大多人在面對類似的場景時,想到的方法可能就是手動實現類似『寫時拷貝』的特性,然而這不僅增加了工作量,還增加了程序出現問題的可能性。

到這里,我們簡單總結一下 Redis 為什么在使用 RDB 進行快照時會通過子進程的方式進行實現:

  1.  通過 fork 創建的子進程能夠獲得和父進程完全相同的內存空間,父進程對內存的修改對于子進程是不可見的,兩者不會相互影響;

  2.  通過 fork 創建子進程時不會立刻觸發大量內存的拷貝,內存在被修改時會以頁為單位進行拷貝,這也就避免了大量拷貝內存而帶來的性能問題;

上述兩個原因中,一個為子進程訪問父進程提供了支撐,另一個為減少額外開銷做了支持,這兩者缺一不可,共同成為了 Redis 使用子進程實現快照持久化的原因。到最后,我們還是來看一些比較開放的相關問題,有興趣的讀者可以仔細思考一下下面的問題:

  •     Nginx 的主進程會在運行時 fork 一組子進程,這些子進程可以分別處理請求,還有哪些服務會使用這一特性?

  •     寫時拷貝其實是一個比較常見的機制,在 Redis 之外還有哪里會用到它?

看完上述內容,你們對Redis的快照為什么不會阻塞其他請求有進一步的了解嗎?如果還想了解更多知識或者相關內容,請關注億速云行業資訊頻道,感謝大家的支持。

向AI問一下細節

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

AI

固镇县| 巴林右旗| 桐乡市| 玉山县| 全椒县| 临夏市| 新宾| 许昌市| 萍乡市| 随州市| 揭东县| 台东县| 泉州市| 望都县| 兴宁市| 大渡口区| 美姑县| 海原县| 平安县| 西吉县| 交口县| 太原市| 民勤县| 大理市| 双桥区| 平江县| 长岛县| 和林格尔县| 宾川县| 磐安县| 阿勒泰市| 武邑县| 浦北县| 芜湖市| 文安县| 象州县| 平谷区| 西平县| 会宁县| 南岸区| 滦平县|