消息隊列中吞吐量和處理速度這兩個指標的困惑?
用kafka和redis的list來做消息隊列,看到有人性能測試發現kafka的吞吐量比redis的高,那是不是意味著,kafka在消息入隊和出隊上比redis快?即,處理一條消息kafka所要花費的時間比redis的短?
"吞吐量"是單位時間內處理消息的數量,"處理速度"是處理一條消息所花費的時間
吞吐量和處理速度這兩個指標,能劃等號嗎?
這是個非常好的問題。 @huxihx 的答案是正確的。
所謂的「性能」一般指的是吞吐和延遲這兩件事。他們相互關聯,但是互相的關係並不是簡單的一一對應。
我改用一個通俗的例子來講解這個問題。
假設你去一個樂高店去買散裝樂高。首先你要排隊。等你排到隊首,跟店員說,你要小方塊紅色的10個,淺綠色花花的圓形15個,厚長條白色的3個……。店員就開始按照你說的給你去後邊倉庫拿。假設很少有兩個顧客會有完全一樣的要求,所以每次店員每次都拿的不一樣。拿一趟的時間要數分鐘。(假設店員比較傻,不會把倉庫的東西拿一部分到櫃檯)
當你提完要求後,店員可以有兩個做法:
第一種做法是:馬上去給你拿。這時對於你來講是最快的。店員去給你拿東西的時間是無法消除的,所以他馬上給你拿,對於你來說等待時間是最小的。但是這僅僅是當你已經排在隊首的情況。假如你在隊伍中間,你前面每個人向店員買東西,店員都要去花幾分鐘拿一趟,實際上會讓你等的更久。如果隊伍很短,或者壓根沒有人排隊,那麼這樣做還是比較合適的。但如果人比較多——進來排隊的人比店員拿東西的速度更快,這樣很快隊伍就會排出大門,而店員,要不就累死,要不就得被顧客罵死。
第二種做法是:問你和你後邊的幾個人要什麼,然後一趟拿回來。這時對於你來說就會需要多等一會。畢竟店員要挨個問,需要比問你一個人要多花一點點時間。但是店員給你拿一個,和給幾個人同時拿多個樂高積木,跑腿的時間是幾乎一樣的。所以整體看來,付出了你需要多等一點點時間的代價,換來的是整個「吞吐」的提高。長時間看下來,店員可以接待完畢的顧客數量比上一種要大得多。並且,這裡說的等待是說從你到了隊首到拿到東西的時間。如果從你開始排隊開始算等待時間,說不定等的時間還短點。
但是,店員一次行拿的東西總量畢竟還是有限的。如果比如他一次最多可以拿10人份的積木,而排隊的人每次都能進來15個人排隊。那麼即便是店員次次滿載工作,也沒法滿足顧客需要。對於顧客來講,越排在後邊的人等的時間越長。而單位時間內,店員能夠接的顧客數量不會再增加,而是會保持穩定。(但我相信,此時,店員的心中一定是千萬個草尼瑪飛過的)
但這還沒完。比如店員一次問10個人要的積木品種,然後去拿。但是10個人里有5個覺得已經等太久了,不想等了,就走人(超時了)。這時店員還是得把積木拿回來。但是顧客已經走了。於是店員的勞動就是無效的勞動。系統整體的利用率反而變得更低了。
相信通過這個例子你可以大概明白一個排隊系統里吞吐和延遲的關係,大概就是:
- 當吞吐量小時,延遲比較低,但是延遲低到一定成都受限於網路延遲、磁碟IO延遲的物理限制,無法進一步降低;
- 通過batch/並發等方式,可以有效提高系統的吞吐,此時延遲會著上漲,但不會像吞吐量那樣長的那麼快。
- 當壓力超過了系統的某個臨界值,吞吐不升反降,並且延遲會急劇上升
然而,現實會更加的複雜。上面假設店員每次拿東西時的時間差不多,這已經是非常理想的情況了。如果根據要求,店員要跑3~5個不同的倉庫,花的時間時多時少,那麼系統的行為就會更加難以捉摸。極端情況下,店員憋不住了去大號(對,就是說你Java GC),這樣一來一段時間內顧客的要求就完全得不到處理。
更複雜的情況是多層隊列,即不光顧客要排隊,店員去倉庫拿東西也要排隊,而倉庫的管理員去總庫拿東西還是要排隊…… 這時候拿捏系統的性能表現就很難用簡單的公式/模擬來搞定了。
所以工程上的做法都是要壓測——就是要找到系統的臨界值,將其作為設計的上限。生產環境中,絕對不要讓系統接近臨界值。
最後推薦一篇關於系統性能的文章:Thinking Clearly about Performance
其中的一副圖很有說明性。
」處理一條消息所花費的時間「——稱之為延時(latency)而非處理速度可能更加貼切一些:)
誠然,吞吐量和延時這兩個指標是相互制約的,但並不完全劃等號。有這樣的疑問可能是因為這樣的想法:
假設Kafka每發送一條消息需要花費2ms(即延時是2ms)。顯然吞吐量就應該是500條/秒,因為1秒可以發送1 / 0.002 = 500條消息。因此,吞吐量和延時的關係可以用公式來表示:TPS = 1000 / Latency(ms)
其實,兩者的關係遠非上面公式表示的這麼簡單。我們以Kafka producer為例,假設它以2ms的延時來發送消息。如果每次只是發送一條消息,那麼TPS自然就是500條/秒。但如果producer不是每次發送一條消息,而是在發送前等待一段時間然後統一發送一批消息,比如producer每次發送前先等待8ms,而8ms之後producer共緩存了1000條消息,此時總延時就累加到10ms(2 + 8),而TPS等於1000 / 0.01 = 100,000條/秒。由此可見,雖然延時增加了4倍,但TPS卻增加了將近200倍。這個場景解釋了目前為什麼批次化(batching)或微批次化(micro-batching)流行的原因了。實際環境中用戶幾乎總是願意用較小的延時增加代價去換取TPS的顯著提升。畢竟從2ms到10ms的延時增加通常是可以忍受的。事實上Kafka producer就是採取了這樣的設計思想。
當然,你可能會問:發送一條消息需要2ms,那麼等待8ms就能累積1000條消息?答案是可以的!producer累積消息一般僅僅是將消息發送到內存中的緩衝區,而發送消息卻需要涉及網路I/O傳輸。內存操作和I/O操作的時間量級是不同的,前者通常是幾百納秒級別,而後者從毫秒到秒級別不等,故producer等待8ms積攢出的消息數可能遠遠多於同等時間內producer能夠發送的消息數。
推薦閱讀:
※Redis內部數據結構詳解(4)——ziplist
※請教各位大拿:Tachyon和Redis有什麼區別?
※圖解Redis之持久化篇
※Redis有序集合
※R語言操作redis資料庫詳解