億級流量網站構架核心技術
高併發原則
-
無狀態
-
拆分
-
系統維度:根據系統功能 / 業務進行拆分
-
功能維度:對一個系統進行功能再拆分
-
讀寫維度:根據讀寫比例進行拆分
-
AOP 維度:根據訪問特徵
-
模塊維度:比如按照基礎或代碼維護特徵進行拆分
-
服務化:進程內服務 -> 單機遠程服務 -> 集羣手動註冊服務 -> 自動註冊和發現服務 -> 服務的分組 / 隔離 / 路由 -> 服務治理 (限流 / 黑白名單等)
-
消息隊列:服務解耦、異步處理、流量削峯 / 緩衝等
-
消息隊列進行多個鏡像複製
-
重試功能、防重、(冪等性)
-
失敗處理、日誌、報警
-
大流量緩衝:一般是犧牲強一致性,而保證最終一致性
-
數據校對:數據校對與修正來保證數據的一致性和完整性
-
數據異構:形成數據閉環,任何依賴系統出問題了,還是能正常工作,只是更新會有積壓,但不影響前端展示
-
數據異構:通過如 MQ 接受數據變更,然後原子化存儲到合適的存儲引擎,如 Redis 等。目的是把數據從多個數據源拿過來
-
數據聚合:可選的,目的是把這些數據做聚合,前端可以一個調用拿到全部數據,該步驟一般存儲在 KV 存儲中
-
前端展示:前端通過一次或少量調用拿到所需要的數據
-
緩存銀彈
-
使用接入層提供的緩存機制:對於沒 CDN 緩存的應用來說,可以考慮使用如 Nginx 搭建一層接入層,可以考慮以下機制:
-
使用應用層提供的緩存機制:使用 Tomcat 時可以使用堆內緩存 / 堆外緩存;local redis cache 在應用所在服務器上部署一組 redis,應用直接讀取本機 Redis 數據,多機之間使用主從機制同步數據
-
使用分佈式緩存:數據量太大,使用分片機制將流量分散到多臺,或直接使用分佈式緩存實現。常見分片機制是一致性哈希
-
靜態化 / 僞靜態化,使用服務器操作系統提供的緩存機制
-
URL 重寫:將 URL 按照指定的順序或格式重寫,去除隨機數
-
一致性哈希:按照指定的參數做一致性哈希,從而保證相同數據落到一臺服務器上
-
proxy_cache:使用內存級 / SSD 級代理緩存來緩存內容
-
proxy_cache_lock:使用 Lock 機制,將多個回源合併爲一個,以減少回源量,並設置相應的 Lock 超時時間
-
shared_dict:如果架構使用 nginx+lux 實現,,可考慮使用 Lua shared_dict 進行 cache,最大好處是 reload 緩存不會丟失
-
對於託底 (或兜底,指降級後顯示的) 數據或異常數據,不應該讓其緩存,否則用戶會很長一段時間內看到這些數據
-
使用代理服務器 (含 CDN):一般有兩種機制:推送機制 (當內容變更後主動推送到 CDN 邊緣節點),拉取機制 (先訪問邊緣節點,當沒有內容時,回源到源服務器拿到內容並存儲到節點上)。使用 CDN 需要考慮 URL 的設計,比如不能有隨機數,否則每次都穿透 CDN 回源到源服務器;對於爬蟲,可以返回過期數據而不選擇回源
-
使用鏡像服務器,使用 P2P 技術
-
使用瀏覽器緩存:設置請求過期時間,對應相應頭 Expires, Cache-control 進行控制,適合於實時性不敏感數據
-
客戶端應用緩存:提前將內容發到客戶端進行緩存
-
客戶端:
-
客戶端網絡:代理服務器開啓緩存
-
廣域網:
-
源站及源站網絡:
-
併發化
-
方式
-
應用級緩存:緩存回收策略 (空間 / 容量 / 時間),緩存回收算法 (FIFO/LRU/LFU),java 堆 / java 堆外 / 磁盤緩存,Guava/Ehcache/MapDB,緩存使用模式 (Cache-Asize/Cache-As-SoR/Copy Pattern)
-
HTTP 緩存:瀏覽器緩存,HttpClient 客戶端緩存,nginx 代理層緩存
-
多級緩存:分佈式緩存,熱點數據與更新緩存,更新緩存與原子性,緩存崩潰與快速修復
-
池化:數據庫連接池,FttpClient 連接池,線程池
-
異步併發:同步阻塞調用,異步 Future,異步 CallBack,異步編排 CompletableFuture,請求緩存,請求合併
-
擴容:單體應用垂直擴容,單體應用水平擴容,應用拆分,數據庫拆分 (水平 / 垂直),使用 sharding-jdbc 分庫分表 / 讀寫分離,數據異構,任務系統擴容 (Elastic-Job)
-
隊列:異步處理 / 系統解耦 / 數據同步 / 流量削峯,緩衝隊列 / 任務隊列 / 消息隊列 / 請求隊列 / 數據總線隊列,Disruptor+Redis 隊列,基於 Canal 實現數據異構
高可用原則
-
負載均衡:負載均衡算法、失敗重試機制、健康檢查機制、動態負載均衡
-
降級
-
開關集中化管理:通過推送機制把開關推送到各個應用
-
可降級的多級讀服務:比如服務調用降級爲只讀本地緩存、只讀分佈式緩存、只讀默認降級數據
-
開關前置化:如架構師 nginx+tomcat,將開關前置到 nginx 接入層,請求流量不回源後端 tomcat 或只小部分流量回源
-
業務降級
-
降級預案、自動降級 / 開關降級、讀服務 / 寫服務降級、多級降級、配置中心、使用 Hystrix 降級、使用 Hystrix 熔斷
-
限流:
-
惡意請求流量只訪問到 cache
-
對於穿透到後端的流量可以考慮使用 nginx 的 limit 模塊處理
-
對於惡意 IP 可以使用 nginx deny 進行屏蔽
-
限流算法、應用級限流、分佈式限流、接入層限流
-
隔離:進程線程隔離、集羣 / 機房隔離、讀寫隔離、動靜隔離、爬蟲 / 熱點隔離、使用 Hystrix 隔離、基於 Servlet3 的請求隔離
-
超時與重試:代理層超時與重試、Web 容器超時、中間件客戶端超時與重試、數據庫客戶端超時、NOSQL 客戶端超時、業務超時、前端 AJAX 超時
-
切流量:
-
DNS:切換機房入口
-
HttpDNS:主要 APP 場景下,在客戶端分配好流量入口,繞過運營商 LocalDNS 並實現更精準流量調度
-
LVS/HaProxy:切換故障的 nginx 接入層
-
Nginx:切換故障的應用層
-
可回滾:版本化的目的是實現可審計可追溯,並且可回滾。如果程序或數據出錯時,如果有版本化機制,那就可以通過回滾恢復到最近一個正確的版本,比如事務回滾、代碼庫回滾、部署版本回滾、數據版本回滾、靜態資源版本回滾等。
-
壓測與預案:系統壓測、系統優化與容災、應急預案、
-
監控報警:服務器 / 系統 / JVM / 接口監控、監控時間段、報警閥值、通知方式
業務設計原則
-
防重設計:防重 key、防重表
-
冪等設計
-
流程可定義:關聯、可複用流程,個性化流程
-
狀態與狀態機
-
比如訂單交易,會有正向狀態 (待付款 / 待發貨 / 已發貨 / 完成) 與逆向狀態(取消 / 退款),正向狀態與逆向狀態根據系統特徵決定要不要分離存儲。狀態設計時應有狀態軌跡,方便跟蹤訂單軌跡並記錄相關日誌,萬一出問題時可回溯問題。
-
比如訂單狀態的變遷 (待支付 -> 待發貨 ->已發貨 ->完成)。要考慮要不要使用狀態機來驅動狀態的變更和後續流程節點操作,尤其當狀態很多的時候使用狀態機能更好地控制狀態遷移
-
考慮併發狀態修改問題:一個訂單同時只能有一個修改、狀態變更的有序性、時間差
-
後臺系統操作可反饋
-
後臺系統審計化
-
文檔和註釋
-
備份
負載均衡與反向代理
四層負載均衡:首先 DNS 解析到 LVS/F5,然後 LVS/F5 轉發給 Nginx,再由 Nginx 轉發給後端 Real Server
兩層負載均衡是通過改寫報文的目標 MAC 地址爲上游服務器 MAC 地址,源 IP 和目標 IP 地址是沒有改變的,負載均衡服務器和真實服務器共享同一個 VIP,如 LVS DR 工作模式。
四層負載均衡是根據端口將報文轉發到上游服務器 (不同的 IP 地址 + 端口),如 LVS NAT 模式、HaProxy。
七層負載均衡是根據端口號和應用層協議如 HTTP 協議的主機名、URL,轉發報文到上游服務器 (不同的 IP 地址 + 端口),如 HaProxy、Nginx
-
上游服務器配置:使用 upstream server 配置上游服務器
-
IP 地址和端口
-
權重
-
負載均衡算法:
-
round-robin 輪循
-
ip_hash
-
hash key [consistent]:對某一 key 進行哈希或使用一致性哈希算法
-
哈希算法:根據請求 uri 進行負載均衡,可以使用 nginx 變量
-
least_conn
-
失敗重試機制:配置當超時或上游服務器不存活時,是否需要重試其他上游服務器
-
服務器心跳檢查
-
TCP 心跳檢查
-
HTTP 心跳檢查
隔離
-
線程隔離:主要是指線程池隔離,實際使用時,會把請求分類,然後交給不同的線程池處理。當一種業務的請求處理發生問題時,不會將故障擴散到其他線程池,從而保證其他服務可用。
-
進程隔離:過渡方案,較好的解決方案是將系統拆分爲多個子系統來實現物理隔離
-
集羣隔離
-
機房隔離
-
讀寫隔離:通過主從模式將讀和寫集羣分離
-
動靜隔離:動態內容和靜態內容隔離,一般應將靜態資源放在 CDN 上
-
爬蟲隔離:一種方法通過限流解決;另一種方法是在負載均衡層面將爬蟲路由到單獨集羣,從而保證正常流量可用,爬蟲流量儘量可用
-
熱點隔離:秒殺、搶購。讀熱點可用多級緩存,寫熱點可用緩存 + 隊列模式削峯
-
資源隔離
-
其他:
-
環境隔離:測試環境、預發佈環境 / 灰度環境、正式環境
-
壓測隔離:真實數據、壓測數據
-
AB 測試:不同用戶提供不同版本的服務
-
查詢隔離:簡單、批量、複雜條件查詢分別路由到不同的集羣
Hystrix
在一個分佈式系統裏,許多依賴不可避免的會調用失敗,比如超時、異常等,如何能夠保證在一個依賴出問題的情況下,不會導致整體服務失敗,這個就是 Hystrix 需要做的事情。Hystrix 提供了熔斷、隔離、Fallback、cache、監控等功能,能夠在一個、或多個依賴同時出現問題時保證系統依然可用。
servlet3 異步化模型:
-
請求解析和業務處理線程池分離
-
業務線程池隔離
-
業務線程池監控 / 運維 / 降級
限流
-
限流算法:令牌桶算法、漏桶算法
-
應用級限流:
-
限流總併發 / 連接 / 請求數
-
限制總資源數
-
限流某個接口的總併發 / 請求數
-
限流某個接口的時間窗請求數:Guava
-
平滑限流某個接口的請求數:Guava RateLimiter
-
分佈式限流:redis+lua、 nginx+lua
-
接入層線路:該層通常指請求流量的入口,主要目的爲負載均衡、非法請求過濾、請求聚合、緩存、降級、限流、A/B 測試、服務質量監控等
-
節流:throttleFirst、throttleLast、throttleWithTimeout
降級
超時與重試機制
回滾機制
事務回滾:事務表、消息隊列、補償機制 (執行 / 回滾)、TCC 模式(預佔 / 確認 / 取消)、Sagas 模式(拆分事務 + 補償機制) 實現最終一致性
壓測與預案
應用級緩存
緩存回收策略:
-
基於空間:到達存儲上限後按策略移除數據
-
基於容量:設置最大大小,當緩存條數超過時,按策略移除
-
基於時間:TTL 存活期、TTI 空閒期
-
基於 Java 對象引用:軟引用 (適合做緩存,從而當 JVM 堆內存不足時也可以回收這些對象)、弱引用 (相當於軟引用,更短的生命週期)
-
回收算法:FIFO 先進先出、LRU 最少使用、LFU 最不常用
堆緩存 (Gauva Cache, Ehcache 3.x)、堆外緩存 (Ehcache 3.x, MapDB 3.X)、磁盤緩存 (Ehcache 3.x, MapDB 3.X)、分佈式緩存 (Redis, Ehcache 3.x + Terracotta server)、多級緩存
緩存使用模板
-
三個名詞:
-
SoR:記錄系統,或者叫數據源
-
Cache:緩存,是 SoR 的快照數據
-
回源:即回到數據源頭獲取數據
-
Cache-Aside:即業務代碼圍繞 Cache 寫,是由業務代碼直接維護緩存。適合用 AOP 模式去實現。
-
Cache-As-SoR:即把 Cache 看做 SoR, 所有操作都是對 Cache 進行,然後 Cache 再委託給 SoR 進行真實的讀 / 寫。即代碼中只能看到 Cache 的操作,看不到關於 SoR 相關的代碼。- Read-Through:業務代碼首先調用 Cache,如果 Cache 不命中,由 Cache 回源到 SoR,而不是業務代碼。使用 Read-Through 模式,需要配置一個 CacheLoader 組件用來回源到 SoR 加載數據 - Write-Through:被稱爲穿透寫 / 直寫模式 ———— 業務代碼首先調用 Cache 寫數據,然後由 Cache 負責寫緩存和寫 SoR,而不是業務代碼。需要配置一個 CacheLoaderWriter - Write-Behind:稱之爲回寫模式,不同於 Write-Through 是同步寫 SoR 與 Cache,Write-Behind 是異步寫。異步之後可以實現批量寫、合併寫、延時和限流
-
Copy Pattern
-
Copy-On-Read 在讀時複製
-
Copy-On-Write 在寫時複製
HTTP 緩存
HTTP 緩存:
-
服務器端響應 Last-Modified 會在下次請求時,將 If-Modified-Since 請求頭帶到服務器端進行文檔是否修改的驗證,如果沒有修改則返回 304,瀏覽器可以直接使用緩存內容
-
Cache-Control:max-age 和 Expires 用於決定瀏覽器端內容緩存多久,即多久過期。過期後則刪除緩存重新從服務器端獲取最新的。另外可以用於 from cache 場景
-
HTTP/1.1 規範定義的 Cache-Control 優先級高於 HTTP/1.0 定義的 Expires
-
HTTP/1.1 規範定義 ETag 爲 “被請求變量的實體值”,可簡單理解爲文檔內容摘要,ETag 可用來判斷頁面內容是否已經被修改過了
HttpClient 客戶端緩存:
-
maxCacheEntries
-
maxObjectSize
-
asynchronousWorkersCore/asynchronousWorkersMax/revalidationQueueSize
Nginx HTTP 緩存設置:
-
expires
-
if-modified-since
-
nginx proxy_pass
-
Nginx 代理層緩存:
-
HTTP 模塊配置
-
proxy_cache 配置
多級緩存
應用 Nginx 本地緩存、分佈式緩存、Tomcat 堆緩存
如何緩存數據:
-
過期與不過期:
-
不過期緩存場景一般思路 Cache-Aside 模式,1. 開啓事務 2. 執行 SQL 3. 提交事務 4. 寫緩存
-
過期緩存機制,如懶加載,一般用於緩存其他系統的數據、緩存空間有限、低頻熱點緩存等場景
-
維度化緩存與增量緩存:只更新變的部分
-
大 Value 緩存:多線程實現緩存、對 Value 壓縮、拆分 Value 爲多個小 Value
-
熱點緩存:掛更多的從緩存,通過負載均衡機制讀取;客戶端所在應用 / 代理層本地存儲一份
-
單機全量緩存 + 主從
-
分佈式緩存 + 應用本地緩存
更新緩存與原子性:
-
更新數據時使用更新時間戳或版本對比,如果使用 Redis,則可以使用其單線程機制進行原子化更新
-
使用如 canal 訂閱數據庫 binlog
-
將更新請求按照相應的規則分散到多個隊列,然後每個隊列進行單線程更新,更新時拉取最新的數據保存
-
用分佈式鎖,在更新之前獲取相關的鎖
連接池 / 線程池
-
數據庫連接池:C3P0、DBCP、Druid 等
-
HttpClient 連接池
-
線程池:
-
ThreadPoolExecutor:標準線程池
-
ScheduledThreadPoolExecutor:支持延遲任務的線程池
-
ForkJoinPool:類似於 ThreadPoolExecutor,但使用 work-stealing 模式,會爲線程池中每個線程創建一個隊列,從而用 work-stealing(任務竊取) 算法使得線程可以從其他線程隊列裏竊取任務來執行。
-
提供 ExecutorService 三種實現:
-
Executors 創建簡單線程池
-
根據任務列型是 IO 密集型還是 CPU 密集型、CPU 核數,來設置合理的線程池大小、隊列大小、拒絕策略,並進行壓測和不斷調優來決定適合自己場景的參數
-
Tomcat 線程池
異步併發
異步 Web 服務實現:
如何擴容
=======
隊列
案例
OpenResty
souce: https://zhouj000.github.io/2018/06/25/coreTechnologyOfWebArchitecture-kaitao/
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/y3HgSyn7tfiT8Wkp-SNgZQ