此處我說的HTTP服務主要指如訪問京東網站時我們看到的熱門搜索、用戶登錄、實時價格、實時庫存、服務支持、廣告語等這種非Web頁面,而是在Web頁面中異步加載的相關數據。這些服務有個特點即訪問量巨大、邏輯比較單一;但是如實時庫存邏輯其實是非常復雜的。在京東這些服務每天有幾億十幾億的訪問量,比如實時庫存服務曾經在沒有任何IP限流、DDos防御的情況被刷到600多萬/分鍾的訪問量,而且能輕松應對。支撐如此大的訪問量就需要考慮設計良好的架構,並很容易實現水平擴展。
架構
此處介紹下Nginx+JavaEE的架構。
1. 單DB架構
早期架構可能就是Nginx直接upstream請求到后端Tomcat,擴容時基本是增加新的Tomcat實例,然后通過Nginx負載均衡upstream過去。此時數據庫還不是瓶頸。當訪問量到一定級別,數據庫的壓力就上來了,此處單純的靠單個數據庫可能扛不住了,此時可以通過數據庫的讀寫分離或加緩存來實現。
2. DB+Cache數據庫讀寫分離架構
此時就通過使用如數據庫讀寫分離或者Redis這種緩存來支撐更大的訪問量。使用緩存這種架構會遇到的問題諸如緩存與數據庫數據不同步造成數據不一致(一般設置過期時間),或者如Redis掛了,此時會直接命中數據庫導致數據庫壓力過大;可以考慮Redis的主從或者一致性Hash 算法做分片的Redis集群;使用緩存這種架構要求應用對數據的一致性要求不是很高;比如像下訂單這種要落地的數據不適合用Redis存儲,但是訂單的讀取可以使用緩存。
3. Nginx+Lua+Local Redis+Mysql集群架構
首先Nginx通過Lua讀取本機Redis緩存,如果不命中才回源到后端Tomcat集群;后端Tomcat集群再讀取Mysql數據庫。Redis都是安裝到和Nginx同一台服務器,Nginx直接讀本機可以減少網絡延時。Redis通過主從方式同步數據,Redis主從一般采用樹的方式實現:
在葉子節點可以做AOF持久化,保證在主Redis掛時能進行恢復;此處假設對Redis很依賴的話,可以考慮多主Redis架構,而不是單主,來防止單主掛了時數據的不一致和擊穿到后端Tomcat集群。這種架構的缺點就是要求Redis實例數據量較小,如果單機內存不足以存儲這么多數據,當然也可以通過如尾號為1的在A服務器,尾號為2的在B服務器這種方式實現;缺點也很明顯,運維復雜、擴展性差。
4. Nginx+Lua+ Redis集群+Mysql集群架構
和之前架構不同的點是此時我們使用一致性Hash算法實現Redis集群而不是讀本機Redis,保證其中一台掛了,只有很少的數據會丟失,防止擊穿到數據庫。Redis集群分片可以使用Twemproxy;如果 Tomcat實例很多的話,此時就要考慮Redis和Mysql鏈接數問題,因為大部分Redis/Mysql客戶端都是通過連接池實現,此時的鏈接數會成為瓶頸。一般方法是通過中間件來減少鏈接數。
Twemproxy與Redis之間通過單鏈接交互,並Twemproxy實現分片邏輯;這樣我們可以水平擴展更多的Twemproxy來增加鏈接數。
此時的問題就是Twemproxy實例眾多,應用維護配置困難;此時就需要在之上做負載均衡,比如通過LVS/HAProxy實現VIP(虛擬IP),可以做到切換對應用透明、故障自動轉移;還可以通過實現內網DNS來做其負載均衡。
本文沒有涉及Nginx之上是如何架構的,對於Nginx、Redis、Mysql等的負載均衡、資源的CDN化不是本文關注的點,有興趣可以參考
很早的Taobao CDN架構
Nginx/LVS/HAProxy負載均衡軟件的優缺點詳解