京東資深架構(gòu)師:高性能高并發(fā)服務(wù)的瓶頸及突破思路
關(guān)于高性能高并發(fā)服務(wù)這個(gè)概念大家應(yīng)該也都比較熟悉了,今天為大家?guī)砣绾巫鲆粋€(gè)高性能高并發(fā)服務(wù)架構(gòu)的實(shí)踐和思考。
本次分享主要包括三個(gè)部分:
- 服務(wù)的瓶頸有哪些
- 如何提升整體服務(wù)的性能及并發(fā)
- 如何提升單機(jī)服務(wù)的性能及并發(fā)
服務(wù)的瓶頸有哪些
通常來說程序的定義是算法+數(shù)據(jù)結(jié)構(gòu)+數(shù)據(jù),算法簡單的理解就是一種計(jì)算方式,數(shù)據(jù)結(jié)構(gòu)顧名思義是一種存儲組織數(shù)據(jù)的結(jié)構(gòu)。
這兩者體現(xiàn)了程序需要用到的計(jì)算機(jī)資源,涉及到 CPU 資源、內(nèi)存資源,而數(shù)據(jù)部分除了內(nèi)存資源,往往還可能涉及到硬盤資源,甚至是彼此之間傳輸數(shù)據(jù)時(shí)會(huì)消耗網(wǎng)絡(luò)(網(wǎng)卡)資源。
當(dāng)我們搞清楚程序運(yùn)行起來時(shí)涉及哪些資源后,就可以更好地分析我們的服務(wù)中哪些可能是臨界資源。
所謂臨界資源就是多個(gè)進(jìn)程(線程)并發(fā)訪問某個(gè)資源時(shí),該資源同時(shí)只能服務(wù)某個(gè)或者某些進(jìn)程(線程)。
服務(wù)的瓶頸主要就是在這些臨界資源上,還有一些資源原本并不是臨界資源。
比如內(nèi)存在一開始是夠的,但是因?yàn)檫B接數(shù)或者線程數(shù)不斷地增多,最終導(dǎo)致其成為臨界資源,其他的 CPU、磁盤、網(wǎng)卡其實(shí)和內(nèi)存一樣,在訪問量增大以后一樣都可能會(huì)成為瓶頸。
所以怎么做到高性能高并發(fā)的服務(wù),簡單地說就是找到服務(wù)的瓶頸,在合理的范圍內(nèi)盡可能的消除瓶頸或者降低瓶頸帶來的影響。
再通俗一點(diǎn)的說就是資源總量不夠就加資源,什么資源不夠就加什么資源,同時(shí)盡量降低單次訪問的資源消耗,做到在資源總量一定的情況下有能力支撐更多的訪問。
如何提升整體服務(wù)的性能及并發(fā)
數(shù)據(jù)拆分
最典型的一個(gè)臨界資源就是數(shù)據(jù)庫,數(shù)據(jù)庫在一個(gè)大訪問量的系統(tǒng)中往往是最薄弱的一環(huán),因?yàn)閿?shù)據(jù)庫本身的服務(wù)能力是有限的。
以 MySQL 為例,MySQL 可以支持的并發(fā)連接數(shù)可能也就幾千個(gè),假設(shè)是 3000 個(gè),一個(gè)服務(wù)對其數(shù)據(jù)庫的并發(fā)訪問如果超過了 3000 個(gè),有部分訪問可能在建立連接的時(shí)候就失敗了。
在這種情況下,需要考慮的是如何將數(shù)據(jù)進(jìn)行分片,引入多個(gè) MySQL 實(shí)例,增加資源,如圖 1 所示。
圖 1:單數(shù)據(jù)實(shí)例改成數(shù)據(jù)庫集群
數(shù)據(jù)庫這個(gè)臨界資源通過數(shù)據(jù)拆分的方式,由原來的一個(gè) MySQL 實(shí)例變成了多個(gè) MySQL 實(shí)例。
這種情況下數(shù)據(jù)庫資源的整體并發(fā)服務(wù)能力自然提升了,同時(shí)由于服務(wù)壓力被分散,整個(gè)數(shù)據(jù)庫集群表現(xiàn)出來的性能也會(huì)比單個(gè)數(shù)據(jù)庫實(shí)例高很多。
存儲類的解決思路基本是類似的,都是將數(shù)據(jù)拆分,通過引入多個(gè)存儲服務(wù)實(shí)例提升整體存儲服務(wù)的能力,不管對于 SQL 類的還是 NoSQL 類的或文件存儲系統(tǒng)等都可以采用這個(gè)思路。
服務(wù)拆分
應(yīng)用程序自身的服務(wù)需要根據(jù)業(yè)務(wù)情況進(jìn)行合理的細(xì)化,讓每個(gè)服務(wù)只負(fù)責(zé)某一類功能,這個(gè)思想和微服務(wù)思想類似。
一句話就是盡量合理地將服務(wù)拆分,同時(shí)有一個(gè)非常重要的原則是讓拆分以后的同類服務(wù)盡量是無狀態(tài)或弱關(guān)聯(lián),這樣就可以很容易進(jìn)行水平擴(kuò)展。
如果拆分以后的同類服務(wù)的不同實(shí)例之間本身是有一些狀態(tài)引起彼此非常強(qiáng)的依賴,比如彼此要共享一些信息這些信息又會(huì)彼此影響,那這種拆分可能就未必非常的合理,需要結(jié)合業(yè)務(wù)重新進(jìn)行審視。
當(dāng)然生產(chǎn)環(huán)節(jié)上下游拆分以后不同的服務(wù)彼此之間的關(guān)聯(lián)又是另外一種情形,因?yàn)橥粋€(gè)生產(chǎn)環(huán)節(jié)上往往是走完一個(gè)服務(wù)環(huán)節(jié)才能進(jìn)入下一個(gè)服務(wù)環(huán)節(jié)。
相當(dāng)于有多個(gè)串行的服務(wù),任何一個(gè)環(huán)節(jié)的服務(wù)都有可能出現(xiàn)瓶頸,所以需要拆分以后針對相應(yīng)的服務(wù)進(jìn)行單獨(dú)優(yōu)化,這是拆分以后服務(wù)與服務(wù)之間的關(guān)系。
假設(shè)各個(gè)同類服務(wù)本身是無狀態(tài)或者弱依賴的情況下,針對應(yīng)用服務(wù)進(jìn)行分析,不同的應(yīng)用服務(wù)不太一樣,但是通常都會(huì)涉及到內(nèi)存資源以及計(jì)算資源。
以受內(nèi)存資源限制為例,一個(gè)應(yīng)用服務(wù)能承受的連接數(shù)是有限的(連接數(shù)受限),另外如果涉及上傳下載等大量數(shù)據(jù)傳輸?shù)那闆r,網(wǎng)絡(luò)資源很快就會(huì)成為瓶頸(網(wǎng)卡打滿)。
這種情況下最簡單的方式就是同樣的應(yīng)用服務(wù)實(shí)例部署多份,達(dá)到水平擴(kuò)展,如圖 2 所示。
圖 2:服務(wù)拆分
實(shí)際在真正拆分的時(shí)候需要考慮具體的業(yè)務(wù)特點(diǎn),比如像京東主站這種類型的網(wǎng)站,用戶在訪問的時(shí)候除了加載基本信息以外,還有商品圖片信息、價(jià)格信息、庫存信息、購物車信息以及訂單信息、發(fā)票信息等。
以及下單完成以后對應(yīng)的分揀配送等配套的物流服務(wù),這些都可以拆成單獨(dú)的服務(wù),拆分以后各個(gè)服務(wù)各司其職也能做更好的優(yōu)化。
服務(wù)拆分這件事情,打個(gè)不是特別恰當(dāng)?shù)谋确?,就好比上學(xué)時(shí)都是學(xué)習(xí),但是分了很多的科目,高考的時(shí)候要看總分,有些同學(xué)會(huì)有偏科的現(xiàn)象,有些科成績好有些科成績差一點(diǎn)。
因?yàn)榉趾芏嗫颇克院苋菀字雷约耗目剖潜容^強(qiáng)的、哪科是比較弱的,為了保證總體分?jǐn)?shù)***,一般在弱的科目上都需要多花點(diǎn)精力努力提高一下分?jǐn)?shù),不然總體分?jǐn)?shù)不會(huì)太高。
服務(wù)拆分也是同樣的道理,拆分以后可以很容易知道哪個(gè)服務(wù)是整體服務(wù)的瓶頸,針對瓶頸服務(wù)再進(jìn)行重點(diǎn)優(yōu)化就可以比較容易的提升整體服務(wù)的能力。
增長服務(wù)鏈路
在大型的網(wǎng)站服務(wù)方案上,在各種合理拆分以后,數(shù)據(jù)拆分以及服務(wù)拆分支持?jǐn)U展只是其中的一部分工作,之后還要根據(jù)需求看看是否需要引入緩存 CDN 之類的服務(wù)。
我把這個(gè)叫做增長服務(wù)鏈路,原來直接打到數(shù)據(jù)庫的請求,現(xiàn)在可能變成了先打到緩存再打到數(shù)據(jù)庫,對整個(gè)服務(wù)鏈路長度來說是變長的。
增長服務(wù)鏈路的原則主要是將越脆弱或者說越容易成為瓶頸的資源(比如數(shù)據(jù)庫)放置在鏈路的越末端。
在增長完服務(wù)鏈路之后,還要盡量的縮短訪問鏈路,比如可以在 CDN 層面就返回的就盡量不要繼續(xù)往下走了。
如果可以在緩存層面返回的就不要去訪問數(shù)據(jù)庫了,盡可能地讓每次的訪問鏈路變短。
可以一步解決的事情就一步解決,可以兩步解決的事情就不要走第三步,本質(zhì)上是降低每次訪問的資源消耗,尤其是越到鏈路的末端訪問資源的消耗會(huì)越大。
比如獲取一些產(chǎn)品的圖片信息可以在訪問鏈路的最前端使用 CDN,將訪問盡量擋住。
如果 CDN 上沒有***,就繼續(xù)往后端訪問,利用 Nginx 等反向代理將訪問打到相應(yīng)的圖片服務(wù)器上,而圖片服務(wù)器本身又可以針對性的做一些訪問優(yōu)化等。
比如像價(jià)格等信息比較敏感,如果有更改可能需要立即生效,需要直接訪問***的數(shù)據(jù),但是如果讓訪問直接打到數(shù)據(jù)庫中,數(shù)據(jù)庫往往直接就打掛了。
所以可以考慮在數(shù)據(jù)庫之前引入 Redis 等緩存服務(wù),將訪問打到緩存上,價(jià)格服務(wù)系統(tǒng)本身保證數(shù)據(jù)庫和緩存的強(qiáng)一致,降低對數(shù)據(jù)庫的訪問壓力。
在極端情況下,數(shù)據(jù)量雖然不是特別大,幾十臺緩存機(jī)器就可以抗住,但訪問量可能會(huì)非常大,可以將所有的數(shù)據(jù)都放在緩存中,如果緩存有異常甚至都不用去訪問數(shù)據(jù)庫直接返回訪問失敗即可。
因?yàn)樵谠L問量非常大的情況下,如果緩存掛了,訪問直接打到數(shù)據(jù)庫上,可能瞬間就把數(shù)據(jù)庫打趴下了。
所以在特定場景下可以考慮將緩存和數(shù)據(jù)庫切開,服務(wù)只訪問緩存,緩存失效重新從數(shù)據(jù)庫中加載數(shù)據(jù)到緩存中再對外服務(wù)也是可以的,所以在實(shí)踐中是可以靈活變通的。
小結(jié)
如何提升整體服務(wù)的性能及并發(fā),一句話概括就是:在合理范圍內(nèi)盡可能的拆分,拆分以后同類服務(wù)可以通過水平擴(kuò)展達(dá)到整體的高性能高并發(fā)。
同時(shí)將越脆弱的資源放置在鏈路的越末端,訪問的時(shí)候盡量將訪問鏈接縮短,降低每次訪問的資源消耗。
如何提升單機(jī)服務(wù)的性能及并發(fā)
前面說的這些情況可以解決大訪問量情況下的高并發(fā)問題,但是高性能最終還是要依賴單臺應(yīng)用的性能。
如果單臺應(yīng)用性能在低訪問量情況下性能已經(jīng)成渣了,那部署再多機(jī)器也解決不了問題,所以接下來聊一下單臺服務(wù)本身如果支持高性能高并發(fā)。
多線程/線程池方式
以 TCP server 為例來展開說明,最簡單的一個(gè) TCP server 代碼,版本一示例如圖 3 所示。
圖 3:版本一
這種方式純粹是一個(gè)示例,因?yàn)檫@個(gè) server 啟動(dòng)以后只能接受一條連接,也就是只能跟一個(gè)客戶端互動(dòng),且該連接斷開以后,后續(xù)就連不上了,也就是這個(gè) server 只能服務(wù)一次。
這個(gè)當(dāng)然是不行的,于是就有了版本二,如圖 4 所示,版本二可以一次接受一條連接,并進(jìn)行一些交互處理,當(dāng)這條連接全部處理完以后才能繼續(xù)下一條連接。
圖 4:版本二
這個(gè) server 相當(dāng)于是串行的,沒有并發(fā)可言,所以在版本二的基礎(chǔ)上又演化出了版本三,如圖 5 所示。
圖 5:版本三
這其實(shí)是我們經(jīng)常會(huì)接觸到的一種模型,這種模型的特點(diǎn)是每連接每線程,MySQL 5.5 以前用的就是這種模型,這種模型的特點(diǎn)是當(dāng)有大量連接的時(shí)候會(huì)創(chuàng)建大量的線程。
所以往往需要限制連接總數(shù),如果不做限制可能會(huì)出現(xiàn)創(chuàng)建了大量的線程,很快就會(huì)將內(nèi)存等資源耗干。
另一個(gè)是當(dāng)出現(xiàn)了大量的線程的時(shí)候,操作系統(tǒng)會(huì)有大量的 CPU 資源花費(fèi)在線程間的上下文切換上,導(dǎo)致真正給業(yè)務(wù)提供服務(wù)的 CPU 資源比例反倒很小。
同時(shí),考慮到大多數(shù)時(shí)候即使有很多連接也并不代表所有的連接在同一個(gè)時(shí)刻都是活躍的,所以版本三又演化出了版本四,如圖 6 所示。
圖 6:版本四
版本四的時(shí)候是很多的連接共享一個(gè)線程池,這些線程池里的線程數(shù)是固定的,這樣就可以做到線程池里的一個(gè)線程同時(shí)服務(wù)多條連接了,MySQL 5.6 之后采用的就是這種方式。
在絕大多數(shù)的開發(fā)中,線程池技術(shù)就已經(jīng)足夠了,但是線程池在充分榨干 CPU 計(jì)算資源或者說提供有效計(jì)算資源方面并不是最***的。
以一核的計(jì)算資源為例,線程池里假設(shè)有 x 個(gè)線程,這 x 個(gè)線程會(huì)被操作系統(tǒng)依據(jù)具體調(diào)度策略進(jìn)行調(diào)度,但是線程上下文切換本身是會(huì)消耗一定的 CPU 資源的。
假設(shè)這部分消耗代價(jià)是 w,而實(shí)際有效服務(wù)的能力是 c,那么理論上來說 w+c 就是總的 CPU 實(shí)際提供的計(jì)算資源,同時(shí)假設(shè)一核 CPU 理論上提供計(jì)算資源假設(shè)為 t,這個(gè)是固定的。
所以就會(huì)出現(xiàn)一種情況:當(dāng)線程池中線程數(shù)量較少的時(shí)候并發(fā)度較低,w 雖然小了,但是 c 也是比較小的,也就是 w+c < t,甚至是遠(yuǎn)遠(yuǎn)小于 t,如果線程數(shù)很多,又會(huì)出現(xiàn)上下文切換代價(jià)太大,即 w 變大了。
雖然 c 也隨之提升了一些,但因?yàn)?t 是固定的,所以 c 的上限值一定是小于 t-w 的,而且隨著 w 越大,c 的上限值反倒降低了,因此使用線程池的時(shí)候,線程數(shù)的設(shè)置需要根據(jù)實(shí)際情況進(jìn)行調(diào)整。
基于事件驅(qū)動(dòng)的模式
多線程(線程池)的方式可以較為方便地進(jìn)行并發(fā)編程,但是多線程的方式對 CPU 的有效利用率并不是***的,真正能夠充分利用 CPU 的編程方式是盡量讓 CPU 一直在工作,同時(shí)又盡量避免線程的上下文切換等開銷。
基于事件驅(qū)動(dòng)的模式(也稱 I/O 多路復(fù)用)在充分利用 CPU 有效計(jì)算能力這件事件上是非常出色的。
比較典型的有 select/poll/epoll/kevent(這些機(jī)制本身之間的優(yōu)劣今天先不展開說明,后續(xù)以 epoll 為例說明)。
這種模式的特點(diǎn)是將要監(jiān)聽的 socket fd 注冊在 epoll 上,等這個(gè)描述符可讀事件或者可寫事件就緒了,那么就會(huì)觸發(fā)相應(yīng)的讀操作或者寫操作。
可以簡單地理解為需要 CPU 干活的時(shí)候就會(huì)告知 CPU 需要做什么事情,實(shí)際使用時(shí)示例,如圖 7 所示。
圖 7:epoll 示例
這個(gè)事情拿一個(gè)經(jīng)典的例子來說明:假如在餐廳就餐,餐廳里有很多顧客(訪問),每連接每線程的方式相當(dāng)于每個(gè)客戶一個(gè)服務(wù)員(線程相當(dāng)于一個(gè)服務(wù)員)。
服務(wù)的過程中一個(gè)服務(wù)員一直為一個(gè)客戶服務(wù),那就會(huì)出現(xiàn)這個(gè)服務(wù)員除了真正提供服務(wù)以外有很大一段時(shí)間可能是空閑的,且隨著客戶數(shù)越多服務(wù)員數(shù)量也會(huì)越多,可餐廳的容量是有限的。
因?yàn)橐瑫r(shí)容納相同數(shù)量的服務(wù)員和顧客,所以餐廳服務(wù)顧客的數(shù)量將變成理論容量的 50%。
那這件事件對于老板(老板相當(dāng)于開發(fā)人員,希望可以充分利用 CPU 的計(jì)算能力,也就是在 CPU 計(jì)算能力<成本>一定的情況下希望盡量的多做一些事情)來說代價(jià)就會(huì)很大。
線程池的方式是雇傭固定數(shù)量的服務(wù)員,服務(wù)的時(shí)候一個(gè)服務(wù)員服務(wù)好幾個(gè)客戶,可以理解為一個(gè)服務(wù)員在客戶 A 面前站 1 分鐘,看看 A 客戶是否需要服務(wù)。
如果不需要就到 B 客戶那邊站 1 分鐘,看看 B 客戶是否需要服務(wù),以此類推。這種情況會(huì)比之前每個(gè)客戶一個(gè)服務(wù)員的情況節(jié)省一些成本,但是還是會(huì)出現(xiàn)一些成本上的浪費(fèi)。
還有一種模式也就是 epoll 的方式,相當(dāng)于服務(wù)員就在總臺等著,客戶有需要的時(shí)候就會(huì)在桌上的呼叫器上按一下按鈕表示自己需要服務(wù),服務(wù)員每次看一下總臺顯示的信息。
比如一共有 100 個(gè)客戶,一次可能有 10 個(gè)客戶呼叫,這個(gè)服務(wù)員就會(huì)過去為這 10 個(gè)客戶服務(wù)(假設(shè)服務(wù)每個(gè)客戶的時(shí)候不會(huì)出現(xiàn)停頓且可以在較短的時(shí)間內(nèi)處理完)。
等這個(gè)服務(wù)員為這 10 個(gè)客戶服務(wù)員完以后再重新回到總臺查看哪些客戶需要服務(wù),依此類推。在這種情況下,可能只需要一個(gè)服務(wù)員,而餐廳剩余的空間可以全部給客戶使用。
Nginx 服務(wù)器性能非常好,也能支撐非常多的連接,其網(wǎng)絡(luò)模型使用的就是 epoll 的方式,且在實(shí)現(xiàn)的時(shí)候采用了多個(gè)子進(jìn)程的方式。
相當(dāng)于同時(shí)有多個(gè) epoll 在工作,充分利用了 CPU 多核的特性,所以并發(fā)及性能都會(huì)比單個(gè) epoll 的方式會(huì)有更大的提升。
另外 Redis 緩存服務(wù)器大家應(yīng)該也非常熟悉,用的也是 epoll 的方式,性能也是非常好。
通過這些現(xiàn)成的經(jīng)典開源項(xiàng)目,大家就可以直觀地理解基于事件驅(qū)動(dòng)這一方式在實(shí)際生產(chǎn)環(huán)境中的性能是非常高的,性能提升以后并發(fā)效果一般都會(huì)隨之提升。
但是這種方式在實(shí)現(xiàn)的時(shí)候是非??简?yàn)編程功底以及邏輯嚴(yán)謹(jǐn)性,換句話編程友好性是非常差的。
因?yàn)橐粋€(gè)完整的上下文邏輯會(huì)被切成很多片段,比如“客戶端發(fā)送一個(gè)命令-服務(wù)器端接收命令進(jìn)行操作-然后返回結(jié)果”這個(gè)過程。
這個(gè)過程至少會(huì)包括一個(gè)可讀事件、一個(gè)可寫事件??勺x事件,簡單地理解就是指這條命令已經(jīng)發(fā)送到服務(wù)器端的 tcp 緩存區(qū)了,服務(wù)器去讀取命令(假設(shè)一次讀取完,如果一次讀取的命令不完整,可能會(huì)觸發(fā)多次讀事件)。
服務(wù)器再根據(jù)命令進(jìn)行操作獲取到結(jié)果,同時(shí)注冊一個(gè)可寫事件到 epoll 上,等待下一次可寫事件觸發(fā)以后再將結(jié)果發(fā)送出去。
想象一下當(dāng)有很多客戶端同時(shí)來訪問時(shí),服務(wù)器就會(huì)出現(xiàn)一種情況——一會(huì)兒在處理某個(gè)客戶端的讀事件,一會(huì)兒在處理另外的客戶端的寫事件。
總之都是在做一個(gè)完整訪問的上下文中的一個(gè)片段,其中任何一個(gè)片段有等待或者卡頓都將引起整個(gè)程序的阻塞。
當(dāng)然這個(gè)問題在多線程編程時(shí)也是同樣是存在的,只不過有時(shí)候大家習(xí)慣將線程設(shè)置成多個(gè),有些線程阻塞了,但可能其他線程并沒有在同一時(shí)刻阻塞。
所以問題不是特別嚴(yán)重,更嚴(yán)謹(jǐn)?shù)淖龇ㄊ窃诙嗑€程編程時(shí),將線程池的數(shù)量調(diào)整到最小進(jìn)行測試。
如果確實(shí)有卡頓,可以確保程序在最快的時(shí)間內(nèi)出現(xiàn)卡頓,從而快速確認(rèn)邏輯上是否有不足或者缺陷,確認(rèn)這種卡頓本身是否是正常現(xiàn)象。
語言層提供協(xié)程支持
多線程編程的方式明顯是支持了高并發(fā),但因?yàn)檎麄€(gè)程序線程間上下文調(diào)度可能造成 CPU 的利用率不是那么高,而基于事件驅(qū)動(dòng)的編程方式效果是非常好的。
但對編程功底要求非常高,而且在實(shí)現(xiàn)的時(shí)候需要花費(fèi)的時(shí)間也是最多的,所以一種比較折中的方式是考慮采用提供協(xié)程支持的語言比如 golang 這種的。
簡單說就是語言層面抽象出了一種更輕量級的線程,一般稱為協(xié)程,在 golang 里又叫 goroutine。
這些底層最終也是需要用操作系統(tǒng)的線程去跑,在 golang 的 runtime 實(shí)現(xiàn)時(shí)底層用到的操作系統(tǒng)的線程數(shù)量相對會(huì)少一點(diǎn)。
而上層程序里可以跑很多的 goroutine,這些 goroutine 會(huì)在語言層面進(jìn)行調(diào)度,看該由哪個(gè)線程來最終執(zhí)行這個(gè) goroutine。
因?yàn)?goroutine 之間的切換代價(jià)是遠(yuǎn)小于操作系統(tǒng)線程之間的切換代價(jià),而底層用到的操作系統(tǒng)數(shù)量又較少,線程間的上下文切換代價(jià)也會(huì)大大降低。
這類語言能比其他語言的多線程方式提供更好的并發(fā),因?yàn)樗鼘⒉僮飨到y(tǒng)的線程間切換的代價(jià)在語言層面盡可能擠壓到最小,同時(shí)編程復(fù)雜度大大降低,在這類語言中上下文邏輯可以保持連貫。
因?yàn)榻档土司€程間上下文切換的代價(jià),而 goroutine 之間的切換成本相對來說是遠(yuǎn)遠(yuǎn)小于線程間切換成本。
所以 CPU 的有效計(jì)算能力相對來說也不會(huì)太低,可以比較容易的獲得了一個(gè)高并發(fā)且性能還可以的服務(wù)。
小結(jié)
如何提升單機(jī)服務(wù)的性能及并發(fā),如果對性能或者高并發(fā)的要求沒有達(dá)到非??量痰囊?,選型的時(shí)候基于事件驅(qū)動(dòng)的方式可以優(yōu)先級降低一點(diǎn),選擇普通的多線程編程即可(其實(shí)多數(shù)場景都可以滿足了)。
如果想單機(jī)的并發(fā)程度更好一點(diǎn),可以考慮選擇有協(xié)程支持的語言,如果還嫌不夠,那就將邏輯理順,考慮采用基于事件驅(qū)動(dòng)的模式,這個(gè)在 C/C++ 里直接用 select/epoll/kevent 等就可以了。
在 Java 里可以考慮采用 NIO 的方式,而從這點(diǎn)上來說像 golang 這種提供協(xié)程支持的語言一般是不支持在程序?qū)用孀约簩?shí)現(xiàn)基于事件驅(qū)動(dòng)的編程方式的。
總結(jié)
其實(shí)并沒有一刀切的***法則,大體原則是根據(jù)實(shí)際情況具體問題具體分析,找到服務(wù)瓶頸,資源不夠加資源,盡可能降低每次訪問的資源消耗,整體服務(wù)每個(gè)環(huán)節(jié)盡量做到可以水平擴(kuò)展。
同時(shí)盡量提高單機(jī)的有效利用率,從而確保在扛住整個(gè)服務(wù)的同時(shí)盡量降低資源消耗成本。
張成遠(yuǎn),京東資深架構(gòu)師,《MariaDB原理與實(shí)現(xiàn)》作者,開源項(xiàng)目 Speedy 作者,分布式數(shù)據(jù)庫相關(guān)研究方向碩士。負(fù)責(zé)京東分布式數(shù)據(jù)庫系統(tǒng)的架構(gòu)與研發(fā),擅長大規(guī)模分布式系統(tǒng)架構(gòu)。