程序員過(guò)關(guān)斬將--應(yīng)對(duì)高并發(fā)系統(tǒng)有沒(méi)有通用的解決方案呢?
靈魂拷問(wèn):
- 應(yīng)對(duì)高并發(fā)系統(tǒng)有沒(méi)有一些通用的解決方案呢?
- 這些方案解決了什么問(wèn)題呢?
- 這些方案有那些優(yōu)勢(shì)和劣勢(shì)呢?
對(duì)性能孜孜不倦的追求是互聯(lián)網(wǎng)技術(shù)不斷發(fā)展的根本驅(qū)動(dòng)力,從最初的大型機(jī)到現(xiàn)在的微型機(jī),在本質(zhì)上也是為了性能而生。軟件系統(tǒng)也存在類似的現(xiàn)象,一個(gè)系統(tǒng)從最初的少量訪問(wèn)請(qǐng)求到后期的大并發(fā)請(qǐng)求,這都需要我們對(duì)性能的提升提供一系列解決方案。像最初的淘寶,也僅僅是一個(gè)外包做出來(lái)的產(chǎn)品,隨著業(yè)務(wù)的不斷發(fā)展,淘寶的并發(fā)量指數(shù)級(jí)增加,同時(shí)對(duì)系統(tǒng)提出了嚴(yán)峻的挑戰(zhàn),這才逐步造就了現(xiàn)在淘寶這樣可以支撐數(shù)千萬(wàn)人同時(shí)在線的高并發(fā)系統(tǒng)。
提起應(yīng)對(duì)高并發(fā),每個(gè)人都或多或少可以說(shuō)出幾種解決方案,高并發(fā)系統(tǒng)的設(shè)計(jì)魅力在于我們能夠憑借程序員的聰明才智設(shè)計(jì)巧妙的方案,從而應(yīng)對(duì)巨大流量的沖擊。從目前已知的方案中,大體可以歸納為以下幾種
提升單機(jī)性能
盡可能的提升單機(jī)的性能是一個(gè)永恒的話題,無(wú)論是采用分布式還是其他方案,單機(jī)性能的提高,對(duì)于一個(gè)系統(tǒng)來(lái)說(shuō)只有益處。拿編程語(yǔ)言來(lái)說(shuō),c或者c++語(yǔ)言編寫(xiě)的程序理論上會(huì)比java ,net,Python寫(xiě)的程序要高效,當(dāng)然這需要建立在程序正常運(yùn)行的情況下。提升單機(jī)性能最簡(jiǎn)單粗暴的方式就是提升硬件性能,舉一個(gè)簡(jiǎn)單例子:假如數(shù)據(jù)庫(kù)DB的服務(wù)器內(nèi)存為8G,隨著數(shù)據(jù)量的增加,你會(huì)發(fā)現(xiàn)有些sql執(zhí)行會(huì)慢慢的變慢,原因是數(shù)據(jù)庫(kù)的索引或者數(shù)據(jù)在內(nèi)存中完全存放不下,需要回寫(xiě)磁盤,有些查詢?cè)趦?nèi)存中并不能命中,造成了一些sql會(huì)在磁盤中查詢數(shù)據(jù),這個(gè)時(shí)候如果把服務(wù)器的內(nèi)存增加到16G,你會(huì)發(fā)現(xiàn)這些慢sql居然憑空消失了,這是硬件提升性能的一個(gè)典型案例。
對(duì)于運(yùn)行的程序也是同樣的道理,盡可能的把程序優(yōu)化到極致,也許單機(jī)就可以達(dá)到別人分布式部署的性能效果,當(dāng)然這需要我們?cè)诰帉?xiě)代碼的時(shí)候仔細(xì)構(gòu)思。
“無(wú)論什么時(shí)候,我覺(jué)得提升單機(jī)性能都有必要
橫向擴(kuò)展
當(dāng)一個(gè)單機(jī)系統(tǒng)無(wú)法抵抗巨大流量沖擊的時(shí)候,最簡(jiǎn)單有效的解決方案之一便是橫向擴(kuò)展,橫向擴(kuò)展是指把巨大的流量分割為數(shù)個(gè)比較小的流量,從而解決高并發(fā)系統(tǒng)的性能問(wèn)題,本質(zhì)上,橫向擴(kuò)展屬于分而治之的理論,屬于分布式的概念范疇。
舉一個(gè)很簡(jiǎn)單的例子,假設(shè)目前單機(jī)處理請(qǐng)求數(shù)為200/s,當(dāng)每秒的請(qǐng)求數(shù)到達(dá)1000的時(shí)候,單臺(tái)機(jī)器肯定會(huì)遇到瓶頸,這個(gè)時(shí)候如果處理請(qǐng)求的服務(wù)器增加到5臺(tái),甚至更多,這樣便輕松解決了性能問(wèn)題。當(dāng)然,能否方便的橫向擴(kuò)展還要看具體的系統(tǒng)設(shè)計(jì),如果系統(tǒng)是無(wú)狀態(tài)的,理論上橫向擴(kuò)展是沒(méi)問(wèn)題的,但是一些有狀態(tài)的服務(wù),可能會(huì)涉及到狀態(tài)的遷移等工作,這也是為什么很多架構(gòu)師提倡無(wú)狀態(tài)服務(wù)的一個(gè)原因。
一個(gè)應(yīng)用程序的橫向擴(kuò)展可以通過(guò)負(fù)載均衡來(lái)實(shí)現(xiàn),像阿里云的SLB服務(wù),nginx的反向代理功能,這些都可以很方便實(shí)現(xiàn)應(yīng)用程序的橫向擴(kuò)展。但是,像數(shù)據(jù)庫(kù)比如mysql,這樣的DB系統(tǒng),無(wú)限制的橫向擴(kuò)展可能只是一個(gè)目標(biāo)。大多數(shù)DB采用的主從或者多主多從來(lái)解決橫向擴(kuò)展問(wèn)題,主節(jié)點(diǎn)負(fù)責(zé)寫(xiě)操作,從節(jié)點(diǎn)負(fù)責(zé)讀操作,當(dāng)然這里涉及到主從同步的機(jī)制,主從同步的延遲等問(wèn)題,有興趣的同學(xué)可以去深入研究一下。
image
那什么時(shí)候該選擇橫向擴(kuò)展呢?一般來(lái)講,在系統(tǒng)的設(shè)計(jì)之初便會(huì)考慮橫向擴(kuò)展,因?yàn)檫@種方案足夠簡(jiǎn)單,可以用堆砌硬件來(lái)解決的問(wèn)題就不是問(wèn)題?,F(xiàn)在我敢說(shuō)90%以上的系統(tǒng)在第一版上線的時(shí)候就做了類似負(fù)載均衡的部署方案,其中有很多就利用了nginx的反向代理功能。
image
當(dāng)然橫向擴(kuò)展并非沒(méi)有負(fù)面影響,和單機(jī)系統(tǒng)一樣,橫向擴(kuò)展也要考慮某個(gè)節(jié)點(diǎn)down掉的問(wèn)題,所以監(jiān)控和健康檢查是現(xiàn)在一個(gè)系統(tǒng)必備的手段,而且在系統(tǒng)設(shè)計(jì)之初便會(huì)在整體架構(gòu)之中。就像我前幾篇的文章所說(shuō),橫向擴(kuò)展既然屬于分布式范疇,必然需要考慮分布式系統(tǒng)需要考慮的問(wèn)題:
分布式系統(tǒng)的問(wèn)題
緩存除了上面所說(shuō)的橫向擴(kuò)展方案,另外一種行之有效并且足夠簡(jiǎn)單的便是緩存方案。這一點(diǎn)毋庸置疑,緩存可以遍布在一個(gè)系統(tǒng)的各個(gè)角落,從操作系統(tǒng)到瀏覽器,從cpu到磁盤,從數(shù)據(jù)庫(kù)到消息隊(duì)列,任何稍微復(fù)雜的服務(wù)和組件中都有緩存的影子。
緩存為什么可以大幅度提高性能的性能呢?這還需要從系統(tǒng)的瓶頸來(lái)說(shuō),在客戶端一個(gè)請(qǐng)求的生命周期中,這個(gè)請(qǐng)求的響應(yīng)時(shí)間嚴(yán)重受限于最慢的那個(gè)環(huán)節(jié),這類似于木桶效應(yīng)(一個(gè)木桶可以存的水量,取決于最短那個(gè)木板)。
舉一個(gè)很簡(jiǎn)單的例子:當(dāng)客戶端請(qǐng)求商城的一個(gè)商品信息的時(shí)候,請(qǐng)求經(jīng)過(guò)http協(xié)議到達(dá)服務(wù)器的某個(gè)端口,服務(wù)端程序把請(qǐng)求解包然后去請(qǐng)求數(shù)據(jù)庫(kù),數(shù)據(jù)庫(kù)不單單在另外一臺(tái)服務(wù)器上,而且還需要從磁盤中加載數(shù)據(jù),所謂的DB緩存沒(méi)有命中。在這整個(gè)過(guò)程中,請(qǐng)求磁盤的過(guò)程是最慢的,普通磁盤是由機(jī)械手臂,磁頭,轉(zhuǎn)軸,盤片組成,磁盤在查詢數(shù)據(jù)的時(shí)候,磁頭是需要花費(fèi)很長(zhǎng)時(shí)間累尋道的,當(dāng)然SSD的速度要比普通磁盤快的多,但是相比較內(nèi)存還是要慢幾個(gè)量級(jí)。而我們最想要的流程是這樣的:當(dāng)一個(gè)請(qǐng)求到達(dá)服務(wù)端的時(shí)候能盡快的從某個(gè)設(shè)備上取出信息,然后返給客戶端,這個(gè)設(shè)備絕不可能是磁盤,這個(gè)設(shè)備在速度和容量上比較均衡,它應(yīng)該是內(nèi)存。
“緩存在語(yǔ)義上要豐富很多,我們可以把任何可以降低響應(yīng)時(shí)間的中間存儲(chǔ)都稱之為緩存。比如CPU的一級(jí)緩存,二級(jí)緩存,三級(jí)緩存,瀏覽器的緩存等。緩存主要解決了上下游設(shè)備速度不匹配的問(wèn)題
image
程序界有一句古話:把數(shù)據(jù)放在離用戶最近的地方才是最快的。CDN本質(zhì)上就是做的這件事。對(duì)于緩存而言,我們經(jīng)常會(huì)聽(tīng)到瀏覽器緩存,進(jìn)程內(nèi)緩存,進(jìn)程外緩存等概念。目前針對(duì)于服務(wù)端一般的緩存策略為采用第三方kv存儲(chǔ)設(shè)備,比如redis,Memcache等。當(dāng)然在對(duì)性能極其苛刻的系統(tǒng)中,我還是推薦使用進(jìn)程內(nèi)緩存。
異步談到異步,必須要說(shuō)下同步,同步調(diào)用是指調(diào)用方要阻塞等待被調(diào)用方執(zhí)行完畢才可以返回。系統(tǒng)現(xiàn)在普遍都會(huì)采用多線程的方式來(lái)提供系統(tǒng)的吞吐量(多進(jìn)程的方式現(xiàn)在很少,但不代表沒(méi)有,比如:nodejs,nginx),在同步這種方式下,如果被調(diào)用方的響應(yīng)時(shí)間過(guò)長(zhǎng),會(huì)造成調(diào)用方的線程長(zhǎng)時(shí)間處于等待狀態(tài),線程的利用率大幅度降低,線程對(duì)于系統(tǒng)來(lái)說(shuō),是很昂貴的資源,創(chuàng)建大量的線程去應(yīng)對(duì)高并發(fā)是不明智的,不僅僅浪費(fèi)了內(nèi)存,而且會(huì)加大線程上下文cpu切換的成本。
一個(gè)高吞吐量的系統(tǒng),理論上所有的線程都要時(shí)時(shí)刻刻在工作,而且把cpu資源壓榨到最多。對(duì)于一個(gè)IO密集型操作來(lái)說(shuō),采用異步方式可以大大提高系統(tǒng)吞吐量。異步不需要等待被調(diào)用方執(zhí)行完成就可以執(zhí)行其他的邏輯,在被調(diào)用方執(zhí)行完畢之后通過(guò)通知回調(diào)的方式反饋給調(diào)用方。
“異步本質(zhì)上是一種編程思想,一種編程模型。他提高的是系統(tǒng)整體的吞吐量,但是請(qǐng)求的響應(yīng)時(shí)間對(duì)比同步方式來(lái)說(shuō)會(huì)略微加大。
像平時(shí)用的最多的消息隊(duì)列,在模型上也屬于異步編程模型。調(diào)用方會(huì)把消息丟到隊(duì)列中,然后直接返回去執(zhí)行其他業(yè)務(wù),被調(diào)用方接收到消息然后進(jìn)行處理,然后根據(jù)具體的業(yè)務(wù)看是否需要給予結(jié)果回復(fù)。有不少秒殺系統(tǒng)會(huì)采用消息隊(duì)列進(jìn)行流量削峰,這是異步帶來(lái)的優(yōu)勢(shì)之一。
image
在這里我需要多說(shuō)一句:異步并不是沒(méi)有代價(jià),在多數(shù)情況下,采用異步會(huì)比同步方式編寫(xiě)更多的代碼,而且查找bug會(huì)花費(fèi)更多的時(shí)間。但是對(duì)于一個(gè)高并發(fā)系統(tǒng)來(lái)說(shuō),異步帶來(lái)的益處還是值得的,前提是你正確應(yīng)用了異步。
本文轉(zhuǎn)載自微信公眾號(hào)「架構(gòu)師修行之路」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系架構(gòu)師修行之路公眾號(hào)。