1. 程式人生 > >網際網路三高架構之高併發和高效能的理解

網際網路三高架構之高併發和高效能的理解

網際網路三高架構:高併發、高效能、高可用,簡稱三高(3H)

網際網路應用系統開發肯定經常會看到高併發和高效能這兩個詞,可謂是耳熟能詳,而具體的含義和關係真的如你所想的,真正的理解了嗎?

先來看一個例子:

一個蓄水池,是1m*1m*1m=1立方米大小,有一個出水口,出水口每秒鐘流出0.1立方米,那麼這個蓄水池的併發量是1立方米,出水速度是0.1立方米/秒。

如果增加一個出水口,都是每秒鐘流出0.1立方米,那麼這個蓄水池的併發量沒變,但是出水速度變成了0.2立方米/秒。

同理,增大了出水口,蓄水池的出水速度也變快了。

上面我們很容易知道,併發量是一個容量的概念,效能就是出水速度,而且有下面這些結果。

  • 增大蓄水池的長寬高,可以增加併發能力
  • 出水口如果擴大了出口大小,則可以提高出水的速度,也就是效能提高了。
  • 增加出水口的數量,則是增加了並行處理的能力,同樣可以提高效能。

那麼對照我們計算機中,我們的系統中,是怎麼樣的結果呢?

  • 增加伺服器的記憶體大小,可以增加併發量。因為記憶體增加了,就可以開更多的程序,更多的執行緒,也可以擴大任務佇列的大小。
  •  提高cpu的主頻速度,優化程式,可以提高效能。cpu更快了,程式優化的更好了,處理單個任務的時間也就更短了。
  • 增加多核甚至分散式伺服器數量,也可以提高效能,同時提高併發量。

如果只是效能提高了,併發量是否也能提高呢?

如果我們靜態的理解併發量,那它是不會提高的。

而我更願意動態的來理解併發量,即:單位時間內可以進來的最大數量。

那麼提高效能,是可以線性提高併發量的,因為單位時間內,進來的同時也有出去。

我們先來做一個假設:
  單個程序(php fast-cgi)記憶體佔用10M
  單個執行緒( java web)記憶體佔用2M
  單個協程(go)記憶體佔用20K
  佇列任務(nginx)記憶體佔用2K


我們下面來看看記憶體與併發量的關係。

記憶體量 程序數 執行緒數 協程     佇列任務
1G 100 500 50K 500K
2G     200    1000      100K      1000K
4G     400        2000        200K      2000K
8G     800        4000        400K   4000K

從上面的結果中,我們可以很直觀的看出來,併發能力在不同的執行模式中的巨大區別。
多程序和多執行緒的模式,不僅是記憶體開銷巨大,而且在數量不斷增加的情況下,對CPU的壓力也是非常巨大,
這也是為什麼這類系統在併發量大的情況下會很不穩定,甚至宕機。

假設上邊計算出來的資料,都是靜態的容量,如果所有任務都不處理,那麼記憶體肯定都是會很快就被撐爆。
所以要達到更高的併發量,就需要有更快的處理速度,即做好效能優化。

下面,再來做一個假設。

我們現在有一臺伺服器,配置是8核16G記憶體。

如果我們的應用是計算密集型,純運算的系統,如:資料索引查詢、排序等操作。

而且還要假設,這個應用在多核並行運算時不存在鎖競爭的情況(只讀)。

        QPS = 1000ms/單個請求耗時*8


① 如果單個請求(任務)耗時100ms,那麼我們可以計算出來:qps = (1000ms/100ms)*8核 = 80個/秒

② 如果我們優化處理的演算法,單個請求耗時降低到10ms,那麼:qps = (1000ms/10ms)*8核 = 800個/秒

③ 如果可以繼續優化,將單個請求耗時降低到1ms,那麼:qps = (1000ms/1ms)*8核 = 8000個/秒

上面的情況和優化的效果理解起來應該很容易,因為對伺服器資源的依賴更多是CPU的運算能力和數量。


在實際的網際網路應用中,系統更多是依賴mysql,redis,rest api或者微服務,屬於IO密集型。按照上面的計算方式,可能就不太準確了,因為cpu是有富餘的。
在IO阻塞的時候,開啟更多工的方式當然有上面多程序、多執行緒、多協程和佇列的方式來實現,而且也是有效且更好地利用伺服器資源的方法,可以達到更高的併發量,
畢竟我們把大部分的運算放到了應用外部的mysql,redis,rest api等服務。

到此為止,我們已經知道併發量、效能優化跟伺服器資源(伺服器數量,cpu,記憶體)的關係,也知道效能優化對併發量的影響。

解疑答惑

1 記憶體越多,併發量一定可以越大嗎?

答:大部分情況是的。這個問題,上面有提到過,對於多程序、多執行緒的模式,執行緒太多的時候,執行緒搶佔時間片,CPU切換上下文會越來越慢。影響系統性能。

對於協程、佇列的執行模式,這個問題會好很多,當然協程排程、佇列維護的開銷,肯定也是會增加,只是增加的開銷不至於對系統性能造成直線下降。


2 CPU越快,應用的效能一定越好嗎?

答:絕對的。只不過CPU和應用效能提升可能不成線性增長的關係,因為應用可能是IO密集型,應用效能還會受到IO阻塞的影響。


3 CPU越多,應用的效能一定越好嗎?

答:大部分情況是的。如果大量鎖存在,效能提升可能會大打折扣,因為並行能力會被鎖住,又變成單執行緒執行了,沒有最大的發揮多CPU的作用。


4 伺服器越多,併發量一定越大嗎?

答:絕對的。伺服器增加,CPU和記憶體資源相應也就越多,併發能力也就會增大,他們之間是線性相關。


5 伺服器越多,效能一定越好嗎?

答:大部分情況是的。但是單個伺服器的效率可能會是下降的,資料一致性問題、同步問題、鎖問題,這些都會導致單個伺服器的效率(CPU利用率)下降,所以不是線性相關。

關於CPU利用率:
    如果只是考慮應用對CPU利用效率的話:單核=多核=多伺服器

    單程序/單執行緒的系統對於伺服器資源的利用率更高。

    到多核的系統中,就會因為鎖的問題,多工同步的問題,作業系統排程的問題,造成一定的資源浪費。而分散式系統中,這些浪費也會更嚴重。

6 怎樣更好的更有效的利用伺服器資源呢?

答:避免因為IO阻塞讓CPU閒置,導致CPU的浪費;

    避免多執行緒間增加鎖來保證同步,導致並行系統序列化;

    避免建立、銷燬、維護太多程序、執行緒,導致作業系統浪費資源在排程上;

    避免分散式系統中多伺服器的關聯,比如:依賴同一個mysql,程式邏輯中使用分散式鎖,導致瓶頸在mysql,分散式又變成序列化運算。


    上面說了要避免的地方,要具體怎麼來避免,到具體的業務場景就需要具體分析了。

     而且有些時候,為了業務功能,或者其它方面的需求,比如:可用性、伸縮性、擴充套件性、安全性,不得不犧牲掉一部分效能。

最後,做一個總結:

併發量,是一個容量的概念,服務可以接受的最大任務數量,動態的看待它,還需要把效能考慮進去。

效能,是一個速度的概念,單位時間內可以處理的任務數量。

高併發和高效能是緊密相關的,提高應用的效能,是肯定可以提高系統的併發能力的。

應用效能優化的時候,對於計算密集型和IO密集型還是有很大差別,需要分開來考慮。

增加伺服器資源(CPU、記憶體、伺服器數量),絕大部分時候是可以提高應用的併發能力和效能
(前提是應用能夠支援多工平行計算,多伺服器分散式計算才行),但也是要避免其中的一些問題,才可以更好的更有效率的利用伺服器資源。