緩存 + 哈希 = 高并發(fā)?

當前互聯(lián)網(wǎng)時代,怎么少的了高并發(fā)呢?高并發(fā)和高可用一樣, 已經(jīng)變成各個系統(tǒng)的標配了,如果你的系統(tǒng)QPS沒有個大幾千上萬,都不好意思跟人打招呼,雖然可能每天的調用量不超過100。

高并發(fā)這個詞,我個人感覺是從電商領域開始往外流傳的,特別是電商領域雙11那種藐視全球的流量,再把技術架構出來分享一把,現(xiàn)在搞得全互聯(lián)網(wǎng)都在說高并發(fā),而且你注意回憶一下所有你看到的高并發(fā)系統(tǒng),往往都逃不開一個核心概念,那就是緩存+哈希,一切都是以這個概念和基礎的,仿佛這就是高并發(fā)的核心技術了。`

我們看到的高并發(fā)技術

圍繞這個核心技術,通常我們看到的各種高并發(fā)的架構系統(tǒng),在博客、論壇、現(xiàn)場分享出來的高并發(fā)系統(tǒng),都跑不出以下幾個方面的東西。

資源靜態(tài)化

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

讀寫分離和分庫分表

讀寫分離是大家看到的第二個高并發(fā)的架構了,也很常規(guī),因為一般情況下讀比寫要多得多,所以數(shù)據(jù)庫的主庫寫,從庫們提供讀操作,一下就把數(shù)據(jù)庫的并發(fā)性能提高了。

如果還不夠,那么分庫分表把,把數(shù)據(jù)分到各個數(shù)據(jù)庫的各個機器上,進一步的減少單臺機器的壓力,從而達到高并發(fā)的目的。

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

萬能的緩存

說到高并發(fā),不得不說緩存了,現(xiàn)在各種緩存的工具也很多也很成熟,memcache,redis之類的KV數(shù)據(jù)庫作為緩存已經(jīng)非常成熟了,而且基本上都可以集群化部署,操作起來也很簡單,簡直變成了一個高并發(fā)的代言詞了,核心就是緩存技術了,而memcache和redis只是用來實現(xiàn)這個緩存技術的工具而已。

無敵的哈希

但凡大數(shù)據(jù)處理,高并發(fā)系統(tǒng),必言哈希,隨機插入,時間復雜度O(1),隨便查詢,時間復雜度O(1),除了耗費點空間以外,幾乎沒什么缺點了,在現(xiàn)在這個內(nèi)存廉價的時代,哈希表變成了一個高并發(fā)系統(tǒng)的標配。

正面的例子

我們來看個例子,看看一些個大家眼中標準的高并發(fā)系統(tǒng)的設計,這些設計大家應該都看過,無非就是上面的幾個要點,最重要的就是緩存+哈希,這兩個東西的組合好像無所不能。

活動秒殺頁面

活動秒殺頁面,這是個標準的高并發(fā)吧,到了搞活動的那個時刻,單頁面的訪問量是天量數(shù)據(jù)了,但這種系統(tǒng)有個特點邏輯簡單,只要帶寬和性能夠,就一定能提供穩(wěn)定的服務

服務能迅速的返回數(shù)據(jù)即可,沒有什么計算邏輯,這種高并發(fā)系統(tǒng)的設計基本上就是在怎么壓榨機器的IO性能了,如果有CDN絕對使用CDN,能在本機讀取的絕不走網(wǎng)絡獲取,能讀取到內(nèi)存中絕不放在硬盤上,把系統(tǒng)的磁盤IO和網(wǎng)絡IO都盡可能的壓榨,使用緩存+哈希技術,只要設計合理,99%的情況能搞定。

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

這樣的場景再擴展一點,就是凡是能提前提供數(shù)據(jù)的并發(fā)訪問,就可以用緩存+哈希來搞定并發(fā)。

12306

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

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

對于這樣的系統(tǒng),設計關注的就不僅僅是提高性能了,因為性能瓶頸已經(jīng)擺在那了,就1000張票,做得更多的是分流和限流了,除了緩存+哈希來保證用戶體驗以外,出現(xiàn)了奇葩驗證碼,各個站點分時間點放票。然后通過排隊系統(tǒng)來以一種異步的方式提供最終的服務。

我們給這樣的場景再擴展一下,凡是不能提前提供數(shù)據(jù)的,可以通過緩存+哈希來提高用戶體驗,然后通過異步方式來提供服務。

高并發(fā)系統(tǒng)如何設計

如果把上面兩個場景的情況合并一下,仿佛緩存+哈希變成萬能的了,很多人眼中的高并發(fā)就是上面的場景的組合,認為緩存+哈希就可以解決高并發(fā)的問題,其他的場景中,加上緩存提高讀寫速度,在加上哈希提供分流技術,再通過一個異步提供最終服務,高并發(fā)就這么搞定了,但實際上是不是這樣呢?顯然沒那么簡單,那如何來設計一個高并發(fā)的系統(tǒng)呢?

合理的數(shù)據(jù)結構

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

如果是google,baidu這種大型搜索系統(tǒng),或者京東淘寶這種電商系統(tǒng),搜索提示的調用量是搜索服務本身調用量的幾倍,因為你每輸入一個鍵盤,就要調用一次搜索提示服務,這算得上是個標準的高并發(fā)系統(tǒng)吧?那么它是怎么實現(xiàn)的呢?

可能很多人腦子里立刻出現(xiàn)了緩存+哈希的系統(tǒng),把搜索的搜索提示詞存在redis集群中,每次來了請求直接redis集群中查找key,然后返回相應的value值就行了,完美解決,雖然耗費點內(nèi)存,但是空間換時間嘛,也能接受,這么做行不行?恩,我覺得是可以的,但有人這么做嗎?沒有。

了解的人應該知道,沒有人會這么來實現(xiàn),這種搜索提示的功能一般用trie樹來做,耗費的內(nèi)存不多,查找速度為O(k),其中k為字符串的長度,雖然看上去沒有哈希表的O(1)好,但是少了網(wǎng)絡開銷,節(jié)約了很多內(nèi)存,并且實際查找時間還要不比緩存+哈希慢多少,一種合適當前場景的核心數(shù)據(jù)結構才是高并發(fā)系統(tǒng)的關鍵,緩存+哈希如果也看成一種數(shù)據(jù)結構,但這種數(shù)據(jù)結構并不適用于所有的高并發(fā)場景,所以

高并發(fā)系統(tǒng)的設計,關鍵在合理的數(shù)據(jù)結構的設計,而不在架構的套用

不斷的代碼性能優(yōu)化

有了上面的數(shù)據(jù)結構,并且設計出了系統(tǒng)了,拿到線上一跑,效果還行,但感覺沒達到極限,這時候可千萬不能就直接上外部工具(比如緩存)提升性能,需要做的是不斷的代碼性能的優(yōu)化,簡單的說,就是不斷的review你的代碼,不斷的找出可以優(yōu)化的性能點,然后進行優(yōu)化,因為之前設計的時候就已經(jīng)通過理論大概能算出來這個系統(tǒng)的并發(fā)量了,比如上面那個搜索提示,如果我們假定平均每個搜索詞6個字符,檢索一次大約需要查詢6次,需要2-3毫秒,這樣的話,如果8核的機器,多線程編程方式,一秒鐘最多能接受3200次請求(1000ms/2.5ms*8),如果沒達到這個量級,那么肯定是代碼哪里有問題。

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

或者最簡單的,就是把各個模塊的時間打印出來,壓測一遍,看看哪個模塊耗時,然后再去仔細review那個模塊的代碼,進行算法和數(shù)據(jù)結構的優(yōu)化,我個人比較推崇這個辦法,雖然比較笨,但是比較實在,性能差就是差,比較直觀能看出來,也能看出需要優(yōu)化的點,而且比較貼近代碼,少了外部工具的干擾,可能也比較裝逼吧。

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

高并發(fā)系統(tǒng)的實現(xiàn),就是不斷的優(yōu)化你代碼的性能,不斷逼近你設計時的理論值

再考慮外部通用方法

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

說起LRU,多說一句,這是個標準的緩存技術了,實現(xiàn)起來代碼也不復雜,就是個哈希表+鏈表的數(shù)據(jù)結構,一個合格的開發(fā)人員,即便沒有聽說過,給定一個場景,應該也能自己設計出來,我見過很多簡歷都說自己有大型高并發(fā)系統(tǒng)的開發(fā)經(jīng)驗,能熟練運用各種緩存技術,也對緩存技術有深入的了解,但是一面試的時候我讓他寫個LRU,首先有50%的人沒聽說過,OK,沒聽過沒關系,我描述一下,然后給一個場景,硬盤上有N條數(shù)據(jù),并且有一個程序包,提供GET和SET方法,可以操作磁盤讀寫數(shù)據(jù),但是速度太慢,請設計一個內(nèi)存中的數(shù)據(jù)結構,也提供GET和SET方法,保存最近訪問的前100條數(shù)據(jù),這個數(shù)據(jù)結構就是一個LRU了,讓面試者實現(xiàn)出來,如果覺得寫代碼麻煩,可以把數(shù)據(jù)結構設計出來描述一下就行了,就這樣,還很多人不會,這怎么能說是對緩存技術有深入了解呢?就這樣,怎么能說有過大型高并發(fā)系統(tǒng)的經(jīng)驗呢?這只是開源工具的使用經(jīng)驗罷了。

在沒把系統(tǒng)的性能壓榨完全之前,不要使用外部的通用方法,因為使用了以后就沒有太多進一步優(yōu)化空間了。

最后靠運維技術了

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

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

總結

羅里吧嗦說了這么多,其實我就想表達一個意思,不管是前面的高可用,還是今天的高并發(fā)。

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

代碼的健壯性決定了高可用,這些印度人就能做到,而高性能,高并發(fā)需要的不僅僅是代碼的健壯性,還有數(shù)據(jù)結構的設計和代碼的調優(yōu)能力了。

架構模式是大家總結出來的,和你的系統(tǒng)可能關系不是很大,學習太多的架構,腦袋會亂,還不如實打實的看幾本書,然后對著架構多推敲練習,很多人對數(shù)據(jù)結構嗤之以鼻,覺得對于現(xiàn)有的開發(fā)來說,數(shù)據(jù)結構沒那么重要了,但對于后端開發(fā)來說,數(shù)據(jù)結構是很重要的技能,雖然面試的時候不會讓你去翻轉一棵二叉樹,但二叉樹是什么,什么場景下用還是應該知道的吧?

找準合適的數(shù)據(jù)結構,不斷的優(yōu)化代碼,這樣來提升你的系統(tǒng)性能,這樣的系統(tǒng)才是你可控的,才有不斷優(yōu)化的空間,更好的高并發(fā),如果一開始就上外部的緩存技術,很可能如果性能達不到要求,就沒有優(yōu)化空間了,因為要修改外部的系統(tǒng)還是很困難的。

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

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容