
本篇是讀《億級流量網(wǎng)站架構(gòu)核心技術(shù)》的一些總結(jié);可以作為在實際項目搭建過程中架構(gòu)核心點實施的擴展發(fā)散或是作為一個項目架構(gòu)的參考
限流
限流算法
令牌桶
固定速率生成令牌
桶滿時新加的令牌丟棄
批量獲取的時候,如果令牌數(shù)不夠,丟棄請求或緩沖區(qū)等待
-可以應(yīng)對請求量突發(fā)增加,Guava 提供RateLimiter實現(xiàn)
漏桶
漏桶為容量,常量流出請求,容量可以任意速率填滿,如果溢出則丟棄
-可以使流量平滑過度
計數(shù)器
-
超出設(shè)定的并發(fā)閾值,則進行限流
-
總訪問數(shù)限流(秒殺)
AtomicLong
Semaphore
-
時間段限流
- 對時間進行hash,做一個緩存計數(shù),設(shè)置超時
-
分布式限流方案
- Redis + Lua 或 Nginx + Lua
超時、重試機制
讀服務(wù)
天然適合重試
寫服務(wù)
需要保證服務(wù)的冪等性
網(wǎng)絡(luò)連接超時
代理層超時與重試
Haproxy、Nginx、Twemproxy(Redis分片代理)
-
Nginx
代理超時設(shè)置
客戶端超時
DNS解析超時
Web容器超時
- Tomcat、Jetty
中間件客戶端超時與重試
- Dubbo、MQ、HttpclientHttpclient
數(shù)據(jù)庫客戶端超時
- MySql、Oracle
業(yè)務(wù)超時
訂單取消、限時活動
Future#get
異步并發(fā)
異步是針對CPU和IO的,是指當(dāng)IO沒有就緒時要讓出CPU來處理其他任務(wù)。Java中真正實現(xiàn)異步化是非常困難的,大多數(shù)場景并不是真正的異步化
異步并發(fā)并不能使響應(yīng)變得更快,更多是為了提升吞吐量、對請求更細粒度的控制
同步阻塞調(diào)用
- 即串行調(diào)用,響應(yīng)時間為所有依賴服務(wù)的響應(yīng)時間總和
異步Future
- 并發(fā)發(fā)出N個請求,等待最慢的一個返回
異步Callback
- 通過回調(diào)機制實現(xiàn),提升吞量
異步編排CompletableFuture
- JDK8或以上,內(nèi)部使用ForkJoinPool實現(xiàn)異步處理
Hystrix
熔斷、降級
- HystrixCircuitBreaker#allowRequest實現(xiàn)
采樣統(tǒng)計(內(nèi)存中存儲)
計數(shù)統(tǒng)計
- BucketedCounterStream,時間滾轉(zhuǎn)采樣分組
最大并發(fā)統(tǒng)計
- RollingConcurrencyStream
延時百分比統(tǒng)計
- RollingDistributionStream、HdrHistogram
Turbine + Hystrix-Dashboard實現(xiàn)可視化統(tǒng)計數(shù)據(jù)
請求合并
- 支持將多個單請求轉(zhuǎn)換為一個批量請求;調(diào)用過程中使用#queue。批量口查詢結(jié)果會回調(diào)返回到相應(yīng)的單個請求口
壓測與預(yù)案
系統(tǒng)壓測
線下壓測
JMeter、Apache ab
- 仿真度不高,數(shù)據(jù)只能作為參考
全鏈路壓測
線上壓測
數(shù)據(jù)仿真度
-
仿真壓測
- 程序模擬請求
-
引流壓測
- 可以通過TCPCopy復(fù)制線上流量進行壓測
讀、寫
-
讀壓測
- 商品的詳情,價格
-
寫壓測
- 下單;盡量回滾或刪除寫入數(shù)據(jù)
混合壓測
業(yè)務(wù)服務(wù)
-
隔離集群壓測
- 可以先將需要壓測的集群從線上摘除,再將線上流量引流到該集群
-
線上集群壓測
- 直接壓測線上集群,風(fēng)險高
連接池、線程池
數(shù)據(jù)庫連接池
C3P0
DBCP
Druid
HikariCP
切記要設(shè)置destroy-method=“close”,否則多次重啟tomcat后舊的數(shù)據(jù)庫連接池的連接不會釋放
建議需要有熔斷和快速失敗機制
HttpClient連接池
3.x、4.x、5.x API完全不兼容
只有在開啟長連接時才是真正的連接池,如果是短連接,只作為一個信號量來限制總請求數(shù)
HttpClient是線程安全的,不要每次使用創(chuàng)建一個
使用連接池時,要盡快消費響應(yīng)體并釋入連接到連接池
連接復(fù)用條件還很苛刻,使用的時候要格外注意
JVM設(shè)置線程棧大小
- -Xss128k
線程池大小配置
利特爾法則
實際業(yè)務(wù)壓測
java實現(xiàn)
-
ThreadPoolExecutor
- 標(biāo)準(zhǔn)線程池
-
ScheduledThreadPoolExecutor
- 支持延遲任務(wù)的線程池
ForkJoinPool
- 使用work-stealing算法,使得空閑線程可以竊取其它隊列的任務(wù)去處理
隊列術(shù)
異步處理
系統(tǒng)解耦
- 不需要實時處理、不需要強一致
數(shù)據(jù)同步
流量削峰
- 緩存+隊列將數(shù)據(jù)流量流削峰;秒殺系統(tǒng)下單服務(wù)的應(yīng)用場景
緩沖隊列
- 使用緩沖隊列應(yīng)對突發(fā)流量時,并不能使處理速度變快,而是使處理速度變平滑
任務(wù)隊列
- 不需要與主線程同步執(zhí)行的任務(wù)扔到任務(wù)隊列進行異步處理
消息隊列
訂閱模式
點對點
發(fā)布訂閱
雙寫模式
- DB寫入和MQ發(fā)送在同一個線程處理;保證數(shù)據(jù)最終一致
單寫DB
- 可以訂閱數(shù)據(jù)庫日志機制進行業(yè)務(wù)處理;不存在數(shù)據(jù)不一致情況
通過消息隊列可以實現(xiàn)異步處理、系統(tǒng)解耦和數(shù)據(jù)異構(gòu)
請求隊列
- 可以實現(xiàn)流量控制、請求分級、請求隔離;提高系統(tǒng)的可用性,一般用于前端接入層
數(shù)據(jù)總線隊列
- 使用場景只是數(shù)據(jù)維度的同步,阿里的otter,全量離線數(shù)據(jù)同步 kettle
混合隊列
- 應(yīng)用層按照不同維度發(fā)送MQ,下游應(yīng)用接收到該消息后會將其放入Redis中,使用RedisList來存儲這些任務(wù),消息被消費后再次發(fā)送MQ出去;使用Redis隊列的主要原因是想提升消息堆積能力和并發(fā)處理能力
分布式服務(wù)隔離機制
線程
- 項目折分服務(wù)化
進程
- 負載均衡
集群
- 對服務(wù)化分組
讀、寫隔離
動、靜隔離
- 路由服務(wù)接口,靜態(tài)js/css路由CDN
爬蟲隔離
- user-agent路由方案
熱點隔離
- 秒殺、搶購系統(tǒng)單獨部署
資源隔離
- 硬件資源
構(gòu)建需求響應(yīng)式
單品頁技術(shù)架構(gòu)發(fā)展
1.0
- DB+memcached
2.0
- 靜態(tài)化HTML
3.0
- 多維度數(shù)據(jù)異構(gòu)
詳情頁架構(gòu)設(shè)計原則
數(shù)據(jù)閉環(huán)
- 數(shù)據(jù)都在自己的系統(tǒng)里維護、自我管理,不依賴于任何其他系統(tǒng)。包括:數(shù)據(jù)異構(gòu)、數(shù)據(jù)原子化、數(shù)據(jù)聚合、數(shù)據(jù)存儲
數(shù)據(jù)維度化
- 按照商品自身維度和作用進行維度化,進行更有效地存儲和使用
worker無狀態(tài)化+任務(wù)化
- 數(shù)據(jù)異構(gòu)和數(shù)據(jù)同步worker無狀態(tài)設(shè)計;任務(wù)的多隊列化;任務(wù)副本隊列用來執(zhí)行修正邏輯回放
異步化+并發(fā)化
多級緩存化
動態(tài)化
數(shù)據(jù)獲取動態(tài)化
模板渲染實時化
重啟應(yīng)用秒級化
彈性化
- 自動擴容
降級開關(guān)
擴容
單體應(yīng)用垂直擴容
- 通過硬件來擴容
單體應(yīng)用水平擴容
- 部署更多的鏡像
應(yīng)用拆分
- 單體應(yīng)用垂直和水平擴容都不能解決問題的時候,需要采取應(yīng)用拆分
數(shù)據(jù)拆分
單庫查詢要改為跨庫查詢
- 全局表、ES搜索
讀、寫分離
水平拆分;分庫分表
-
策略
-
取模
可以按照主鍵哈希取模進行分庫分表
擴容:成倍增量,每一個舊節(jié)點與之對應(yīng)一個或一組哈希后的新節(jié)點,只需要把對應(yīng)分組的數(shù)據(jù)復(fù)制遷移就可以了
優(yōu)點:數(shù)據(jù)熱點分散
缺點:按照非主鍵維度進行查詢時需要跨庫/跨表查詢
-
分區(qū)
-
時間段分區(qū)
- 例:一個月一張表,一年一個庫
-
數(shù)據(jù)量分區(qū)
- 每2000萬記錄一個表
優(yōu)點:易于水平擴展
缺點:存在熱點問題
-
路由表
-
-
注意點
應(yīng)用層支持,還是通過中間件層
分庫分表的算法是什么
join是否支持,排序分頁是否支持,事務(wù)是否支持
中間件層的實現(xiàn)有奇虎360的Atlas、阿里的Cobar、Mycat;對oracle支持低
應(yīng)用層的實現(xiàn)有當(dāng)當(dāng)?shù)膕harding-jdbc,阿里的cobar-client;對oracle支持低
數(shù)據(jù)異構(gòu)
查詢維度異構(gòu)
聚合據(jù)異構(gòu)
垂直拆分;寬表拆子表
緩存的應(yīng)用
數(shù)據(jù)最終一致
-
緩存的同步寫
- 性能差,數(shù)據(jù)一致性好
-
緩存的異步寫
- 性能好,數(shù)據(jù)一致性有延時,可以把用戶指到同一集群,避免多次刷新看到的數(shù)據(jù)不一致
任務(wù)系統(tǒng)擴容
簡單任務(wù)
- 可以使用Thread死循環(huán);周期性的任務(wù)可以使用Timer
分布式任務(wù)
- 可以選擇Quartz集群版、tbschedule、elastic-job
回滾機制
事務(wù)回滾
分布式事務(wù)
-
最終一致性
事務(wù)表
消息隊列
補償機制
TCC模式
Sagas模式
-記錄事務(wù)日志
-批處理任務(wù)核驗
代碼庫回滾
SVN
- 集中版本控制系統(tǒng)
GIT
- 分布式版本控制系統(tǒng)
部署版本回滾
部署版本化
- 留存歷史發(fā)布包,以備回全量主機回滾
小版本增量發(fā)布
- 部分主機部署驗證后,全量主機發(fā)布
大版本灰度發(fā)布
- 新、老版本共存
架構(gòu)升級并發(fā)發(fā)布
- 新、老版本共存
例:新、老共存遷移部署比例1%->10%->50%->100%
靜態(tài)資源版本回滾
數(shù)據(jù)版本回滾
全量回滾
- 保存的數(shù)據(jù)多,回滾方便
增量回滾
- 保存數(shù)據(jù)少,需要逐級回溯
應(yīng)用級緩存
五分鐘法則
- 數(shù)據(jù)的訪問周期在5分鐘以內(nèi)則存放在內(nèi)存中,否則應(yīng)該存放在硬盤中。具體由來可以參考:https://blog.csdn.net/pennyliang/article/details/5903181
局布性原理
- 時間局布性,空間局布性。具體可參考:http://www.cnblogs.com/jqctop1/p/4714116.html
緩存命中率
- 可以做為評定緩存優(yōu)劣的標(biāo)準(zhǔn)
回收策略
基于空間
- 達到設(shè)定空間上限時,指定策略刪除
基于容量
- 達到設(shè)定記錄條數(shù)上限時,指定策略刪除
基于時間
-
TTL(Time To Live)
- 存活期,超過設(shè)定時間段內(nèi)的數(shù)據(jù)全部視為過期
-
TTI(Time To Idle)
- 空閑時期,數(shù)據(jù)在設(shè)定時間內(nèi)沒有被訪問過視為過期
基于Java對象引用
- JVM回收機制
回收算法
FIFO(First In First Out)
- 先進先出算法
LRU(Least Recently Used)
- 最近最少使用算法,使用時間距離當(dāng)前時間最長的數(shù)據(jù)將被移除;Guava Cache、Ehcache
LFU(Least Frequently Used)
- 最不常用算法,約定時間段內(nèi)使用頻率最少的數(shù)據(jù)將被移除
java緩存類型
堆緩存
優(yōu)點:不需要序列化
缺點:GC暫停時間變長
重啟需要重新加截
實現(xiàn):Guava Cache、Ehcache 3.x、MapDB
堆外緩存
優(yōu)點:可以有更大的緩存空間,GC時間短
缺點:需要序列化,讀、寫相對堆緩存慢很多
重啟需要重新加載
實現(xiàn):Ehcache 3.x、MapDB
磁盤緩存
- 重啟不需要重新加載
分布式緩存
- 解決了單機容量問題。但需要注意數(shù)據(jù)一致性問題
單機方案:最熱的數(shù)據(jù)到堆緩存,相對熱的數(shù)據(jù)到堆外緩存,不熱的數(shù)據(jù)到磁盤緩
集群方案:存儲最熱的數(shù)據(jù)到堆緩存,相對熱的數(shù)據(jù)到堆外緩存,全量數(shù)據(jù)到分布式緩存
Cache-Aside方式
理解:業(yè)務(wù)代碼圍繞著Cache寫,由業(yè)務(wù)代碼直接維護緩存
-
并發(fā)更新問題
用戶維度的緩存發(fā)生并發(fā)概率很小,可以不考慮
如商品數(shù)據(jù)更新,可以使用canal訂閱binlog來進行增量更新分布式緩存
Cache-As-SoR方式
概念:所有操作都只對Cache進行操作,Cache看作為SoR,然后Cache再委托給SoR進行真實的讀、寫。
-
read-through
cache不命中需要回源SoR
Guava Cache、Ehcache 3.x支持,需要配置一個CacheLoader
-
write-through
穿透寫模式,由cache直接寫入SoR
Ehcache支持,需要配置CacheLoaderWriter
-
write-behind
回寫模式,不同于write-through的同步寫,轉(zhuǎn)為異步寫
Ehcache支付,需要配置CacheLoaderWriter
Guava Cache、Ehcache中的堆緩存都是基于引用,在存儲到SoR時應(yīng)該復(fù)制對象
性能測試
- 可以使用JMH
HTTP緩存
基于瀏覽器的last-modified
spring MVC 請求響應(yīng)設(shè)置last-modified,expires,cache-control,http-status = 304
nginx 也提拱了expires、etag、if-modified-since指令來實現(xiàn)緩存控制
HttpClient客戶端緩存
多級緩存
搭建層級
接入Nginx將請求負載均衡到應(yīng)用Nginx
-
輪詢算法
- 使用服務(wù)器的請求更加均衡
-
一致性哈希
- 提升應(yīng)用的緩存命中率
應(yīng)用Nginx讀取本地緩存
提升吞吐量,降低后端壓力
解決熱點問題
讀取分布式緩存
如果應(yīng)用Nginx沒有命中本地緩存
如果是讀寫分離的集群,可以再嘗試讀取一次主集群
減少回源訪問率
回源Tomcat集群
如果分存式緩存也沒有命中
可以使用輪詢、一致性哈希算法回源Tomcat
Tomcat讀取本地緩存
- 添加一層本地緩存,目的在于預(yù)防緩存崩潰,和快速修復(fù)緩存
回源DB讀取數(shù)據(jù)
- 如果所有緩存都未命中,則回源數(shù)據(jù)源讀取數(shù)據(jù)
數(shù)據(jù)緩存
時限
-
設(shè)定過期
- 常見用法,讀取緩存未命中,回源數(shù)據(jù)源,異步存入緩存,設(shè)定過期時間;需要考慮數(shù)據(jù)一至性問題
-
設(shè)定不過期
- 長尾數(shù)據(jù),比如用戶、分類、商品、價格、訂單等;可以考慮使用LRU機制驅(qū)逐緩存數(shù)據(jù)
維度化緩存與增量緩存
- 比如一個商品會由多維度拼裝而成,如果全量更新成本會很高,這時可以建立多維度緩存,增量更新某一維度
大Value緩存
- 首先要盡量避免大Value來緩存數(shù)據(jù);如果當(dāng)前場景必須使用,則可以考慮memcached的多線程來實現(xiàn),或者對Value進行壓縮,或者還可以拆分多個小Value,由業(yè)務(wù)端來聚合
熱點緩存
可以在客戶端做一份本地緩存,避免拉取遠程數(shù)量訪問量太大導(dǎo)致遠程緩存請求過多、負載過高或帶寬過高等問題
-
實現(xiàn)架構(gòu)
-
單機全量緩存+主從
- 所有緩存都存儲在應(yīng)用本機,如果緩存未命中,回源數(shù)據(jù)更新到主緩存服務(wù)器,再同步在從緩存服務(wù)器中;回源更新可以采用懶加載或訂閱消息
分布式緩存+應(yīng)用本地?zé)狳c
-
-
更新緩存與原子性
使用時間戳或者版本對比,如果使用的是Redis,則可以利用其單線程機制進行原子化更新
使用如canal訂閱數(shù)據(jù)庫binlog
緩存的分步式
- 將數(shù)據(jù)分散在多個實例或多臺服務(wù)器中;如果使用redis可以考慮redis-cluster
緩存崩潰與快速修復(fù)
主從機制,做好冗余
部分用戶降級,慢慢減少降級量;通過worker預(yù)熱緩存數(shù)據(jù)
寫緩存操作不要放在事務(wù)中,防止寫操作異常引起整個事務(wù)回滾