對於一個大型網站,主要有如下幾個特徵:css
咱們常見的大型網站,如百度、淘寶、京東等,都是一個分佈式系統。這麼複雜的系統也不是一天建成的,每一個系統都經歷了漫長的演變過程。數據庫
在大型網站中,其最核心的功能就是 計算 和 存儲。所以系統演變過程也主要圍繞這兩點進行。瀏覽器
在網站剛剛起步時,數據量、訪問量都很是小,一般狀況下,只需一臺應用服務器就能夠了。緩存
起步時,咱們把全部資源所有打包到部署文件中(如 XXX.war),其中包括安全
而後,將打好的發佈包放到 Web 容器中,好比 Tomcat,最後啓動容器,讓其直接對外提供服務。性能優化
該部署策略有如下幾個特徵:服務器
這時,有人會問,Java 應用程序直接對外,會不會存在一些安全或性能方面的問題呢?微信
是的,Tomcat 這種 Web 容器對連接的保持能力比較弱,當存在大量連接時,性能降低很快。同時,Tomcat 並不擅長靜態資源的處理,對此,咱們能夠引入 Nginx,以緩解 Tomcat 的壓力。
咱們在單機部署基礎上,添加 Nginx,也就有了進階方案。cookie
該方案存在如下特徵:網絡
此時,架構顯得清晰不少,但咱們發現一個問題,就是系統對靜態資源和動態資源的處理是徹底不一樣的。
對於靜態資源的處理,相對簡單,只是簡單的文件讀寫。而,動態請求(也就是咱們的業務承載者)會隨着業務的發展愈來愈複雜。
因爲 靜態請求 與 動態請求 採用不一樣的處理策略,咱們能夠將其進行分離。
該部署方案存在如下特性:
靜態資源服務器功能單一,部署繁瑣,有沒有一種更好的策略呢?
答案就是雲服務,好比阿里雲的 OSS 提供靜態資源存儲服務。CDN 提供訪問加速服務,二者結合使用,就獲得了一個海量容量而且性能超強的靜態資源服務器(集羣)。
結合 OSS 和 CDN,靜態請求不會成爲系統的瓶頸,所以,接下來只對動態請求進行討論。
隨着系統訪問量的增長,動態請求出現了明顯的瓶頸。
因爲全部的動態請求所有由一臺應用服務器進行處理,當訪問量上升時,這臺服務就成了系統的瓶頸。此時,咱們須要將系統中的多個組件部署到不一樣的服務器上。
新部署有如下特徵:
應用集羣化,會面臨不少挑戰,主要的焦點是如何有效的分配用戶請求。
首先要解決的問題即是,用戶如何將請求發送到不一樣的 Nginx 中,最多見的方式即是 DNS 輪詢。
大多域名註冊商都支持多條 A 記錄的解析,其實這就是 DNS 輪詢,DNS 服務器將解析請求按照 A 記錄的順序,逐一分配到不一樣的 IP 上,這樣就完成了簡單的負載均衡。
這裏的負載均衡器主要指的是 Nginx 的反向代理功能。當用戶請求發送到 Nginx 後,Nginx 須要決定將請求轉發到哪臺應用服務器上。
反向代理(Reverse Proxy)是指以代理服務器來接受 internet 上的鏈接請求,而後將請求轉發給內部網絡上的服務器,並將從服務器上獲得的結果返回給 internet 上請求鏈接的客戶端,此時代理服務器對外就表現爲一個反向代理服務器。
Nginx 對於後臺服務器配置比較靈活,能夠同時配置多臺服務器,並根據負載策略將請求分發給後臺服務器。
在單機時代,咱們的請求只會發送到同一臺機器上,不存在會話問題。當將應用集羣部署時,用戶的屢次請求會發送到不一樣的應用服務器上。此時,如何對會話進行同步即是棘手問題。
這種方案主要由 Nginx 處理,讓一樣 session 請求每次都發送到同一臺服務器進行處理。
Nginx 會將相同用戶的請求發送到同一臺應用服務器中。
這是最簡單的策略,但存在必定的問題:
會話問題的根源在於 Session 由多個應用維護,咱們可使用某種機制,在多臺 Web 服務間進行 Session 的數據同步。
由 Session 同步器在各個 Java 應用程序間完成 Session 的同步,最終使每一個服務器中都存在全部用戶的 Session 數據。
這個方案的問題:
咱們能夠將 Session 從 Web 服務中抽取出來,並對其進行集中存儲。
將 Session 信息保存到 Session 存儲集羣中,Java 應用程序不在負責 Session 的存儲。
這個方案的問題:
還能夠將 session 數據放在 cookie 中,而後在 Web 服務器上從 cookie 中生成對應的 Session 數據。
將 Session 數據編碼到 Cookie 中,每次 Java 應用程序使用 Session 時,都從 Cookie 中重建 Session。
該方案的問題:
隨着系統訪問量的持續增長,面對大量的數據讀取請求,數據庫有些不堪重負。
此時,咱們須要對數據庫進行優化。
一般狀況下,數據庫的讀會帥選成爲系統的瓶頸。對此,咱們可使用數據庫主從機制,經過添加多個從庫來減緩讀壓力。
與以前部署相比,該架構只是爲數據庫增長了若干個從庫:
因爲主庫與從庫間的數據同步須要時間,會出現數據不一致的狀況,這塊是業務上須要慎重考慮的一點。
隨着業務愈來愈複雜,對功能和性能的要求也愈來愈高,最多見的即是數據庫 like 語句性能已經沒法知足需求;對於某些熱點數據的訪問,其性能也降低很快。
此時,咱們須要引入其餘組件來有針對性的解決問題。
針對數據庫的 like 語句,一般狀況下,是經過引入搜索引擎來解決;而熱點數據的訪問加速,是經過引入緩存服務來解決。
該架構的特徵以下:
在對數據查詢進行優化後,慢慢的系統的寫性能成爲了瓶頸。
此時,須要對數據的寫性能進行擴展。
隨着數據量的增加,寫請求量的增長,數據庫的寫入逐漸成爲了瓶頸。常規的寫性能優化即是對數據庫進行分庫分表。
將不一樣的業務數據放到不一樣的數據庫實例中。
把同一個表中的數據拆分到多的數據庫中。
隨着研發團隊的規模愈來愈多,你們同時在一個項目中進行開發,致使頻繁的衝突和相互影響。
此時,會將整個應用程序根據功能模塊進行拆分,從而造成多個子網站或子頻道。
面對一個巨無霸式的應用,就像面對一團毛線團,總有一種沒法下手的感受。對此,能夠將其進行拆分,將其拆分爲多個應用,每一個應用獨立開發、獨立部署、獨立維護。
該部署方案更加靈活,大大下降維護成本。
問題慢慢展示出來,系統間公共部分沒有統一維護點,一樣的功能、一樣的代碼分佈在各個系統中。
固然,咱們能夠經過發佈 jar 包的方式,共享功能代碼;但當 jar 升級時,就須要全部的子系統同步升級,運維開銷巨大。此時,咱們須要引入服務化架構。
咱們能夠將通用功能封裝成一個服務,獨立開發、獨立部署、獨立維護。
在該方案中,咱們將業務邏輯進行了進一步拆分:
服務化解決了系統之間的直接調用問題,也就是常說的 RPC,整個系統的協調點所有由應用服務完成。這種架構適用於多種場景,但在一些須要異步處理的極端場景就顯得有心無力了。
此時,咱們須要引入消息中間件。
服務化解決了直接調用問題,對於異步調用,最多見的即是消息中間件。
相比以前的架構,變化很小,只是在各個業務服務間添加了另外的一種調用方式。
冰凍三尺非一日之寒,一個大型系統的構建也不是一朝一夕的事情。咱們須要根據業務狀況、數據量狀況、請求量狀況對系統進行合理規劃。
切記,架構不是越複雜越好,而是「適合本身的即是最好的」。
掃一掃,添加個人微信,一塊兒交流共同成長(備註爲技術學習)