高可用上文咱們已經講過了,可當前互聯網時代,怎麼少的了高併發呢?高併發和高可用同樣, 已經變成各個系統的標配了,若是你的系統QPS沒有個大幾千上萬,都很差意思跟人打招呼,雖然可能天天的調用量不超過100。前端
高併發這個詞,我我的感受是從電商領域開始往外流傳的,特別是電商領域雙11那種藐視全球的流量,再把技術架構出來分享一把,如今搞得全互聯網都在說高併發,並且你注意回憶一下全部你看到的高併發系統,每每都逃不開一個核心概念,那就是緩存+哈希,一切都是以這個概念和基礎的,彷彿這就是高併發的核心技術了。web
緩存+哈希=高併發?面試
(一)咱們看到的高併發技術redis
圍繞這個核心技術,一般咱們看到的各類高併發的架構系統,在博客,論壇,現場分享出來的高併發系統,都跑不出如下幾個方面的東西。算法
1資源靜態化數據庫
就是那種單個頁面流量巨大無比,每秒的QPS幾十萬上百萬的系統,確實併發高的系統,核心解決方案就是靜態化,靠機器和帶寬去抗,假如沒有CDN的話,全部流量都落到同一個IP下面的話,基本上也就是用Nginx的文件靜態化了,單機的承受能力主要取決於帶寬和單機的性能,要再多的話,那就LVS(或者F5)+集羣了,這種的典型場景就是搞活動時候的首頁,活動頁面了,還有就是引流搜索引擎的着陸頁了,通常都是現成的圖片和文字靜態化,固然,這種還有不少前端的技巧和技術了,這一點我不是很瞭解,就不得瑟了,就中後臺來講,大部分狀況下直接Nginx搞定了,核心仍是使用了緩存技術。編程
2讀寫分離和分庫分表後端
讀寫分離是你們看到的第二個高併發的架構了,也很常規,由於通常狀況下讀比寫要多得多,因此數據庫的主庫寫,從庫們提供讀操做,一下就把數據庫的併發性能提升了。數組
若是還不夠,那麼分庫分表把,把數據分到各個數據庫的各個機器上,進一步的減小單臺機器的壓力,從而達到高併發的目的。緩存
若是是分庫分表,有時候使用的就是哈希技術了,以某個字段哈希一下而後來分庫分表,讀寫分離的讀操做,基本也是經過哈希技術把讀落到不一樣的機器上去減輕單機壓力。
3萬能的緩存
說到高併發,不得不說緩存了,如今各類緩存的工具也不少也很成熟,memcache,redis之類的KV數據庫做爲緩存已經很是成熟了,並且基本上均可以集羣化部署,操做起來也很簡單,簡直變成了一個高併發的代言詞了,核心就是緩存技術了,而memcache和redis只是用來實現這個緩存技術的工具而已。
4無敵的哈希
但凡大數據處理,高併發系統,必言哈希,隨機插入,時間複雜度O(1),隨便查詢,時間複雜度O(1),除了耗費點空間之外,幾乎沒什麼缺點了,在如今這個內存廉價的時代,哈希表變成了一個高併發系統的標配。
5正面的例子
咱們來看個例子,看看一些個你們眼中標準的高併發系統的設計,這些設計你們應該都看過,無非就是上面的幾個要點,最重要的就是緩存+哈希,這兩個東西的組合好像無所不能。
活動秒殺頁面
活動秒殺頁面,這是個標準的高併發吧,到了搞活動的那個時刻,單頁面的訪問量是天量數據了,但這種系統有個特色:邏輯簡單,只要帶寬和性可以,就必定能提供穩定的服務服務。
能迅速的返回數據便可,沒有什麼計算邏輯,這種高併發系統的設計基本上就是在怎麼壓榨機器的IO性能了,若是有CDN絕對使用CDN,能在本機讀取的毫不走網絡獲取,能讀取到內存中毫不放在硬盤上,把系統的磁盤IO和網絡IO都儘量的壓榨,使用緩存+哈希技術,只要設計合理,99%的狀況能搞定。活動頁面的衝擊感實在太強,想象一下幾千萬人同時訪問網站尚未掛,讓不少人以爲高併發應該就是這樣子,這估計也是高併發此次常常在電商技術中出現的緣由吧,由於搞個活動就能夠搞出一個高併發事件。
這樣的場景再擴展一點,就是凡是能提早提供數據的併發訪問,就能夠用緩存+哈希來搞定併發。
12306
接下來,咱們再看看這個星球併發量最瘋狂的網站,瞬間的訪問量碾壓其餘網站的12306,這種場景下的高併發也有個特色,那就是雖然量大,但其實沒法給每一個用戶提供服務。
相似的其實還有商品的搶購系統,商品和車票一共就1000張,你100萬的人搶,你係統作得再好,也沒法給100萬人提供服務,以前12306剛剛上線的時候不少人噴,說若是讓某某公司來作確定能作好,但你們不少只看到了表面,讓某很厲害的公司來作,最多也只能作到你們訪問的時候不會掛掉,你買不到車票仍是買不到,並且如今的12306體驗也已經作得很好了,也不卡了,可是仍是不少人罵,爲何?還不是由於買不到票。
對於這樣的系統,設計關注的就不只僅是提升性能了,由於性能瓶頸已經擺在那了,就1000張票,作得更多的是分流和限流了,除了緩存+哈希來保證用戶體驗之外,出現了奇葩驗證碼,各個站點分時間點放票。而後經過排隊系統來以一種異步的方式提供最終的服務。
咱們給這樣的場景再擴展一下,凡是不能提早提供數據的,能夠經過緩存+哈希來提升用戶體驗,而後經過異步方式來提供服務。
(二)高併發系統如何設計
若是把上面兩個場景的狀況合併一下,彷彿緩存+哈希變成萬能的了,不少人眼中的高併發就是上面的場景的組合,認爲緩存+哈希就能夠解決高併發的問題,其餘的場景中,加上緩存提升讀寫速度,在加上哈希提供分流技術,再經過一個異步提供最終服務,高併發就這麼搞定了,但其實是不是這樣呢?顯然沒那麼簡單,那如何來設計一個高併發的系統呢?
1合理的數據結構
舉個例子來講吧,搜索提示功能你們都知道吧,就是下面這個圖的東西。
若是是google,baidu這種大型搜索系統,或者京東淘寶這種電商系統,搜索提示的調用量是搜索服務自己調用量的幾倍,由於你每輸入一個鍵盤,就要調用一次搜索提示服務,這算得上是個標準的高併發系統吧?那麼它是怎麼實現的呢?
可能不少人腦子裏馬上出現了緩存+哈希的系統,把搜索的搜索提示詞存在redis集羣中,每次來了請求直接redis集羣中查找key,而後返回相應的value值就好了,完美解決,雖然耗費點內存,可是空間換時間嘛,也能接受,這麼作行不行?恩,我以爲是能夠的,但有人這麼作嗎?沒有。
瞭解的人應該知道,沒有人會這麼來實現,這種搜索提示的功能通常用trie樹來作,耗費的內存很少,查找速度爲O(k),其中k爲字符串的長度,雖然看上去沒有哈希表的O(1)好,可是少了網絡開銷,節約了不少內存,而且實際查找時間還要不比緩存+哈希慢多少,一種合適當前場景的核心數據結構纔是高併發系統的關鍵,緩存+哈希若是也當作一種數據結構,但這種數據結構並不適用於全部的高併發場景,因此高併發系統的設計,關鍵在合理的數據結構的設計,而不在架構的套用。
2不斷的代碼性能優化
有了上面的數據結構,而且設計出了系統了,拿到線上一跑,效果還行,但感受沒達到極限,這時候可千萬不能就直接上外部工具(好比緩存)提高性能,須要作的是不斷的代碼性能的優化,簡單的說,就是不斷的review你的代碼,不斷的找出能夠優化的性能點,而後進行優化,由於以前設計的時候就已經經過理論大概能算出來這個系統的併發量了,好比上面那個搜索提示,若是咱們假定平均每一個搜索詞6個字符,檢索一次大約須要查詢6次,須要2-3毫秒,這樣的話,若是8核的機器,多線程編程方式,一秒鐘最多能接受3200次請求(1000ms/2.5ms*8),若是沒達到這個量級,那麼確定是代碼哪裏有問題。
這個階段可能須要藉助一些個工具了,JAVA有JAVA的性能優化工具,你們都有本身以爲好使的,我自己JAVA用得不多,也沒啥可推薦的,若是是Golang的話,自帶的go tool pprof就能很好的進行性能優化。
或者最簡單的,就是把各個模塊的時間打印出來,壓測一遍,看看哪一個模塊耗時,而後再去仔細review那個模塊的代碼,進行算法和數據結構的優化,我我的比較推崇這個辦法,雖然比較笨,可是比較實在,性能差就是差,比較直觀能看出來,也能看出須要優化的點,並且比較貼近代碼,少了外部工具的干擾,可能也比較裝逼吧。
這個過程是一個長期的過程,也是《重構:改善代碼的既有設計》中提到的,一個優秀的系統須要不斷的進行代碼級別的優化和重構,因此高併發系統的實現,就是不斷的優化你代碼的性能,不斷逼近你設計時的理論值。
3再考慮外部通用方法
以上兩個都完成了,併發量也基本達到理論值了,可是還有提高的需求,這時候再來考慮外部的通用方法,好比加一個LRU緩存,把熱詞的查詢時間變成O(1),進一步提升性能。
提及LRU,多說一句,這是個標準的緩存技術了,實現起來代碼也不復雜,就是個哈希表+鏈表的數據結構,一個合格的開發人員,即使沒有據說過,給定一個場景,應該也能本身設計出來,我見過不少簡歷都說本身有大型高併發系統的開發經驗,能熟練運用各類緩存技術,也對緩存技術有深刻的瞭解,可是一面試的時候我讓他寫個LRU,首先有50%的人沒據說過,OK,沒聽過不要緊,我描述一下,而後給一個場景,硬盤上有N條數據,而且有一個程序包,提供GET和SET方法,能夠操做磁盤讀寫數據,可是速度太慢,請設計一個內存中的數據結構,也提供GET和SET方法,保存最近訪問的前100條數據,這個數據結構就是一個LRU了,讓面試者實現出來,若是以爲寫代碼麻煩,能夠把數據結構設計出來描述一下就好了,就這樣,還不少人不會,這怎麼能說是對緩存技術有深刻了解呢?就這樣,怎麼能說有過大型高併發系統的經驗呢?這只是開源工具的使用經驗罷了。
在沒把系統的性能壓榨徹底以前,不要使用外部的通用方法,由於使用了之後就沒有太多進一步優化空間了。
4最後靠運維技術了
上面幾種都已經弄完了,還須要提高性能,這時候再考慮運維的技術了,好比常規的加負載均衡,部署成集羣之類的,經過運維和部署的方法提升服務的併發性。
高併發系統只是相對的,沒有什麼無上限的高併發,流量的洪流來了,再高的高併發同樣掛,新浪微博的高併發應該作得很好吧?可是林心如發條微博說她和霍建華談戀愛了,同樣把微博搞掛了(非官方消息啊,我猜想的,呵呵,那天下午新浪微博正好掛了),呵呵,你說要是TFBOY明天過生日,微博是否是要連夜加幾個redis集羣啊?若是有微博的朋友,留個言溜溜唄:)
三、總結
羅裏吧嗦說了這麼多,其實我就想表達一個意思,無論是前面的高可用,仍是今天的高併發。
代碼纔是關鍵,架構都是錦上添花的東西,既然是錦上添花的,必然坑多,沒有什麼捷徑。
代碼的健壯性決定了高可用,這些印度人就能作到,而高性能,高併發須要的不只僅是代碼的健壯性,還有數據結構的設計和代碼的調優能力了。架構模式是你們總結出來的,和你的系統可能關係不是很大,學習太多的架構,腦殼會亂,還不如實打實的看幾本書,而後對着架構多推敲練習,不少人對數據結構嗤之以鼻,以爲對於現有的開發來講,數據結構沒那麼重要了,但對於後端開發來講,數據結構是很重要的技能,雖然面試的時候不會讓你去翻轉一棵二叉樹,但二叉樹是什麼,什麼場景下用仍是應該知道的吧?
找準合適的數據結構,不斷的優化代碼,這樣來提高你的系統性能,這樣的系統纔是你可控的,纔有不斷優化的空間,更好的高併發,若是一開始就上外部的緩存技術,極可能若是性能達不到要求,就沒有優化空間了,由於要修改外部的系統仍是很困難的。
時間和空間的平衡
最後咱們來講說架構中時間和空間的平衡吧,這裏的時間指代比較廣,多是開發時間,但大部分指的是執行時間,也就是算法的時間複雜度了,而空間就是算法中常常說的空間換時間中的空間了,一個好的系統,設計出來必然是各類時間複雜度和空間複雜度平衡出來的結果,架構設計的過程,並不只僅是模塊的堆疊,在走到岔路口的時候,更多的是時間和空間平衡以後選的一個技術方案,這一篇,我會用一個搜索提示服務設計的實際例子,來講一下架構設計的過程當中,時間和空間的各類矛盾,怎麼分析,怎麼選擇,最後淌過這些時空的坑。
1. 搜索提示是什麼
搜索提示是搜索引擎的重要組成部分,雖然通常是做爲一個單獨的服務來對外提供服務,但在一個搜索系統中,搜索提示是很是重要的組成部分,我還沒看到哪一個比較成熟的搜索引擎沒有搜索提示功能的。首先,咱們看看搜索提示是什麼,你們確定都用過,就是下面這些個東西:
2. 搜索提示的場景和目的
搜索提示通常狀況下是爲了提升用戶的搜索體驗,更快的選擇合適的搜索詞,提升檢索的效率的,可是由於搜索框的流量實在是太大了,因此搜索提示也扮演着廣告變現的責任,互聯網嘛,有流量就有變現,好比下面這個圖,明顯就是一個廣告啦。
3. 初步技術選型
1搜索提示的需求
要實現一個搜索提示系統,首先須要肯定的是須要提示出來什麼東西,有兩種提示方式。
一種是提示出其餘的搜索詞,這也是大部分的搜索提示所作的,提示出其餘用戶的相似搜索詞。
還有一種是提示出現有的結果集有的東西,這種實現方式比較少見,好比一個生鮮類的電商網站,商品數量比較少,那麼不必去提示一些用戶的搜索詞,直接把商品名稱(好比蘋果,桃子,橘子)提示出來就好了,這種提示方式咱們這裏不討論,由於實現起來比較簡單。
2技術棧
既然知道需求了,那麼開始選擇技術棧了。
首先,既然有其餘用戶的搜索詞,那麼必然有一個離線的數據收集和處理的系統來完成其餘用戶的搜索日誌處理,生成須要的數據。
其次,須要一個單獨的API服務,來提供搜索提示的功能,輸入爲不完整的搜索詞,輸出爲根據這個搜索詞提示出來的其餘搜索詞,檢索方式的話,通常都是使用前綴匹配的方式了,這個你們都比較承認。
最後,須要前端有個js代碼來實時調用後臺的API,這個不在咱們的討論範圍內。
整個系統的結構圖應該是下面這個樣子,離線模塊處理完日誌數據之後,推送到API模塊中,給前面的前端提供服務。
好了,框框設計好了。也就是架構圖完成了哦,真是牛逼的架構啊,三個框,離線,在線,前端全齊了。接下來,咱們來看看在線API部分的設計吧,咱們先假設離線數據都已經準備好了,就是一堆用戶的搜索詞,如何快速的前綴匹配這些詞就成了API設計部分的關鍵了,有這麼幾種實現方式。
粗暴的短平快方式
用redis保存全部信息,每條信息相似
{KEY:北 VALUE:北京,北京大學,北大,北京趕上西雅圖}
{KEY:北京 VALUE:北京,北京大學,北京趕上西雅圖}....
每次來了請求的話,直接查詢redis給出結果返回,就是佔點空間,最好還須要一臺單獨的服務器。
優雅點的實現方式
前綴匹配嘛,最早想到的數據結構就是Trie樹了,因此全部的Key能夠用Trie樹來保存和檢索,速度也挺快的,並且空間佔用比較少。
複雜點的實現方式
既然是檢索嘛,就直接用搜索引擎的倒排索引技術來實現嘛,速度也夠,並且數據量也能夠支持得很大。
4. 時間與空間的平衡一
實際工程應用中,這三種實現方式我都見過,並且有些實現方式是把這三種結合起來使用了,後面的文章我會說到。具體使用哪種須要看你的實際場景,這三種實現方式差很少正好對應三種場景。
若是你是個小型的電商或者論壇之類的,天天的搜索量也不是很大,並且在可見的將來也不會變得很大,並且也不差錢,那麼直接第一種,說不定一天就能擼出來,速度還不錯,可是這種有一些缺陷,首先,value值不能太複雜,影響效率,因此可擴展性不是很強,而如今的電商搜索提示中每每還有不少其餘信息須要保存,redis做爲緩存服務器提供高併發服務的前提是數據量比較小,最好在2K之內,這樣的話用redis就有點不合適了。這種方案是個存空間的選擇了,用空間換取了檢索時間和開發時間,多虧有redis這種神器。
若是是個大型的搜索引擎或者電商,搜索日誌已是巨量了,並且搜索詞多種多樣,那麼第三種倒排索引技術爲基礎的實現方式多是更好的選擇,並且既然是大搜,技術都是現成的,索引分片,集羣都是現成的,直接改了上就是。這種方式用長期的開發時間和檢索速度上稍微的下降換取了內存空間,若是從頭開始作的話,時間成本比較高。
大部分時候,第二種實現方式是你們都採用的方式,首先沒有第一種那麼粗暴,而且能完成方案一的因此功能,單機就能達到較好的效果,也不用索引分片,也不用集羣,因此工程複雜性不是很高,也能在較短的時間內實現出來。其次第二種方案可擴展性較強,後面掛個倒排文件就能夠變成簡化版的第三方案。這種方式用算法換取了內存空間,用O(n)替代了O(1),換取了內存空間,也是標準的計算機領域的時間換空間了。
經過一番分析下來,決定使用第二種實現方式,就是Trie樹的方式了,好了,API的基本選型肯定了,那麼開始設計,準備寫代碼吧。
5. Trie樹的多種結構
既然肯定了Trie樹的實現方式,那麼首先要了解一下Trie樹吧,以及Trie樹的各類結構,看看具體用哪一個吧。
1基本Trie樹
Trie樹又叫字典樹,本質上是一個多叉樹,每個節點就是一個多叉的結構,若是是英文的匹配,那麼是一個26叉樹,每一個節點一個26長度的數組,每一個節點的數據結構以下:
而Trie樹畫出來就是下面這個樣子。
從畫出來的圖,很直觀的能夠看出來這棵樹的構造方法和遍歷方法,若是是純英文的話,每一個節點都有一個26長度的數組,來了一個字符,經過字符的編號直接就能夠遍歷到下一個節點,查找的時候複雜度就是O(K),K表示查找的字符串長度,這種數據結構簡單明瞭,實現起來也很容易。
2優化後的Trie樹
基本Trie樹的數據結構有個問題,就是內存使用得太多了,若是是中文查找的話,須要把全部的中國字都編號到這個數組中,內存就爆了,因而有一種優化方法,就是把數組變成變長的,這種Trie樹的節點數據結構變成下面的樣子了,節點查找變成一個順序查找或者二分查找了。
3雙數組Trie樹
所謂雙數組Trie樹,固然就是經過兩個數組來實現這棵樹了,這兩個數組分別叫base數組和check數組,一個是基礎數組,一個是檢查數組。
Trie樹其實是一種有限狀態機,經過狀態轉移矩陣在各個狀態之間跳轉,雙數組Trie樹極大的節省了空間,大體就是下面這個樣子,我後面會有一篇專門的文章來講Trie樹實現的,這裏就不詳細展開了,實在等不及的能夠本身先搜索一下相關資料看看雙數組Trie樹吧。
6. 時間與空間的平衡二
OK,三種Trie樹的實現方式都說了,如今要開始抉擇了,咱們先看看這三種數據結構的時間和空間。
第一種空間佔用大,特別是中文的狀況,檢索的時間效率爲O(n),其中n爲每次請求的字符串的長度,這種實現方式基本上屬於新人練手的水平,純粹爲了瞭解這個數據結構或者大學生作作課程設計,工程化的可能性幾乎爲0。
第二種空間基本不浪費,但檢索的時間效率若是按照二分進行每一個節點的查找的話,每一個節點的查找時間變成了O(lg(n)),總體的查找時間變成K*O(log(n)),一樣插入效率也變低了。
第三種狀況空間不浪費,時間效率也爲O(n)。
初看,確定選第三種了,可是!!第三種實現方式有個致命的缺陷,就是沒法向下遍歷(具體能夠本身看看雙數組的實現方式),也就是說我輸入北京,找不到北京大學,北京愛上西雅圖,由於它已經不是一個樹型結構了,沒法向下遍歷了。因此若是不對第三種結構進行改造的話,是沒法知足咱們的功能的。要改造,最簡單的辦法就是在每一個詞後面掛一個鏈表,表示這個詞的後繼詞都是什麼,像下圖這樣。
若是按上圖那麼來的話,須要輔助的空間來存儲後繼詞,那麼問題又來了,又是一次時間和空間的抉擇了,是選擇K*O(log(n))的第二種方案,而後後繼詞實時遍歷樹來獲取(又要耗費必定的時間),仍是選擇選擇第三種方案,用空間換取時間呢?
好,既然這樣,咱們來仔細算算這個帳,咱們以每一個節點都存一箇中文來算,雖然經常使用的漢字大概2500個,但其中最經常使用的才500左右。先看第二種方案,那麼咱們大概估算出,每一個節點的平均數組長度大概600(實際上除了第一層的節點,後面的節點數組長度徹底達不到這個量級,用600屬於極限估算了),600的二分查找大約須要7到8次,取個平均值4次,那麼每次查詢的時間就是4*K(K是字符串的長度),若是咱們定好最長的提示詞不超過8個字(太長也沒意義),那麼首先這個樹的高度就是8了,若是50萬的詞量的話,使用多少內存大概能算出來,而後每次遍歷下級節點的時間就是600^(8-K)(若是數組的每一個元素都有值),我去,這麼大,嚇死了,好,咱們即使假設每一個節點的數組長度平均爲60,要遍歷完也要60^(8-K),也嚇尿了,因此實時遍歷全部子節點的方式不可取,並且後繼詞最多也就提示出10個,遍歷出這麼多詞還要排序,遍歷所有節點實在是沒有必要,因此,第二種方案要麼放棄,要麼也要改造,如何改造呢?
由於詞基本上都是離線算好的,稍微把節點的數據結構優化一下,在節點中加一個字段,表示哪一個子節點有須要的數據(排序前10的詞),這樣往下遍歷的時候就直接遍歷相應的下標就能夠了,就能把60^(8-K)這種遍歷減小到幾十次,從而找到10個提示詞,咱們把這個結構叫二次優化的Trie樹。
這一輪的時間和空間的比拼,第三個方案感受就要勝利了,但第二個方案的優化版貌似也還能接受,一個耗費空間,查詢速度快,一個節省空間,查詢速度慢點。
這裏多說一下,其實上面只是預估的辦法比較搓,這麼寫是爲了說預估的技能,最直接的就是拿着日誌統計一遍,獲得一堆不超過8位長度的搜索詞,同時也能算法兩個方案的內存使用規模和大概的查找效率,這樣的預估辦法最準確,可是在大部分時候咱們並無這麼多數據,因此只能作一些基本的預估。
7. 離線數據處理
好了,咱們先把檢索部分放一放,來看看離線數據處理部分吧。咱們先要肯定一下什麼東西須要在離線部分算好,什麼東西須要在線處理?
首先,日誌的清洗確定是離線部分了,咱們先要把沒有搜索結果的詞去掉,而後去掉太長的詞(假定超過8的都不要),而後保留有必定熱度的詞(好比天天搜索量超過10次的詞),等等一些規則之後,假如剩下了50萬的詞,那這50萬就是咱們的基礎數據了。
其次,Trie樹的構建是離線構建好仍是實時往服務推送由服務端去構建呢?
還有,排序的時候是離線給每一個搜索詞打個分,而後實時排序呢?仍是離線把序都排好,服務端直接使用結果呢?
8. 時間與空間的平衡三
雖然是離線處理,但同樣有時間和空間的選擇。咱們先來看構建部分,Trie樹的構建是離線構建好仍是實時往服務推送由服務端去構建,首先咱們須要肯定的是這個搜索提示服務需不須要實時更新,通常狀況下,搜索提示沒有那麼強的實時性要求,通常一天或者兩天更新一次體驗也不會太差,因此作實時更新的搜索提示,要不就是你實在是太蛋疼了,要不就是遇到了一個特別讓人蛋疼的產品經理(臥槽,黑了一下產品經理啊)。因此咱們使用離線構建的方式構建好兩個數組和輔助的數據結構,都存在磁盤上,服務端啓動的時候讀取文件就好了,這是用離線時間換取的服務端的時間,是很划得來的。
再來看看排序的部分,很明顯,排序離線作好也比較合適,排序的位置基本不會有太大的變化,可是若是排序離線作好的話,那麼輔助的數據結構就會比較大了,由於每一個前綴後面跟着的10個詞都要排好序放在輔助結構中,但若是咱們只是把每一個詞打個分(好比就按熱度給個分),而後用第二個方案(優化的Trie樹)的存儲方式,在線的時候去排序,那麼輔助結構就會小不少,兩種狀況的結構大概就是下面這樣的區別。
左邊的是全排序好了的,直接使用,雙數組Trie樹+輔助結構方式;右邊的是隻是打了分的,優化的Trie樹,遍歷出結果之後實時排序的。離線排序的空間佔用大,即使優化一下,把詞都放一個地方單獨存着,輔助結構中只保存詞的編號,同樣也比較佔地方,可是查詢速度快啊。在線排序的方式不怎麼佔地方,就是每一個節點多了一個分數的字段,須要實時排序一下,雖然是實時排序,但個數就10個,無論是快排仍是堆排,都很快的,因此時間效率也慢不到哪去。
9. 總體的時空平衡
綜合衡量一看,我我的以爲兩種方式都能接受,具體選哪個就仁者見仁了。
若是搜索詞的量比較穩定,不會有太大的變化,那麼使用雙數組Trie樹+輔助數據結構+離線構建Trie樹+離線排序的方式更合適。
若是搜索詞雖然如今是50萬,但極可能會增長得比較多,或者像下圖同樣,搜索提示的頁面還會承載不少其餘的數據的話,那麼使用二次優化的Trie樹+離線構建Trie樹+離線打分+實時排序的實現方式更合適,由於能節省更多的內存給後續擴充詞語用或者給其餘數據用。
還有若是對速度要求苛刻,那麼就第一種,若是沒那麼苛刻,那就第二種。
架構設計沒有好壞,只有合適不合適。
10. 總結
上面分析了這麼一大堆,淌過三個的時間與空間的坑,終於基本肯定了技術方案了,這其實也是系統架構設計中常常會要遇到的選擇了,架構師們把這些選擇作完之後,能夠開始細分模塊設計開發了,因此,一個小小的系統就這麼多選擇,各類空間和時間的平衡,你說架構師哪那麼好當?呵呵,你覺得就畫完這篇文章的第一圖就架構結束了啊。
這裏只是用搜索提示做爲一個例子來講明系統設計的時候須要時時刻刻關注時間和空間這兩個因素的平衡,如今不少人設計系統的時候基本上不太關注時間,由於高配的服務器,幾十上百GB的內存隨便用,因此大多數都把設計往空間上去靠,用更多的空間來換取執行效率,這自己並無什麼問題,誰不但願更快啊,可是有時候預估一下,有可能雖然犧牲了一點時間效率,可是換來了很多的空間,這樣的系統在數據量變大時有更多的可擴展空間,我以爲是很是值得的交換。
再有,對數據結構和算法的瞭解以及預估算能力實際上是平衡時間和空間的重要技能,也是架構設計中避坑的基本技能,因此有公司的面試題會出現請你估算一下黃河出海口的面積這類估算題,由於預估算能力也是重要的架構技能吧。
11. 更深刻一下
上面只是這個系統的一小部分,搜索提示須要作的遠不止如此,想一想下面幾個場景,若是是你,你要如何設計呢?如何平衡時間和空間呢?歡迎討論哈。
須要拼音支持,就像這樣
須要拼音首字母支持
某些搜索提示須要更加詳細的信息
須要對每一個用戶的搜索歷史進行搜索提示【這個比較難點】
這個坑系列算是結束了,如今我正在作一些推薦廣告相關的工做,後續也會分享一些相關的東西給你們,還有分詞、相關搜索、分佈式的東西會依次出來哦。