坑系列 —— 快取 + 雜湊 = 高併發?

吳YH堅發表於2016-07-08

今天繼續坑系列,高可用已經講過了,當前網際網路時代,怎麼少的了高併發呢?高併發高可用一樣, 已經變成各個系統的標配了,如果你的系統QPS沒有個大幾千上萬,都不好意思跟人打招呼,雖然可能每天的呼叫量不超過100。

高併發這個詞,我個人感覺是從電商領域開始往外流傳的,特別是電商領域雙11那種藐視全球的流量,再把技術架構出來分享一把,現在搞得全網際網路都在說高併發,而且你注意回憶一下所有你看到的高併發系統,往往都逃不開一個核心概念,那就是快取+雜湊,一切都是以這個概念和基礎的,彷彿這就是高併發的核心技術了。`

我們看到的高併發技術

圍繞這個核心技術,通常我們看到的各種高併發的架構系統,在部落格,論壇,現場分享出來的高併發系統,都跑不出以下幾個方面的東西。

資源靜態化

就是那種單個頁面流量巨大無比,每秒的QPS幾十萬上百萬的系統,確實併發高的系統,核心解決方案就是靜態化,靠機器和頻寬去抗,假如沒有CDN的話,所有流量都落到同一個IP下面的話,基本上也就是用Nginx的檔案靜態化了,單機的承受能力主要取決於頻寬和單機的效能,要再多的話,那就LVS(或者F5)+叢集了,這種的典型場景就是搞活動時候的首頁,活動頁面了,還有就是引流搜尋引擎的著陸頁了,一般都是現成的圖片和文字靜態化,當然,這種還有很多前端的技巧和技術了,這一點我不是很瞭解,就不得瑟了,就中後臺來說,大部分情況下直接Nginx搞定了,核心還是使用了快取技術

讀寫分離和分庫分表

讀寫分離是大家看到的第二個高併發的架構了,也很常規,因為一般情況下讀比寫要多得多,所以資料庫的主庫寫,從庫們提供讀操作,一下就把資料庫的併發效能提高了。

如果還不夠,那麼分庫分表把,把資料分到各個資料庫的各個機器上,進一步的減少單臺機器的壓力,從而達到高併發的目的。

如果是分庫分表,有時候使用的就是雜湊技術了,以某個欄位雜湊一下然後來分庫分表,讀寫分離的讀操作,基本也是通過雜湊技術把讀落到不同的機器上去減輕單機壓力。

萬能的快取

說到高併發,不得不說快取了,現在各種快取的工具也很多也很成熟,memcache,redis之類的KV資料庫作為快取已經非常成熟了,而且基本上都可以叢集化部署,操作起來也很簡單,簡直變成了一個高併發的代言詞了,核心就是快取技術了,而memcacheredis只是用來實現這個快取技術的工具而已。

無敵的雜湊

但凡大資料處理,高併發系統,必言雜湊,隨機插入,時間複雜度O(1),隨便查詢,時間複雜度O(1),除了耗費點空間以外,幾乎沒什麼缺點了,在現在這個記憶體廉價的時代,雜湊表變成了一個高併發系統的標配。

正面的例子

我們來看個例子,看看一些個大家眼中標準的高併發系統的設計,這些設計大家應該都看過,無非就是上面的幾個要點,最重要的就是快取+雜湊,這兩個東西的組合好像無所不能。

活動秒殺頁面

活動秒殺頁面,這是個標準的高併發吧,到了搞活動的那個時刻,單頁面的訪問量是天量資料了,但這種系統有個特點邏輯簡單,只要頻寬和效能夠,就一定能提供穩定的服務 服務能迅速的返回資料即可,沒有什麼計算邏輯,這種高併發系統的設計基本上就是在怎麼壓榨機器的IO效能了,如果有CDN絕對使用CDN,能在本機讀取的絕不走網路獲取,能讀取到記憶體中絕不放在硬碟上,把系統的磁碟IO和網路IO都儘可能的壓榨,使用快取+雜湊技術,只要設計合理,99%的情況能搞定。

活動頁面的衝擊感實在太強,想象一下幾千萬人同時訪問網站還沒有掛,讓很多人覺得高併發應該就是這樣子,這估計也是高併發這次經常在電商技術中出現的原因吧,因為搞個活動就可以搞出一個高併發事件。

這樣的場景再擴充套件一點,就是凡是能提前提供資料的併發訪問,就可以用快取+雜湊來搞定併發。

12306

接下來,我們再看看這個星球併發量最瘋狂的網站,瞬間的訪問量碾壓其他網站的12306,這種場景下的高併發也有個特點,那就是雖然量大,但其實無法給每個使用者提供服務

類似的其實還有商品的搶購系統,商品和車票一共就1000張,你100萬的人搶,你係統做得再好,也無法給100萬人提供服務,之前12306剛剛上線的時候很多人噴,說如果讓某某公司來做肯定能做好,但大家很多隻看到了表面,讓某很厲害的公司來做,最多也只能做到大家訪問的時候不會掛掉,你買不到車票還是買不到,而且現在的12306體驗也已經做得很好了,也不卡了,但是還是很多人罵,為什麼?還不是因為買不到票。

對於這樣的系統,設計關注的就不僅僅是提高效能了,因為效能瓶頸已經擺在那了,就1000張票,做得更多的是分流和限流了,除了快取+雜湊來保證使用者體驗以外,出現了奇葩驗證碼,各個站點分時間點放票。然後通過排隊系統來以一種非同步的方式提供最終的服務。

我們給這樣的場景再擴充套件一下,凡是不能提前提供資料的,可以通過快取+雜湊來提高使用者體驗,然後通過非同步方式來提供服務。

高併發系統如何設計

如果把上面兩個場景的情況合併一下,彷彿快取+雜湊變成萬能的了,很多人眼中的高併發就是上面的場景的組合,認為快取+雜湊就可以解決高併發的問題,其他的場景中,加上快取提高讀寫速度,在加上雜湊提供分流技術,再通過一個非同步提供最終服務,高併發就這麼搞定了,但實際上是不是這樣呢?顯然沒那麼簡單,那如何來設計一個高併發的系統呢?

合理的資料結構

舉個例子來說吧,搜尋提示功能大家都知道吧,就是下面這個圖的東西。

如果是google,baidu這種大型搜尋系統,或者京東淘寶這種電商系統,搜尋提示的呼叫量是搜尋服務本身呼叫量的幾倍,因為你每輸入一個鍵盤,就要呼叫一次搜尋提示服務,這算得上是個標準的高併發系統吧?那麼它是怎麼實現的呢?

可能很多人腦子裡立刻出現了快取+雜湊的系統,把搜尋的搜尋提示詞存在redis叢集中,每次來了請求直接redis叢集中查詢key,然後返回相應的value值就行了,完美解決,雖然耗費點記憶體,但是空間換時間嘛,也能接受,這麼做行不行?恩,我覺得是可以的,但有人這麼做嗎?沒有。

瞭解的人應該知道,沒有人會這麼來實現,這種搜尋提示的功能一般用trie樹來做,耗費的記憶體不多,查詢速度為O(k),其中k為字串的長度,雖然看上去沒有雜湊表的O(1)好,但是少了網路開銷,節約了很多記憶體,並且實際查詢時間還要不比快取+雜湊慢多少,一種合適當前場景的核心資料結構才是高併發系統的關鍵,快取+雜湊如果也看成一種資料結構,但這種資料結構並不適用於所有的高併發場景,所以

高併發系統的設計,關鍵在合理的資料結構的設計,而不在架構的套用

不斷的程式碼效能優化

有了上面的資料結構,並且設計出了系統了,拿到線上一跑,效果還行,但感覺沒達到極限,這時候可千萬不能就直接上外部工具(比如快取)提升效能,需要做的是不斷的程式碼效能的優化,簡單的說,就是不斷的review你的程式碼,不斷的找出可以優化的效能點,然後進行優化,因為之前設計的時候就已經通過理論大概能算出來這個系統的併發量了,比如上面那個搜尋提示,如果我們假定平均每個搜尋詞6個字元,檢索一次大約需要查詢6次,需要2-3毫秒,這樣的話,如果8核的機器,多執行緒程式設計方式,一秒鐘最多能接受3200次請求(1000ms/2.5ms*8),如果沒達到這個量級,那麼肯定是程式碼哪裡有問題。

這個階段可能需要藉助一些個工具了,JAVA有JAVA的效能優化工具,大家都有自己覺得好使的,我本身JAVA用得很少,也沒啥可推薦的,如果是Golang的話,自帶的go tool pprof就能很好的進行效能優化。

或者最簡單的,就是把各個模組的時間列印出來,壓測一遍,看看哪個模組耗時,然後再去仔細review那個模組的程式碼,進行演算法和資料結構的優化,我個人比較推崇這個辦法,雖然比較笨,但是比較實在,效能差就是差,比較直觀能看出來,也能看出需要優化的點,而且比較貼近程式碼,少了外部工具的干擾,可能也比較裝逼吧。

這個過程是一個長期的過程,也是《重構:改善程式碼的既有設計》中提到的,一個優秀的系統需要不斷的進行程式碼級別的優化和重構,所以

高併發系統的實現,就是不斷的優化你程式碼的效能,不斷逼近你設計時的理論值

再考慮外部通用方法

以上兩個都完成了,併發量也基本達到理論值了,但是還有提升的需求,這時候再來考慮外部的通用方法,比如加一個LRU快取,把熱詞的查詢時間變成O(1),進一步提高效能。

說起LRU,多說一句,這是個標準的快取技術了,實現起來程式碼也不復雜,就是個雜湊表+連結串列的資料結構,一個合格的開發人員,即便沒有聽說過,給定一個場景,應該也能自己設計出來,我見過很多簡歷都說自己有大型高併發系統的開發經驗,能熟練運用各種快取技術,也對快取技術有深入的瞭解,但是一面試的時候我讓他寫個LRU,首先有50%的人沒聽說過,OK,沒聽過沒關係,我描述一下,然後給一個場景,硬碟上有N條資料,並且有一個程式包,提供GET和SET方法,可以操作磁碟讀寫資料,但是速度太慢,請設計一個記憶體中的資料結構,也提供GET和SET方法,儲存最近訪問的前100條資料,這個資料結構就是一個LRU了,讓面試者實現出來,如果覺得寫程式碼麻煩,可以把資料結構設計出來描述一下就行了,就這樣,還很多人不會,這怎麼能說是對快取技術有深入瞭解呢?就這樣,怎麼能說有過大型高併發系統的經驗呢?這只是開源工具的使用經驗罷了。

在沒把系統的效能壓榨完全之前,不要使用外部的通用方法,因為使用了以後就沒有太多進一步優化空間了。

最後靠運維技術了

上面幾種都已經弄完了,還需要提升效能,這時候再考慮運維的技術了,比如常規的加負載均衡,部署成叢集之類的,通過運維和部署的方法提高服務的併發性。

高併發系統只是相對的,沒有什麼無上限的高併發,流量的洪流來了,再高的高併發一樣掛,新浪微博的高併發應該做得很好吧?但是林心如發條微博說她和霍建華談戀愛了,一樣把微博搞掛了(非官方訊息啊,我猜測的,呵呵,那天下午新浪微博正好掛了),呵呵,你說要是TFBOY明天過生日,微博是不是要連夜加幾個redis叢集啊?如果有微博的朋友,留個言溜溜唄:)

總結

羅裡吧嗦說了這麼多,其實我就想表達一個意思,不管是前面的高可用,還是今天的高併發

程式碼才是關鍵,架構都是錦上添花的東西,既然是錦上添花的,必然坑多,沒有什麼捷徑。

程式碼的健壯性決定了高可用,這些印度人就能做到,而高效能,高併發需要的不僅僅是程式碼的健壯性,還有資料結構的設計和程式碼的調優能力了。

架構模式是大家總結出來的,和你的系統可能關係不是很大,學習太多的架構,腦袋會亂,還不如實打實的看幾本書,然後對著架構多推敲練習,很多人對資料結構嗤之以鼻,覺得對於現有的開發來說,資料結構沒那麼重要了,但對於後端開發來說,資料結構是很重要的技能,雖然面試的時候不會讓你去翻轉一棵二叉樹,但二叉樹是什麼,什麼場景下用還是應該知道的吧?

找準合適的資料結構,不斷的優化程式碼,這樣來提升你的系統效能,這樣的系統才是你可控的,才有不斷優化的空間,更好的高併發,如果一開始就上外部的快取技術,很可能如果效能達不到要求,就沒有優化空間了,因為要修改外部的系統還是很困難的。

這幾篇我覺得我都在瞎扯淡,雖然比較虛,但也是我工作這麼些年淌坑無數以後的總結吧,後面的文章會寫一些實際的了,我的搜尋引擎部分還沒寫完呢。盡請期待。呵呵。。


如果你覺得不錯,歡迎轉發給更多人看到,也歡迎關注我的公眾號,主要聊聊搜尋,推薦,廣告技術,還有瞎扯。。文章會在這裡首先發出來:)掃描或者搜尋微訊號XJJ267或者搜尋西加加語言就行

相關文章