一次簡單的redis請求會有哪些CPU開銷?

我問大家一個問題,下圖中一個最簡單的例子,會導致哪些CPU開銷產生?你是否能夠說清楚?

 ... 
$redis->get('test');
...

這個例子一下子就把大家在我的文章裡學到的東西和你的實際工作結合起來了。怎麼樣,是不是足夠簡單?就是一句php代碼從redis實例中獲取一個key的value值而已,相信類似的代碼你天天都在寫。對這句redis get實際開銷的理解水平,就代表了你的內功的深度。

在前面的文章中介紹了一些和CPU相關的硬件、內核知識,把開銷大戶進程上下文切換、系統調用、軟中斷的具體開銷都挨個分析了一遍。沒有看過的同學請關注並翻看我以前的文章。不過,這時候我覺得有很多開發同學都有一個疑惑,仍然是覺得:“我是應用層的開發,這麼底層的開銷和我有什麼關係?” 或者是說:“線上服務器的運維不都應該是運維的工作嗎?和開發又沒關係”

我想說的是,如果你只是一個初級或者中級開發工程師,這些確實沒有必要了解。但是如果你想成為一名高級、或者是資深開發工程師,那麼你應該具備大致估算你手下寫出的每一行代碼開銷的能力,要對自己代碼在線上的運行開銷負責!

接下來,我們就來帶大家從更深層次的方向認識到這句簡單代碼的開銷。為了便於測試,我們對代碼進行一些簡單的改造,最終的實際測試文件如下。測試代碼見test07

 $redis = new Redis(); 
$redis->connect('10.153.55.119', 6339);
sleep(60);
echo "Test begin\\n";
for($i=0; $i<10000; $i++){
$redis->get('test');
}
echo "Test end!\\n ";
sleep(60);

例子非常的簡單,就是一句後端同學代碼裡經常出現的從Redis裡獲取了一條數據而已。那麼讓我們看看它到底會產生哪些開銷?

1.系統調用

# strace -c php main.php 
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
97.24 0.039698 1 30003 poll
2.20 0.000899 0 10003 sendto
0.30 0.000122 0 10000 recvfrom
0.13 0.000053 0 10069 gettimeofday
0.03 0.000013 2 6 socket
0.03 0.000012 0 408 munmap
0.02 0.000008 0 657 read

我們代碼所調用的get函數,其實是php的一個redis擴展提供的。該擴展又會去調用Linux系統的網絡庫函數,庫函數再去調用內核提供的系統調用。這個調用層次模型如下:

一次簡單的redis請求會有哪些CPU開銷?

從實際測試結果可見,每次get操作都需要執行多次系統調用才可完成。

2、進程上下文切換

每次調用get後,如果數據沒有返回。進程都是阻塞掉的,因此還會導致進程進入主動上下文切換。 我們用實驗的方式來查看一下:

# php main.php 

然後再另起一個控制檯,分別趕在實驗開始前和實驗開始後執行如下兩行命令:

# grep ctxt /proc/14862/status 
voluntary_ctxt_switches: 4
nonvoluntary_ctxt_switches: 43
# grep ctxt /proc/14862/status
voluntary_ctxt_switches: 10005
nonvoluntary_ctxt_switches: 49

每次get都會導致進程進入自願上下文切換,在網絡IO密集型的應用裡自願上下文切換要比時間片到了被動切換要多的多!

3、軟中斷

每次在redis服務器返回數據的時候,網卡都會通過軟中斷的方式來讓內核處理數據包。因此

# cat /proc/softirqs 
CPU0 CPU1 CPU2 CPU3
HI: 0 0 0 0
TIMER: 196173081 145428444 154228333 163317242
NET_TX: 0 0 0 0
NET_RX: 178159928 116073 10108 160712
# cat /proc/softirqs
CPU0 CPU1 CPU2 CPU3
HI: 0 0 0 0
TIMER: 196173688 145428634 154228610 163317624
NET_TX: 0 0 0 0
NET_RX: 178170212 116073 10108 160712

該虛機的軟中斷親和性在CPU0上,178170212-178159928 = 10284(多出來的284是機器上其它的小服務)。 每次get請求收到數據返回的時候,內核必須要支出一次軟中斷的開銷!

總結

看似一次非常簡單的redis get操作就會把所有系統態的高開銷操作都涉及到了。一次進程上下文切換、一次軟中斷、若干次系統調用。在經過了前面多篇文章的歷練,相信大家對它們的開銷有了一個量化的拿捏。其實除了上面這些容易評估到的開銷外,還有如L1、L2 cache miss,以及TLB cache miss這些在進程被切換掉都會造成cache命中率下降,也會導致額外開銷。

所以,你以為的簡單,其實不一定簡單!


分享到:


相關文章: