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 的耗時)。
要徹底弄清楚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 is27364
I am child,my
pid is27365
/<code>
是不是非常驚訝,為什麼執行一次程序,怎麼打印了2個結果,這是怎麼做到的呢。
fork實現過程
1.分配新的內存塊和內核數據結構給子進程
2.將父進程代碼數據段,頁目錄和頁表,虛擬內存池(vm_area_struct )拷貝至子進程
3.添加子進程到系統進程列表當中
4.fork返回,開始調度器調度
瞭解fork實現過程之後,再來看代碼,當代碼執行到語句int pid = fork(),由於在複製時複製了父進程的堆棧段,所以兩個進程都停留在fork函數中,等待返回。因此fork函數會返回兩次,一次是在父進程中返回,另一次是在子進程中返回,並且這兩次的返回值是不一樣的。
在父進程中,fork返回新創建子進程的進程ID;
在子進程中,fork返回0;
如果出現錯誤,fork返回一個負值;
fork耗時分析
fork實現過程的4個步驟中,基本所有的耗時在步驟2中,將父進程代碼數據段,頁目錄和頁表,虛擬內存池(vm_area_struct )拷貝至子進程,而這裡拷貝頁目錄和頁表佔據了絕大部分時間,所以要一定要弄清楚redis的實例的頁目錄和頁表佔用了多大內存空間,就變的至關重要了。
頁目錄和頁表在內存中的存放形式,其實是一棵樹,其結構如下圖所示。
那這棵樹會佔用多大內存空間呢,我們知道標準的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。