1. 程式人生 > >大型網站應用之海量資料和高併發解決方案總結一二

大型網站應用之海量資料和高併發解決方案總結一二

一、網站應用背景

開發一個網站的應用程式,當用戶規模比較小的時候,使用簡單的:一臺應用伺服器+一臺資料庫伺服器+一臺檔案伺服器,這樣的話完全可以解決一部分問題,也可以通過堆硬體的方式來提高網站應用的訪問效能,當然,也要考慮成本的問題。

當問題的規模在經濟條件下通過堆硬體的方式解決不了的時候,我們應該通過其他的思路去解決問題,網際網路發展至今,已經提供了很多成熟的解決方案,但並不是都具有適用性,你把淘寶的技術全部都搬過來也不一定達到現在淘寶的水平,道理很簡單。

當然,很多文章都在強調,一個網站的發展水平,是逐漸的演變過來的,並不是一朝一夕的事情。雖然目前的情況網際網路的泡沫越來越大,但是整個網際網路技術的發展確實為我們提供了方便快捷的上網體驗。下邊是一張早期的淘寶官網的介面:

早期淘寶網站介面

目前,博主正在跟隨導師做一個創業專案,使用的技術是SSM+MySQL+Linux這些,但是由於資金的限制和考慮到使用者群體的特殊性,系統的架構無奈的選擇的就是最簡單的方式:一臺應用伺服器、一臺資料庫伺服器、一臺檔案系統伺服器,沒有用到高階的技術,也沒有用到分散式部署的方案。下邊整理的是一些針對海量資料和高併發情況下的解決方案,技術水平有限,歡迎留言指導。

二、針對海量資料和高併發的主要解決方案

海量資料的解決方案:

  1. 使用快取;
  2. 頁面靜態化技術;
  3. 資料庫優化;
  4. 分離資料庫中活躍的資料;
  5. 批量讀取和延遲修改;
  6. 讀寫分離;
  7. 使用NoSQL和Hadoop等技術;
  8. 分散式部署資料庫;
  9. 應用服務和資料服務分離;
  10. 使用搜索引擎搜尋資料庫中的資料;
  11. 進行業務的拆分;

高併發情況下的解決方案:

  1. 應用程式和靜態資原始檔進行分離;
  2. 頁面快取;
  3. 叢集與分散式;
  4. 反向代理;
  5. CDN;

三、海量資料的解決方案

(1)使用快取

網站訪問資料的特點大多數呈現為“二八定律”:80%的業務訪問集中在20%的資料上。

例如:在某一段時間內百度的搜尋熱詞可能集中在少部分的熱門詞彙上;新浪微博某一時期也可能大家廣泛關注的主題也是少部分事件。

總的來說就是使用者只用到了總資料條目的一小部分,當網站發展到一定規模,資料庫IO操作成為效能瓶頸的時候,使用快取將這一小部分的熱門資料快取在記憶體中是一個很不錯的選擇,不但可以減輕資料庫的壓力,還可以提高整體網站的資料訪問速度。

使用快取的方式可以通過程式程式碼將資料直接儲存到記憶體中,例如通過使用Map或者ConcurrentHashMap;另一種,就是使用快取框架:Redis、Ehcache、Memcache等。
這裡寫圖片描述
使用快取框架的時候,我們需要關心的就是什麼時候建立快取快取失效策略

快取的建立可以通過很多的方式進行建立,具體也需要根據自己的業務進行選擇。例如,新聞首頁的新聞應該在第一次讀取資料的時候就進行快取;對於點選率比較高的文章,可以將其文章內容進行快取等。

記憶體資源有限,選擇如何建立快取是一個值得思考的問題。另外,對於快取的失效機制也是需要好好研究的,可以通過設定失效時間的方式進行設定;也可以通過對熱門資料設定優先順序,根據不同的優先順序設定不同的失效時間等;

需要注意的是,當我們刪除一條資料的時候,我們要考慮到刪除該條快取,還要考慮在刪除該條快取之前該條資料是否已經到達快取失效時間等各種情況!

使用快取的時候還要考慮到快取伺服器發生故障時候如何進行容錯處理,是使用N多臺伺服器快取相同的資料,通過分散式部署的方式對快取資料進行控制,當一臺發生故障的時候自動切換到其他的機器上去;還是通過Hash一致性的方式,等待快取伺服器恢復正常使用的時候重新指定到該快取伺服器。Hash一致性的另一個作用就是在分散式快取伺服器下對資料進行定位,將資料分佈在不用快取伺服器上。關於資料快取的Hash一致性也是一個比較打的問題,這裡只能大致描述一下,關於Hash一致性的瞭解,推薦一篇文章:http://blog.csdn.net/liu765023051/article/details/49408099

(2)頁面靜態化技術

使用傳統的JSP介面,前端介面的顯示是通過後臺伺服器進行渲染後返回給前端遊覽器進行解析執行,如下圖:
這裡寫圖片描述

當然,現在提倡前後端分離,前端介面基本都是HTML網頁程式碼,通過Angular JS或者NodeJS提供的路由向後端伺服器發出請求獲取資料,然後在遊覽器對資料進行渲染,這樣在很大程度上降低了後端伺服器的壓力。

還可以將這些靜態的HTML、CSS、JS、圖片資源等放置在快取伺服器上或者CDN伺服器上,一般使用最多的應該是CDN伺服器或者Nginx伺服器提供的靜態資源功能。

另外,在《高效能網站建設進階指南-Web開發者效能優化最佳實踐(口碑網前端團隊 翻譯)》這本書中,對網站效能的前端介面提供了一些很寶貴的經驗,如下:

這裡寫圖片描述

因此,在這些靜態資源的處理上,選擇正確的處理方式還是對整體網站效能還是有很大幫助的!

(3)資料庫優化

資料庫優化是整個網站效能優化的最基礎的一個環節,因為,大多數網站效能的瓶頸都是開在資料庫IO操作上,雖然提供了快取技術,但是對資料庫的優化還是一個需要認真的對待。一般公司都有自己的DBA團隊,負責資料庫的建立,資料模型的確立等問題,不像我們現在幾個不懂資料庫優化的人只能在網上找一篇篇資料庫優化的文章,自己去摸索,並沒有形成一個系統的資料庫優化思路。

對於資料庫的優化來說,是一種用技術換金錢的方式。資料庫優化的方式很多,常見的可以分為:資料庫表結構優化、SQL語句優化、分割槽、分表、索引優化、使用儲存過程代替直接操作等 。

1、表結構優化

另外,再設計資料庫表的時候需不需要建立外來鍵,使用外來鍵的好處之一可以方便的進行級聯刪除操作,但是現在在進行資料業務操作的時候,我們都通過事物的方式來保證資料讀取操作的一致性,我感覺相比於使用外來鍵關聯MySQL自動幫我們完成級聯刪除的操作來說,還是自己使用事物進行刪除操作來的更放心一些。當然可能也是有適用的場景,大家如有很好的建議,歡迎留言!

2、SQL優化

對於SQL的優化,主要是針對SQL語句處理邏輯的優化,而且還要根據索引進行配合使用。另外,對於SQL語句的優化我們可以針對具體的業務方法進行優化,我們可以將執行業務邏輯操作的資料庫執行時間記錄下來,來進行有針對性的優化,這樣的話效果還是很不錯的!例如下圖,展示了一條資料庫操作執行呼叫的時間:

這裡寫圖片描述

關於SQL優化的一些建議,以前整理了一些,還請移步檢視:

分表

分表是將一個大表按照一定的規則分解成多張具有獨立儲存空間的實體表,我們可以稱為子表,每個表都對應三個檔案,MYD資料檔案,.MYI索引檔案,.frm表結構檔案。這些子表可以分佈在同一塊磁碟上,也可以在不同的機器上。資料庫讀寫操作的時候根據事先定義好的規則得到對應的子表名,然後去操作它。

例如:使用者表
使用者的角色有很多種,可以通過列舉型別的方式將使用者分為不同類別category:學生、教師、企業等 ,這樣的話,我們就可以根據類別category來對資料庫進行分表,這樣的話每次查詢的時候現根據使用者的型別鎖定一個較小的範圍。

不過分表之後,如果需要查詢完整的順序就需要使用多表操作了。

分割槽

資料庫分割槽是一種物理資料庫設計技術,DBA和資料庫建模人員對其相當熟悉。雖然分割槽技術可以實現很多效果,但其主要目的是為了在特定的SQL操作中減少資料讀寫的總量以縮減響應時間。

分割槽和分表相似,都是按照規則分解表。不同在於分表將大表分解為若干個獨立的實體表,而分割槽是將資料分段劃分在多個位置存放,可以是同一塊磁碟也可以在不同的機器。分割槽後,表面上還是一張表,但資料雜湊到多個位置了。資料庫讀寫操作的時候操作的還是大表名字,DMS自動去組織分割槽的資料。

當一張表中的資料變得很大的時候,讀取資料,查詢資料的效率非常低下,很容易的就是講資料分到不同的資料表中進行儲存,但是這樣分表之後會使得操作起來比較麻煩,因為,將同類的資料分別放在不同的表中的話,在搜尋資料的時候需要便利查詢這些表中的資料。想進行CRUD操作還需要先找到對應的所有表,如果涉及到不同的表的話還要進行跨表操作,這樣操作起來還是很麻煩的。

使用分割槽的方式可以解決這個問題,分割槽是將一張表中的資料按照一定的規則分到不同的區中進行儲存,這樣進行資料查詢的時候如果資料的範圍在同一個區域內那麼就可以支隊一個區中的資料進行操作,這樣的話操作起來資料量更少,操作速度更快,而且該方法是對程式透明的,程式不需要進行任何的修改。

索引優化

索引的大致原理是在資料發生變化的時候就預先按指定欄位的順序排列後儲存到一個類似表的結構中,這樣在查詢索引欄位為條件記錄時就可以很快地從索引中找到對應記錄的指標並從表中獲取到相應的資料,這樣速度是很快地。

不過,雖然查詢的效率大大提高了,但是在進行增刪改的時候,因為資料的變化都需要更新相應的索引,也是一種資源的浪費。

關於使用索引的問題,對待不同的問題,還是需要進行不同的討論,根據具體的業務需求選擇合適的索引對效能的提高效果是很明顯的一個舉措!

推薦文章閱讀:

使用儲存過程代替直接操作

儲存過程(Stored Procedure)是在大型資料庫系統中,一組為了完成特定功能的SQL 語句集,儲存在資料庫中,經過第一次編譯後再次呼叫不需要再次編譯,使用者通過指定儲存過程的名字並給出引數(如果該儲存過程帶有引數)來執行它。儲存過程是資料庫中的一個重要物件,任何一個設計良好的資料庫應用程式都應該用到儲存過程。

在操作過程比較複雜並且呼叫頻率比較高的業務中,可以將編寫好的sql語句用儲存過程的方式來代替,使用儲存過程只需要進行一次變異,而且可以在一個儲存過程裡做一些複雜的操作。

(4)分離資料庫中活躍的資料

正如前邊提到的“二八定律”一樣,網站的資料雖然很多,但是經常被訪問的資料還是有限的,因此可以講這些相對活躍的資料進行分離出來單獨進行儲存來提高處理效率。

其實前邊使用快取的思想就是一個很明顯的分離資料庫中活躍的資料的使用案例,將熱門資料快取在記憶體中。

還有一種場景就是,例如一個網站的所用註冊使用者量很大千萬級別,但是經常登入的使用者只有百萬級別,剩下的基本都是很長時間都沒有進行登入操作,如果不把這些“殭屍使用者”單獨分離出去,那麼我們每次查詢其他登入使用者的時候,就白白浪費了這些殭屍使用者的查詢操作。

(5)批量讀取和延遲修改

批量讀取和延遲修改的原理是通過減少操作資料庫的操作來提高效率。

批量讀取是將多次查詢合併到一次中進行讀取,因為每一個數據庫的請求操作都需要連結的建立和連結的釋放,還是佔用一部分資源的,批量讀取可以通過非同步的方式進行讀取。

延遲修改是對於一些高併發的並且修改頻繁修改的資料,在每次修改的時候首先將資料儲存到快取中,然後定時將快取中的資料儲存到資料庫中,程式可以在讀取資料時可以同時讀取資料庫中和快取中的資料。

例如:我現在在編寫這份部落格,我一開始寫了一寫內容然後點擊發布了,在次回到Markdown進行修改操作,我有一個習慣,沒寫一些東西總是按一下上邊的 “儲存”按鈕,然後我在另一個頁面開啟這篇部落格檢視,我的修改已經被更新,但是我還在 編輯!

這裡寫圖片描述

不知道CSDN的技術是不是在我沒有點擊發布之前,這些資料都是先放到快取裡邊的。

(6) 讀寫分離

讀寫分離的實質是將應用程式對資料庫的讀寫操作分配到多個數據庫伺服器上,從而降低單臺數據庫的訪問壓力。

讀寫分離一般通過配置主從資料庫的方式,資料的讀取來自從庫,對資料庫增加修改刪除操作主庫。

這裡寫圖片描述

(7)使用NoSQL和Hadoop等技術

NoSQL是一種非結構化的非關係型資料庫,由於其靈活性,突破了關係型資料庫的條條框框,可以靈活的進行操作,另外,因為NoSQL通過多個塊儲存資料的特點,其操作大資料的速度也是相當快的。

(8)分散式部署資料庫

任何強大的單一伺服器都滿足不了大型網站持續增長的業務需求。資料庫通過讀寫分離之後將一臺資料庫伺服器拆分為兩臺或者多臺資料庫伺服器,但是仍然滿足不了持續增長的業務需求。分散式部署資料庫是將網站資料庫拆分的最後手段,只有在單表資料規模非常龐大的時候才使用。

分散式部署資料庫是一種很理想的情況,分散式資料庫是將表存放在不同的資料庫中然後再放到不同的資料庫中,這樣在處理請求的時候,如果需要呼叫多個表,則可以讓多臺伺服器同時處理,從而提高處理效率。

分散式資料庫簡單的架構圖如下:

這裡寫圖片描述

(9)應用服務和資料服務分離

應用伺服器和資料庫伺服器進行分離的目的是為了根據應用伺服器的特點和資料庫伺服器的特點進行底層的優化,這樣的話能夠更好的發揮每一臺伺服器的特性,資料庫伺服器當然是有一定的磁碟空間,而應用伺服器相對不需要太大的磁碟空間,這樣的話進行分離是有好處的,也能防止一臺伺服器出現問題連帶的其他服務也不可以使用。

這裡寫圖片描述

(10)使用搜索引擎搜尋資料庫中的資料

使用搜索引擎這種非資料庫查詢技術對網站應用的可伸縮分散式特性具有更好的支援。

常見的搜尋引擎如Solr通過一種反向索引的方式,維護關鍵字到文件的對映關係,類似於我們使用《新華字典》進行搜尋一個關鍵字,首先應該是看字典的目錄進行查詢然後定位到具體的位置。

搜尋引擎通過維護一定的關鍵字到文件的對映關係,能夠快速的定位到需要查詢的資料,相比於傳統的資料庫搜尋的方式,效率還是很高的。

目前一種比較火的ELK stack技術,還是值得學習的。

(11) 進行業務的拆分

為什麼進行業務的拆分,歸根結底上還是使用的還是講不通的業務資料表部署到不用的伺服器上,分別查詢對應的資料以滿足網站的需求。各個應用之間用過指定的URL連接獲取不同的服務,

例如一個大型的購物網站就會將首頁、商鋪、訂單、買家、賣家等拆分為不通的子業務,一方面將業務模組分歸為不同的團隊進行開發,另外一方面不同的業務使用的資料庫表部署到不通的伺服器上,體現到拆分的思想,當一個業務模組使用的資料庫伺服器發生故障也不會影響其他業務模組的資料庫正常使用。另外,當其中一個模組的訪問量激增的時候還可以動態的擴充套件這個模組使用到的資料庫的數量從而滿足業務的需求。

高併發情況下的解決方案

(1)應用程式和靜態資原始檔進行分離

所謂的靜態資源就是我們網站中用到的Html、Css、Js、Image、Video、Gif等靜態資源。應用程式和靜態資原始檔進行分離也是常見的前後端分離的解決方案,應用服務只提供相應的資料服務,靜態資源部署在指定的伺服器上(Nginx伺服器或者是CDN伺服器上),前端介面通過Angular JS或者Node JS提供的路由技術訪問應用伺服器的具體服務獲取相應的資料在前端遊覽器上進行渲染。這樣可以在很大程度上減輕後端伺服器的壓力。

例如,百度主頁使用的圖片就是單獨的一個域名伺服器上進行部署的

這裡寫圖片描述

(2)頁面快取

頁面快取是將應用生成的很少發生資料變化的頁面快取起來,這樣就不需要每次都重新生成頁面了,從而節省大量CPU資源,如果將快取的頁面放到記憶體中速度就更快。

可以使用Nginx提供的快取功能,或者可以使用專門的頁面快取伺服器Squid。

(3)叢集與分散式

(4)反向代理

(5)CDN

CDN伺服器其實是一種叢集頁面快取伺服器,其目的就是儘早的返回使用者所需要的資料,一方面加速使用者訪問速度,另一方面也減輕後端伺服器的負載壓力。

CDN的全稱是Content Delivery Network,即內容分發網路。其基本思路是儘可能避開網際網路上有可能影響資料傳輸速度和穩定性的瓶頸和環節,使內容傳輸的更快、更穩定。

CDN通過在網路各處放置節點伺服器所構成的在現有的網際網路基礎之上的一層智慧虛擬網路,CDN系統能夠實時地根據網路流量和各節點的連線、負載狀況以及到使用者的距離和響應時間等綜合資訊將使用者的請求重新導向離使用者最近的服務節點上。其目的是使使用者可就近取得所需內容,解決 Internet網路擁擠的狀況,提高使用者訪問網站的響應速度。

也就是說CDN伺服器是部署在網路執行商的機房,提供的離使用者最近的一層資料訪問服務,使用者在請求網站服務的時候,可以從距離使用者最近的網路提供商機房獲取資料。電信的使用者會分配電信的節點,聯通的會分配聯通的節點。

CDN分配請求的方式是特殊的,不是普通的負載均衡伺服器來分配的那種,而是用專門的CDN域名解析伺服器在解析與名的時候就分配好的。

CDN結構圖如下所示:

這裡寫圖片描述

總結

文章提到的幾點並沒有詳細的介紹,如需要對其中的一種方式進行研究,可以自行去找資源學習研究,這裡只起到拋磚引玉的作用。當然對於大型網站應用之海量資料和高併發解決方案不侷限於這些技巧或技術,還有很多成熟的解決方案,有需要的可以自行了解。

特此說明:本文的配圖來自《網站架構及其演變過程–韓路彪》,多謝原作者提供的精美配圖,並且文章的結構大致也參考了作者的思路,只不過內容是自己的一些經驗和學習到的東西進行整理的。

參考書籍或文章:

還有一些在整理的時候,忘記記錄連線,還請大家見諒!