最近在解決探針獲取Ruby應(yīng)用服務(wù)器的內(nèi)存使用的情況,將解決的思路總結(jié)一下,希望對此感興趣的伙伴一起探討。
先對比應(yīng)用服務(wù)器:Puma和Passenger,下面對比這2個(gè)服務(wù)器內(nèi)存統(tǒng)計(jì),
Puma (2.13.4)
單進(jìn)程模式:直接獲取進(jìn)程id: Process.pid
memory = `ps -o rss= #{Process.pid}`.to_f / 1024 #單位:MB
cluster模式:以啟動2個(gè)worker進(jìn)程為例:
從上面截圖可以看到,Puma啟動后會出現(xiàn)3個(gè)進(jìn)程:1個(gè)master進(jìn)程和2個(gè)worker進(jìn)程。
內(nèi)存的使用情況(見RSS列):
(109908 + 109868 + 7256 ).to_f / 1024 = 221.7109375 #單位:MB
而對于探針來說,一個(gè)探針實(shí)例是伴隨進(jìn)程一起啟動的,也就說一個(gè)探針只能識別自己所在的進(jìn)程id,那如何獲取應(yīng)用服務(wù)器使用的內(nèi)存?我們用其中1個(gè)woker進(jìn)程所在的進(jìn)程組[PGID]看一下:(為啥不是父進(jìn)程?, 見下文Passenger)
這3個(gè)進(jìn)程都在相同的進(jìn)程組里,而且進(jìn)程組號為master的進(jìn)程id,那我們就可以用這個(gè)信息獲取應(yīng)用服務(wù)器的所使用的內(nèi)存:
得到1個(gè)worker進(jìn)程id:
Process.pid獲取所在進(jìn)程組:
Process.getpgrp獲取到進(jìn)程組內(nèi)所有的進(jìn)程:
`ps -o pid= -g #{Process.getpgrp}`.split("\n")
4.累加進(jìn)程組內(nèi)進(jìn)程內(nèi)存和即為應(yīng)用服務(wù)器使用內(nèi)存:
pids.inject(0.0){|m, pid| m + memory(pid)}
Passenger (5.0.20)
啟動Passenger后的Process信息:
對Passenger架構(gòu)感興趣的請移步到這兒.
查看一下worker所在進(jìn)程組和父進(jìn)程:
通過PPID可以看出
Passenger core —> Passenger AppPreloader —> Passenger RubyApp
三者為爺-父-子關(guān)系,當(dāng)服務(wù)器請求量增大時(shí)AppPreloader會產(chǎn)生新的進(jìn)程來響應(yīng)請求,從而新的RubyApp進(jìn)程的PPID即為AppPreloader的PID,這樣看來就可以將同一個(gè)PPID的進(jìn)程加起來得到應(yīng)用服務(wù)器的內(nèi)存?
由于Passenger會根據(jù)服務(wù)器的負(fù)載量動態(tài)調(diào)整進(jìn)程數(shù),當(dāng)服務(wù)器請求量較小時(shí),Passenger會kill多余的進(jìn)程,會出現(xiàn)下面的情況:
AppPreloader也被Passenger殺掉了。原RubyApp進(jìn)程的PPID變成了1。這時(shí)如果服務(wù)器的請求量增大,應(yīng)用服務(wù)器進(jìn)程會成為這樣:

Passenger core 產(chǎn)生新的AppPreloader進(jìn)程,并且AppPreloader產(chǎn)生新的RubyApp進(jìn)程,這時(shí)如果只用PPID統(tǒng)計(jì)應(yīng)用服務(wù)器內(nèi)存就會不準(zhǔn)確,所以要統(tǒng)計(jì)Passenger的使用的內(nèi)存還得通過累加在同一個(gè)進(jìn)程組(PGID)的所有進(jìn)程使用的內(nèi)存和得到。
由于Unicorn和Rainbows都與Puma的cluster模式[master+worker模式]類似,內(nèi)存統(tǒng)計(jì)的方式可以參考上文的Puma。
總結(jié):
由于Thin啟動多個(gè)server后沒有類似的特點(diǎn),上面方法不適用于Thin,有好方法的伙伴們可以告知:smile:
在解決探針統(tǒng)計(jì)應(yīng)用服務(wù)器的內(nèi)存問題上,摸索出了上面的一條路子,如果小伙伴們有其他更好的方式,可以一起探討一下。