深度解析Redis的bgsave導致QPS性能波動

redis簡介

Redis性能極高,讀的速度是110000次/s,寫的速度是81000次/s 。由於其超高的讀寫速度,被廣泛用於緩存系統,解決超高併發的應用讀寫需求,新浪就有國內最大的redis緩存。

bgsave功能

Redis支持數據的持久化,可以將內存中的數據保持在磁盤中,重啟的時候可以再次加載進行使用。其中RDB的持久化,就是通過bgsave完成的,為什麼佔用內存大的redis實例,在調用bgsave時,會導致redis的qps性能波動呢?

bgsave運行過程

當redis調用bgsave進行RDB持久化時,會先fork子進程,在子進程未被創建成功之前,redis的主線程會被堵塞,這個堵塞的時間長短,就直接決定bgsave導致QPS性能波動有多大,當然這個fork時間,可以通過使用Redis的INFO 命令查看 latest_fork_usec 指標值(表示最近一次 fork 的耗時)。

深度解析Redis的bgsave導致QPS性能波動

要徹底弄清楚bgsave的影響範圍,所以必須要清楚的知道fork操作的實現原理。

fork

在講解fork功能之前,先看一段代碼示例

<code>[root@mysql ~] 
 
 

int

main() {

int

pid =

fork

();

if

(pid == -

1

)

return

-

1

;

if

(pid>

0

) {

printf

(

"I am father, my pid is %d\n"

, getpid());

return

0

; }

else

{

printf

(

"I am child, my pid is %d\n"

, getpid());

return

0

; } } [root@mysql ~] [root@mysql ~] I am father,

my

pid is

27364

I am child,

my

pid is

27365

/<code>

是不是非常驚訝,為什麼執行一次程序,怎麼打印了2個結果,這是怎麼做到的呢。

fork實現過程
1.分配新的內存塊和內核數據結構給子進程
2.將父進程代碼數據段,頁目錄和頁表,虛擬內存池(vm_area_struct )拷貝至子進程
3.添加子進程到系統進程列表當中
4.fork返回,開始調度器調度

瞭解fork實現過程之後,再來看代碼,當代碼執行到語句int pid = fork(),由於在複製時複製了父進程的堆棧段,所以兩個進程都停留在fork函數中,等待返回。因此fork函數會返回兩次,一次是在父進程中返回,另一次是在子進程中返回,並且這兩次的返回值是不一樣的。

深度解析Redis的bgsave導致QPS性能波動

在父進程中,fork返回新創建子進程的進程ID;
在子進程中,fork返回0;
如果出現錯誤,fork返回一個負值;

fork耗時分析

fork實現過程的4個步驟中,基本所有的耗時在步驟2中,將父進程代碼數據段,頁目錄和頁表,虛擬內存池(vm_area_struct )拷貝至子進程,而這裡拷貝頁目錄和頁表佔據了絕大部分時間,所以要一定要弄清楚redis的實例的頁目錄和頁表佔用了多大內存空間,就變的至關重要了。

頁目錄和頁表在內存中的存放形式,其實是一棵樹,其結構如下圖所示。

深度解析Redis的bgsave導致QPS性能波動

那這棵樹會佔用多大內存空間呢,我們知道標準的linux內存頁大小是4kb,一個頁表條目佔用8個字節,一個24GB的Redis實例,頁表佔用空間大約為:24 GB / 4 kB * 8 = 48 MB,實際上會比這個值要大一點。

當執行bgsave命令,調用fork時,就會將這48M的頁表數據拷貝到新的子進程中,這個拷貝性能,官方給了一組數據,在實體機和虛擬機的耗時不一樣,實體機的耗時要比虛擬機中小。

<code>

Linux

running

on

physical

machine

(Unknown HW)

6

.1GB

RSS

forked

in

80

milliseconds

(

13.1

milliseconds per GB)

Linux

running

on

physical

machine

(Xeon @

2.27

Ghz)

6

.9GB

RSS

forked

into

62

milliseconds

(

9

milliseconds per GB)

Linux

VM

on

6sync

(KVM)

360

MB

RSS

forked

in

8

.2

milliseconds

(

23.3

milliseconds per GB).

Linux

VM

on

EC2

,

old

instance

types

(Xen)

6

.1GB

RSS

forked

in

1460

milliseconds

(

239.3

milliseconds per GB)

Linux

VM

on

EC2

,

new

instance

types

(Xen)

1GB

RSS

forked

in

10

milliseconds

(

10

milliseconds per GB).

Linux

VM

on

Linode

(Xen)

0

.9GBRSS

forked

into

382

milliseconds

(

424

milliseconds per GB)./<code>

從上面的數據庫,可以得出:
10G內存的Redis,在物理機上fork操作耗時在90毫秒至131毫秒。
10G內存的Redis,在虛擬機上fork操作耗時在100毫秒至4240毫秒。

由於生產環境的差異,實際的耗時會有不同,不過差異不會特別大。

在這裡有2個建議


建議1:生產的redis實例不要超過10G。
建議2:生產的redis實例最好部署在物理機上。

在這裡有的朋友可能會問,那我用大內存頁,不就可以減少頁表的數量,提升fork速度嗎。這個想法很好,但是超級聰明的linux開發人員,在優化fork時,使用cow(寫時複製),當父進程或者子進程要修改共享內存區域數據時,linux內核就會將要修改的內存數據頁拷貝一份到子進程,這樣就不影響數據的寫操作。

當在做bgsave時,redis突然有大量寫操作,fork操作就會佔用更多的內存空間,嚴重的話,到導致fork失敗,甚至觸發OOM。


分享到:


相關文章: