1. 程式人生 > 其它 >架構設計--網際網路架構演化(轉發)

架構設計--網際網路架構演化(轉發)

  • 轉發
  • 微博
  • Qzone
  • 微信
架構設計--網際網路架構演化 原創後端技術分享2019-09-02 18:01:00 對於一個大型網站,主要有以下幾個特徵:
  1. 支撐海量資料
  2. 非常高的訪問量
我們常見的大型網站,如百度、淘寶、京東等,都是一個分散式系統。這麼複雜的系統也不是一天建成的,每個系統都經歷了漫長的演變過程。 架構演變 在大型網站中,其最核心的功能就是計算和儲存。因此係統演變過程也主要圍繞這兩點進行。 1 單機系統 在網站剛剛起步時,資料量、訪問量都非常小,通常情況下,只需一臺應用伺服器就可以了。 1.1 單機部署方案 起步時,我們把所有資源全部打包到部署檔案中(如 XXX.war),其中包括
  1. class 檔案、依賴 jar等;
  2. js、css、圖片等靜態資源;
  3. 對於使用者上傳檔案的場景,直接在伺服器上新建一個目錄,將上傳的檔案放置在目錄即可。
然後,將打好的釋出包放到 Web 容器中,比如 Tomcat,最後啟動容器,讓其直接對外提供服務。 該部署策略有以下幾個特徵:
  1. 使用者通過瀏覽器直接與 Java 應用程式進行互動(通常是 Tomcat);
  2. Java 應用程式通過 JDBC 與本機的資料庫進行互動(如 MySQL);
  3. 如果存在檔案讀寫的需求,Java 應用程式通過檔案介面直接對檔案進行操作。
這時,有人會問,Java 應用程式直接對外,會不會存在一些安全或效能方面的問題呢? 是的,Tomcat 這種 Web 容器對連結的保持能力比較弱,當存在大量連結時,效能下降很快。同時,Tomcat 並不擅長靜態資源的處理,對此,我們可以引入 Nginx,以緩解 Tomcat 的壓力。 1.2 單機部署方案進階 我們在單機部署基礎上,新增 Nginx,也就有了進階方案。 該方案存在以下特徵:
  1. 使用者不在直接與 Java 應用程式進行互動,而是與 Nginx 進行互動;
  2. Tomcat 掛在 Nginx 後,對動態請求進行處理;
  3. 對於靜態資源的訪問,通過 Nginx 直接訪問檔案系統;
  4. 當有檔案寫需求時,通過 Java 應用程式直接寫入磁碟。
此時,架構顯得清晰很多,但我們發現一個問題,就是系統對靜態資源和動態資源的處理是完全不同的。 對於靜態資源的處理,相對簡單,只是簡單的檔案讀寫。而,動態請求(也就是我們的業務承載者)會隨著業務的發展越來越複雜。 2 動靜分離部署方案 由於靜態請求與動態請求採用不同的處理策略,我們可以將其進行分離。 該部署方案存在以下特性:
  1. 通過不同的域名對動態請求和靜態請求進行分離;
  2. 新增靜態資源伺服器,專門處理靜態請求,並在伺服器上部署Java 應用程式,處理檔案寫需求;Nginx只負責檔案的讀操作;
  3. 對動態請求進行獨立部署,應用程式將檔案的寫請求轉發到靜態伺服器進行處理;
靜態資源伺服器功能單一,部署繁瑣,有沒有一種更好的策略呢? 答案就是雲服務,比如阿里雲的 OSS 提供靜態資源儲存服務。CDN 提供訪問加速服務,兩者結合使用,就得到了一個海量容量並且效能超強的靜態資源伺服器(叢集)。 結合 OSS 和 CDN,靜態請求不會成為系統的瓶頸,因此,接下來只對動態請求進行討論。 隨著系統訪問量的增加,動態請求出現了明顯的瓶頸。 3 應用叢集化部署 由於所有的動態請求全部由一臺應用伺服器進行處理,當訪問量上升時,這臺服務就成了系統的瓶頸。此時,我們需要將系統中的多個元件部署到不同的伺服器上。 新部署有以下特徵:
  1. 對 Nginx 進行獨立部署,形成Web 叢集;
  2. 對 Java 應用程式進行獨立部署,形成應用叢集;
  3. 對資料庫進行獨立部署;
  4. Web 叢集與應用叢集間通過HTTP協議進行互動;
  5. 應用叢集與資料庫間通過JDBC協議進行互動。
應用叢集化,會面臨很多挑戰,主要的焦點是如何有效的分配使用者請求。 3.1 DNS 輪詢 首先要解決的問題便是,使用者如何將請求傳送到不同的 Nginx 中,最常見的方式便是 DNS 輪詢。 大多域名註冊商都支援多條 A 記錄的解析,其實這就是 DNS 輪詢,DNS 伺服器將解析請求按照 A 記錄的順序,逐一分配到不同的 IP 上,這樣就完成了簡單的負載均衡。 3.2 負載均衡器 這裡的負載均衡器主要指的是 Nginx 的反向代理功能。當用戶請求傳送到 Nginx 後,Nginx 需要決定將請求轉發到哪臺應用伺服器上。 反向代理(Reverse Proxy)是指以代理伺服器來接受 internet 上的連線請求,然後將請求轉發給內部網路上的伺服器,並將從伺服器上得到的結果返回給 internet 上請求連線的客戶端,此時代理伺服器對外就表現為一個反向代理伺服器。 Nginx 對於後臺伺服器配置比較靈活,可以同時配置多臺伺服器,並根據負載策略將請求分發給後臺伺服器。 3.3 會話問題 在單機時代,我們的請求只會傳送到同一臺機器上,不存在會話問題。當將應用叢集部署時,使用者的多次請求會發送到不同的應用伺服器上。此時,如何對會話進行同步便是棘手問題。 3.3.1 Session Sticky 這種方案主要由 Nginx 處理,讓同樣 session 請求每次都發送到同一臺伺服器進行處理。 Nginx 會將相同使用者的請求傳送到同一臺應用伺服器中。 這是最簡單的策略,但存在一定的問題:
  1. Web 伺服器重啟 Session 丟失;
  2. 負載均衡需要進行應用層解析(第7層),效能損耗較大;
  3. 負載均衡器變為一個有狀態的點,不易容災;
3.3.2 Session Replication 會話問題的根源在於 Session 由多個應用維護,我們可以使用某種機制,在多臺 Web 服務間進行 Session 的資料同步。 由 Session 同步器在各個 Java 應用程式間完成 Session 的同步,最終使每個伺服器中都存在所有使用者的 Session 資料。 這個方案的問題:
  1. 造成網路開銷;
  2. 每臺 Web 伺服器都儲存所有的 Session,記憶體開銷大;
3.3.3 集中式Session 我們可以將 Session 從 Web 服務中抽取出來,並對其進行集中儲存。 將 Session 資訊儲存到 Session 儲存叢集中,Java 應用程式不在負責 Session 的儲存。 這個方案的問題:
  1. 讀取Session引入了網路開銷;
  2. 儲存設施問題影響應用;
3.3.4 Cookie Based Session 還可以將 session 資料放在 cookie 中,然後在 Web 伺服器上從 cookie 中生成對應的 Session 資料。 將 Session 資料編碼到 Cookie 中,每次 Java 應用程式使用 Session 時,都從 Cookie 中重建 Session。 該方案的問題:
  1. 受到 Cookie 大小的限制;
  2. 存在安全性問題;
  3. 每次都攜帶巨大的 Cookie,頻寬消耗嚴重;
  4. 每次都進行 Session 資料恢復,加大應用伺服器的負擔;
隨著系統訪問量的持續增加,面對大量的資料讀取請求,資料庫有些不堪重負。 此時,我們需要對資料庫進行優化。 4 資料庫讀寫分離 通常情況下,資料庫的讀會帥選成為系統的瓶頸。對此,我們可以使用資料庫主從機制,通過新增多個從庫來減緩讀壓力。 與之前部署相比,該架構只是為資料庫增加了若干個從庫:
  1. 對資料庫實施主從部署策略;
  2. 對於資料的寫請求,只能在主庫上進行;
  3. 對於資料的讀請求,可以在任意的從庫上進行;
  4. 主庫與從庫間,通過資料庫同步策略進行資料同步。
由於主庫與從庫間的資料同步需要時間,會出現資料不一致的情況,這塊是業務上需要慎重考慮的一點。 隨著業務越來越複雜,對功能和效能的要求也越來越高,最常見的便是資料庫 like 語句效能已經無法滿足需求;對於某些熱點資料的訪問,其效能也下降很快。 此時,我們需要引入其他元件來有針對性的解決問題。 5 引入搜尋和快取 針對資料庫的 like 語句,通常情況下,是通過引入搜尋引擎來解決;而熱點資料的訪問加速,是通過引入快取服務來解決。 該架構的特徵如下:
  1. 新增搜尋叢集,用以提升資料檢索效能;
  2. 新增快取叢集,用以提升熱點資料訪問效能。
在對資料查詢進行優化後,慢慢的系統的寫效能成為了瓶頸。 此時,需要對資料的寫效能進行擴充套件。 6 資料庫分庫分表 隨著資料量的增長,寫請求量的增加,資料庫的寫入逐漸成為了瓶頸。常規的寫效能優化便是對資料庫進行分庫分表。 6.1 垂直拆分 將不同的業務資料放到不同的資料庫例項中。 6.2 水平切分 把同一個表中的資料拆分到多的資料庫中。 隨著研發團隊的規模越來越多,大家同時在一個專案中進行開發,導致頻繁的衝突和相互影響。 此時,會將整個應用程式根據功能模組進行拆分,從而形成多個子網站或子頻道。 7 應用垂直拆分 面對一個巨無霸式的應用,就像面對一團毛線團,總有一種無法下手的感覺。對此,可以將其進行拆分,將其拆分為多個應用,每個應用獨立開發、獨立部署、獨立維護。 該部署方案更加靈活,大大降低維護成本。
  1. 通過不同的域名或 URL 將整個系統分解為多個子系統;
  2. 使用者通過瀏覽器將各子系統拼接成一個完整的系統;
  3. 各系統間存在少量互動,甚至沒有互動;
問題慢慢展現出來,系統間公共部分沒有統一維護點,同樣的功能、同樣的程式碼分佈在各個系統中。 當然,我們可以通過釋出 jar 包的方式,共享功能程式碼;但當 jar 升級時,就需要所有的子系統同步升級,運維開銷巨大。此時,我們需要引入服務化架構。 8 服務化架構 我們可以將通用功能封裝成一個服務,獨立開發、獨立部署、獨立維護。 在該方案中,我們將業務邏輯進行了進一步拆分:
  1. 整理各個系統間通用業務功能,將其封裝為服務,以承載核心業務邏輯,構建成服務叢集;
  2. 原來的子系統或子頻道,變成薄薄的一層,不承載核心業務,只是根據業務流程對業務服務進行編排;
  3. 應用服務與業務服務間通過 HTTP 或 其他協議進行通訊,常見的包括 Dubbo、Thrift等。
服務化解決了系統之間的直接呼叫問題,也就是常說的 RPC,整個系統的協調點全部由應用服務完成。這種架構適用於多種場景,但在一些需要非同步處理的極端場景就顯得有心無力了。 此時,我們需要引入訊息中介軟體。 9 引入訊息佇列 服務化解決了直接呼叫問題,對於非同步呼叫,最常見的便是訊息中介軟體。 相比之前的架構,變化很小,只是在各個業務服務間添加了另外的一種呼叫方式。 10 小結 冰凍三尺非一日之寒,一個大型系統的構建也不是一朝一夕的事情。我們需要根據業務情況、資料量情況、請求量情況對系統進行合理規劃。 切記,架構不是越複雜越好,而是“適合自己的便是最好的”。 喜歡的小夥伴可以新增我的微信 litao851025,一起交流共同成長(備註為技術學習) 瞭解更多