億級流量架構(gòu)怎么做資源隔離?寫得太好了!
點(diǎn)擊關(guān)注公眾號,Java干貨及時(shí)送達(dá)
作者:等不到的口琴
鏈接:www.cnblogs.com/Courage129/p/14421585.html
為什么要資源隔離
常見的資源,例如磁盤、網(wǎng)絡(luò)、CPU等等,都會(huì)存在競爭的問題,在構(gòu)建分布式架構(gòu)時(shí),可以將原本連接在一起的組件、模塊、資源拆分開來,以便達(dá)到最大的利用效率或性能。資源隔離之后,當(dāng)某一部分組件出現(xiàn)故障時(shí),可以隔離故障,方便定位的同時(shí),阻止傳播,避免出現(xiàn)滾雪球以及雪崩效應(yīng)。
常見的隔離方式有:
線程隔離 進(jìn)程隔離 集群隔離 機(jī)房隔離 讀寫隔離 動(dòng)靜隔離 爬蟲隔離 等等
線程隔離
網(wǎng)絡(luò)上很多帖子,大多是從框架開始聊的,這兒說人話其實(shí)就是對線程進(jìn)行治理,把核心業(yè)務(wù)線程與非核心業(yè)務(wù)線程隔開,不同的業(yè)務(wù)需要的線程數(shù)量不同,可以設(shè)置不同的線程池,來舉一些框架中應(yīng)用的例子,例如Netty中的主從多線程、Tomcat請求隔離、Dubbo線程模型。
Netty主從程模型

主線程負(fù)責(zé)認(rèn)證,連接,成功之后交由從線程負(fù)責(zé)連接的讀寫操作,大致如下代碼:
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup);
主線程是一個(gè)單線程,從線程是一個(gè)默認(rèn)為cpu*2個(gè)數(shù)的線程池,可以在我們的業(yè)務(wù)handler中做一個(gè)簡單測試:
public void channelRead(ChannelHandlerContext ctx, Object msg) {
System.out.println("thread name=" + Thread.currentThread().getName() + " server receive msg=" + msg);
}
服務(wù)端在讀取數(shù)據(jù)的時(shí)候打印一下當(dāng)前的線程:
thread name=nioEventLoopGroup-3-1 server receive msg="..."
可以發(fā)現(xiàn)這里使用的線程其實(shí)和處理io線程是同一個(gè);
Dubbo線程隔離模型
Dubbo的底層通信框架其實(shí)使用的就是Netty,但是Dubbo并沒有直接使用Netty的io線程來處理業(yè)務(wù),可以簡單在生產(chǎn)者端輸出當(dāng)前線程名稱:
thread name=DubboServerHandler-192.168.1.115:20880-thread-2,...
可以發(fā)現(xiàn)業(yè)務(wù)邏輯使用并不是nioEventLoopGroup線程,這是因?yàn)镈ubbo有自己的線程模型,可以看看官網(wǎng)提供的模型圖:

由圖可以知道,Dubbo服務(wù)端接收到請求后,通過調(diào)度器(Dispatcher)分發(fā)到不同的線程池,也簡單做一些關(guān)于調(diào)度器(Dispatcher)總結(jié):
Dispatcher調(diào)度器可以配置消息的處理線程:
all所有消息都派發(fā)到線程池,包括請求,響應(yīng),連接事件,斷開事件,心跳等。direct所有消息都不派發(fā)到線程池,全部在 IO 線程上直接執(zhí)行。message只有請求響應(yīng)消息派發(fā)到線程池,其它連接斷開事件,心跳等消息,直接在 IO 線程上執(zhí)行。execution只有請求消息派發(fā)到線程池,不含響應(yīng),響應(yīng)和其它連接斷開事件,心跳等消息,直接在 IO 線程上執(zhí)行。connection在 IO 線程上,將連接斷開事件放入隊(duì)列,有序逐個(gè)執(zhí)行,其它消息派發(fā)到線程池。
通過看源碼可以知道,Dubbo默認(rèn)使用的線程池是FixedThreadPool ,線程數(shù)默認(rèn)為200 ;另外,Dubbo 系列面試題和答案全部整理好了,微信搜索Java技術(shù)棧,在后臺(tái)發(fā)送:面試,可以在線閱讀。
Tomcat請求線程隔離
NIO模式:同步非阻塞I/O操作,是一個(gè)基于緩沖區(qū)、并能提供非阻塞I/O操作的API,它擁有比傳統(tǒng)I/O操作具有更好的并發(fā)性能。
在Tomcat7版本之后,Tomcat把連接介入和業(yè)務(wù)處理拆分成兩個(gè)線程池來處理,即:

這樣做,獨(dú)立的業(yè)務(wù)或資源中如果出現(xiàn)崩潰,不會(huì)影響其他的業(yè)務(wù)線程,從而達(dá)到資源隔離和服務(wù)降級的效果。
在使用了servlet3之后,系統(tǒng)線程隔離變得更靈活了。可以劃分核心業(yè)務(wù)隊(duì)列和非核心業(yè)務(wù)隊(duì)列:

線程隔離小總結(jié)
資源一旦出現(xiàn)問題,雖然是隔離狀態(tài),想要讓資源重新可用,很難做到不重啟jvm。 線程池內(nèi)部線程如果出現(xiàn)OOM、FullGC、cpu耗盡等問題也是無法控制的 線程隔離,只能保證在分配線程這個(gè)資源上進(jìn)行隔離,并不能保證整體穩(wěn)定性
點(diǎn)擊關(guān)注公眾號,Java干貨及時(shí)送達(dá)
進(jìn)程隔離
進(jìn)程隔離這種思想其實(shí)并不陌生,Linux操作系統(tǒng)中,利用文件管理系統(tǒng)將各個(gè)進(jìn)程的虛擬內(nèi)存與實(shí)際的物理內(nèi)存映射起來,這樣做的好處是避免不同的進(jìn)程之間相互影響,而在分布式系統(tǒng)中,線程隔離不能完全隔離故障避免雪崩,例如某個(gè)線程組耗盡內(nèi)存導(dǎo)致OOM,那么其他線程組勢必也會(huì)受影響,所以進(jìn)程隔離的思想是,CPU、內(nèi)存等等這些資源也通過不同的虛擬機(jī)來做隔離。
具體操作是,將業(yè)務(wù)邏輯進(jìn)行拆分成多個(gè)子系統(tǒng)(拆分原則可以參考:Redis集群拆分原則之AKF),實(shí)現(xiàn)物理隔離,當(dāng)某一個(gè)子系統(tǒng)出現(xiàn)問題,不會(huì)影響到其他子系統(tǒng)。

集群隔離
如果系統(tǒng)中某個(gè)業(yè)務(wù)模塊包含像搶購、秒殺、存儲(chǔ)I/O密集度高、網(wǎng)絡(luò)I/o高、計(jì)算I/O高這類需求的時(shí)候,很容易在并發(fā)量高的時(shí)候因?yàn)檫@種功能把整個(gè)模塊占有的資源全部耗盡,導(dǎo)致響應(yīng)編碼甚至節(jié)點(diǎn)不可用。
像上圖的的拆分之后,如果某一天購物人數(shù)瞬間暴增,電商交易功能模塊可能受影響,損壞后導(dǎo)致電商模塊其他的瀏覽查詢也無法使用,因此就要建立集群進(jìn)行隔離,具體來說就是繼續(xù)拆分模塊,將功能微服務(wù)化。
推薦一個(gè) Spring Boot 基礎(chǔ)教程及實(shí)戰(zhàn)示例:https://github.com/javastacks/spring-boot-best-practice
解決方案
獨(dú)立拆分模塊 微服務(wù)化
可以使用hystrix在微服務(wù)中隔離分布式服務(wù)故障。他可以通過線程和信號量進(jìn)行隔離。
線程池隔離與信號量隔離對比
這兒同上面的線程隔離,不多贅述,簡單敘述一下hystrix的兩種隔離方式的區(qū)別:
| 隔離方式 | 是否支持超時(shí) | 是否支持熔斷 | 隔離原理 | 是否是異步調(diào)用 | 資源消耗 |
|---|---|---|---|---|---|
| 線程池隔離 | 支持,可直接返回 | 支持,當(dāng)線程池到達(dá)maxSize后,再請求會(huì)觸發(fā)fallback接口進(jìn)行熔斷 | 每個(gè)服務(wù)單獨(dú)用線程池 | 可以是異步,也可以是同步。看調(diào)用的方法 | 大,大量線程的上下文切換,容易造成機(jī)器負(fù)載高 |
| 信號量隔離 | 不支持,如果阻塞,只能通過調(diào)用協(xié)議(如:socket超時(shí)才能返回) | 支持,當(dāng)信號量達(dá)到maxConcurrentRequests后。再請求會(huì)觸發(fā)fallback | 通過信號量的計(jì)數(shù)器 | 同步調(diào)用,不支持異步 | 小,只是個(gè)計(jì)數(shù)器 |
信號量隔離
說人話就是,很多線程涌過來,要去獲得信號量,獲得了才能繼續(xù)執(zhí)行,否則先進(jìn)入隊(duì)列等待或者直接fallback回調(diào)

最重要的是,信號量的調(diào)用是同步的,也就是說,每次調(diào)用都得阻塞調(diào)用方的線程,直到結(jié)果返回。這樣就導(dǎo)致了無法對訪問做超時(shí)(只能依靠調(diào)用協(xié)議超時(shí),無法主動(dòng)釋放)
官網(wǎng)對信號量隔離的描述建議
HystrixCommands is when the call is so high volume (hundreds per second, per instance) that the overhead of separate threads is too high; this typically only applies to non-network calls.理解下兩點(diǎn):
隔離的細(xì)粒度太高,數(shù)百個(gè)實(shí)例需要隔離,此時(shí)用線程池做隔離開銷過大 通常這種都是非網(wǎng)絡(luò)調(diào)用的情況下

機(jī)房隔離
機(jī)房隔離主要目的有兩個(gè),一方面是將不同區(qū)域的用戶數(shù)據(jù)隔離到不同的地區(qū),例如湖北的數(shù)據(jù)放在湖北的服務(wù)器,浙江的放在浙江服務(wù)器,等等,這樣能解決數(shù)據(jù)容量大,計(jì)算密集,i/o(網(wǎng)絡(luò))密集度高的問題,相當(dāng)于將流量分在了各個(gè)區(qū)域。
另一方面,機(jī)房隔離也是為了保證安全性,所有數(shù)據(jù)都放在一個(gè)地方,如果發(fā)生自然災(zāi)害或者爆炸等災(zāi)害時(shí),數(shù)據(jù)將全都丟失,所以把服務(wù)建立整體副本(計(jì)算服務(wù)、數(shù)據(jù)存儲(chǔ)),在多機(jī)房內(nèi)做異地多活或冷備份、是微服務(wù)數(shù)據(jù)異構(gòu)的放大版本。
如果機(jī)房層面出現(xiàn)問題的時(shí)候,可以通過智能dns、httpdns、負(fù)載均衡等技術(shù)快速切換,讓區(qū)域用戶盡量不收影響。

數(shù)據(jù)讀寫隔離
通過主從模式,將mysql、redis等數(shù)據(jù)存儲(chǔ)服務(wù)集群化,讀寫分離,那么在寫入數(shù)據(jù)不可用的時(shí)候,也可以通過重試機(jī)制 臨時(shí)通過其他節(jié)點(diǎn)讀取到數(shù)據(jù)。
多節(jié)點(diǎn)在做子網(wǎng)劃分的時(shí)候,除了異地多活,還可以做數(shù)據(jù)中心,所有數(shù)據(jù)在本地機(jī)房crud 異步同步到數(shù)據(jù)中心,數(shù)據(jù)中心再去分發(fā)數(shù)據(jù)給其他機(jī)房,那么數(shù)據(jù)臨時(shí)在本地機(jī)房不可用的時(shí)候,就可以嘗試連接異地機(jī)房或數(shù)據(jù)中心。
靜態(tài)隔離
主要思路是將一些靜態(tài)資源分發(fā)在邊緣服務(wù)器中,因?yàn)槿粘TL問中有很多資源是不會(huì)變的,所以沒必要每次都想從主服務(wù)器上獲取,可以將這些數(shù)據(jù)保存在邊緣服務(wù)器上降低主服務(wù)器的壓力。
有一篇很詳細(xì)的講解參考:全局負(fù)載均衡與CDN內(nèi)容分發(fā)
爬蟲隔離
建立合適的規(guī)則,將爬蟲請求轉(zhuǎn)移到另外的集群 。
爬蟲限流
想要分辨出來一個(gè)訪問是不是爬蟲,可以簡單的使用nginx來分析ua處理
UA介紹
User Agent 簡稱UA,就是用戶代理。通常我們用瀏覽器訪問網(wǎng)站,在網(wǎng)站的日志中,我們的瀏覽器就是一種UA。
禁止特定UA訪問,例如最近有個(gè)網(wǎng)站A抄襲公司主站B的內(nèi)容,除了域名不同,內(nèi)容、圖片等都完全是我們主站的內(nèi)容。出現(xiàn)這種情況,有兩種可能:
無論怎樣,都要禁止這種行為的繼續(xù)。有兩種方法解決:
1)禁止IP
2)禁止UA
從nginx日志觀察,訪問者的代理IP經(jīng)常變,但是訪問UA卻是固定的,因而可以禁止UA。
nginx不僅可以處理ua來分離流量,還可以通過更強(qiáng)大的openresty來完成更復(fù)雜的邏輯,實(shí)現(xiàn)一個(gè)流量網(wǎng)關(guān),軟防火墻。






關(guān)注Java技術(shù)棧看更多干貨


