緩存+HASH=高并發?你把高并發架構想得太簡單!
原創【51CTO.com原創稿件】在互聯網時代,高并發與高可用一樣,已經變成系統的標配了,如果系統每秒查詢率(QPS)沒有上萬,都不好意思跟人打招呼(雖然實際每天調用量不超過100)。尤其在雙十一期間,電商們憑借著藐視全球的流量,熱心地分享自己的技術架構,幾乎千篇一律地用緩存+哈希(HASH),仿佛這就是高并發的核心技術了。當然,如果你信了,那就離坑不遠了。
緩存+哈希=高并發?
所謂知己知彼百戰不殆,先來看看我們經常看到的高并發技術是什么。
資源靜態化 活動秒殺頁面是標準的高并發場景,活動期間單個頁面流量巨大無比,每秒QPS達到幾十萬上百萬。系統核心解決方案就是靜態化,靠機器和帶寬支撐。如果沒有部署CDN,所有流量都落到同一個IP,解決辦法就是用Nginx的文件靜態化,單機的承受能力主要取決于帶寬和單機的性能。如果流量再多,那就采用LVS(或者F5)+集群了。就中后臺而言,Nginx可以搞定大部分應用,核心還是使用了緩存技術。
現在各種緩存的工具如memcache,redis之類的KV數據庫作為緩存已經非常成熟,而且基本上都可以集群化部署,操作起來也很簡單,簡直成為高并發的代言詞。
讀寫分離和分庫分表 讀寫分離也是大家經常看到的高并發的架構,因為一般情況下讀比寫要多得多,所以數據庫的主庫寫,從庫們提供讀操作,一下就把數據庫的并發性能提高了。如果還不夠,那么分庫分表把,把數據分到各個數據庫的各個機器上,進一步的減少單臺機器的壓力,從而達到高并發的目的。
如果是分庫分表,有時候使用的就是哈希技術了,以某個字段哈希一下然后來分庫分表,讀寫分離的讀操作,基本也是通過哈希技術把讀落到不同的機器上去減輕單機壓力。但凡大數據處理,高并發系統,必言哈希,隨機插入,時間復雜度O(1),隨便查詢,時間復雜度O(1),除了耗費點空間以外,幾乎零缺點,在現在這個內存廉價的時代,哈希表變成了一個高并發系統的標配。
當電商促銷活動時,幾千萬人同時訪問網站還沒有掛,讓很多人覺得高并發應該就是這樣子。這樣的場景再擴展一點,就是凡是能提前提供數據的并發訪問,就可以用緩存+哈希來搞定并發。而像12306網站這樣,不能提前提供數據的,可以通過緩存+哈希來提高用戶體驗,然后通過異步方式來提供服務(被吐槽的驗證碼其實就是異步的排隊方式)。
實際上是不是這樣呢?顯然沒那么簡單。讓我們來看看一個高并發的系統真正需要考慮的元素。
合理的數據結構
搜索提示功能大家都非常熟悉,如果是google,baidu這種大型搜索系統,或者京東淘寶這種電商系統,搜索提示的調用量是搜索服務本身調用量的好幾倍,因為你每輸入一個鍵盤,就要調用一次搜索提示服務,這算得上是個標準的高并發系統吧?那么它是怎么實現的呢?
可能很多人腦子里立刻出現了緩存+哈希的系統,把搜索的搜索提示詞存在redis集群中,每次來了請求直接redis集群中查找key,然后返回相應的value值就行了。雖然耗費點內存,但是空間換時間嘛,也能接受。然而事實上沒有人會這么做。
這種搜索提示的功能一般用trie樹來做,耗費的內存不多,查找速度為O(k),其中k為字符串的長度,雖然看上去沒有哈希表的O(1)好,但是少了網絡開銷,節約了很多內存,并且實際查找時間還要不比緩存+哈希慢多少,一種合適當前場景的核心數據結構才是高并發系統的關鍵,緩存+哈希如果也看成一種數據結構,但這種數據結構并不適用于所有的高并發場景,所以高并發系統的設計,關鍵在合理的數據結構的設計,而不在架構的套用。
不斷優化代碼性能
有了上面的數據結構,并且設計出了系統了,拿到線上一跑,效果還行,但感覺沒達到極限,這時候可千萬不能就直接上外部工具(比如緩存)提升性能,需要做的是不斷的代碼性能的優化,簡單的說,就是不斷的重申你的代碼,不斷找出可以優化的性能點,然后進行優化,因為之前設計的時候就已經通過理論大概能算出來這個系統的并發量了,比如上面那個搜索提示,如果我們假定平均每個搜索詞6個字符,檢索一次大約需要查詢6次,需要2-3毫秒,這樣的話,如果8核的機器,多線程編程方式,一秒鐘最多能接受3200次請求(1000ms/2.5ms*8),如果沒達到這個量級,那么肯定是代碼有問題。
這個階段可能需要借助一些個工具了,Golang自帶的go tool pprof就能很好的進行性能優化。
或者把各個模塊的時間打印出來,壓測一遍,看看哪個模塊耗時,然后再去仔細檢查那個模塊的代碼,進行算法和數據結構的優化。
這個過程是一個長期的過程,也是《重構:改善代碼的既有設計》中提到的,一個優秀的系統需要不斷的進行代碼級別的優化和重構,所以高并發系統的實現,就是不斷的優化代碼的性能,不斷逼近設計時的理論值。
***考慮外部通用方法
如果以上兩個都完成了,并發量也基本達到理論值了,但是還有提升的需求,這時候再來考慮外部的通用方法,比如加一個LRU緩存,把熱詞的查詢時間變成O(1),進一步提高性能。在沒把系統的性能壓榨完全之前,不要使用外部的通用方法,因為使用了以后就沒有太多進一步優化空間了。
此外還需要再考慮運維的技術,如常規的加負載均衡,部署成集群,通過運維和部署的方法提高服務的并發性。
51CTO觀點
其實代碼才是高可用的關鍵,代碼的健壯性決定了高可用。除此之外,還需要看重數據結構的設計和代碼的調優能力。很多人對數據結構嗤之以鼻,覺得對于現有的開發來說,數據結構沒那么重要,但對于后端開發來說,數據結構是很重要的技能。
找準合適的數據結構,不斷的優化代碼,這樣來提升系統性能才是可控的,才有不斷優化的空間,更好的高并發,如果一開始就上外部的緩存技術,很可能如果性能達不到要求,就沒有優化空間了,因為要修改外部的系統還很困難。
【51CTO原創稿件,合作站點轉載請注明原文作者和出處為51CTO.com】
【編輯推薦】
硅材料要哭了!新寵GaN讓數據中心能源效率翻倍,成本還打5折