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