億級(jí)流量架構(gòu)之資源隔離思路與方法

來(lái)源:urlify.cn/2QrEFv
為什么要資源隔離
常見的資源,例如磁盤、網(wǎng)絡(luò)、CPU等等,都會(huì)存在競(jìng)爭(zhēng)的問(wèn)題,在構(gòu)建分布式架構(gòu)時(shí),可以將原本連接在一起的組件、模塊、資源拆分開來(lái),以便達(dá)到最大的利用效率或性能。資源隔離之后,當(dāng)某一部分組件出現(xiàn)故障時(shí),可以隔離故障,方便定位的同時(shí),阻止傳播,避免出現(xiàn)滾雪球以及雪崩效應(yīng)。
常見的隔離方式有:
線程隔離 進(jìn)程隔離 集群隔離 機(jī)房隔離 讀寫隔離 動(dòng)靜隔離 爬蟲隔離 等等
線程隔離
網(wǎng)絡(luò)上很多帖子,大多是從框架開始聊的,這兒說(shuō)人話其實(shí)就是對(duì)線程進(jìn)行治理,把核心業(yè)務(wù)線程與非核心業(yè)務(wù)線程隔開,不同的業(yè)務(wù)需要的線程數(shù)量不同,可以設(shè)置不同的線程池,來(lái)舉一些框架中應(yīng)用的例子,例如Netty中的主從多線程、Tomcat請(qǐng)求隔離、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è)簡(jiǎn)單測(cè)試:
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并沒(méi)有直接使用Netty的io線程來(lái)處理業(yè)務(wù),可以簡(jiǎn)單在生產(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ù)端接收到請(qǐng)求后,通過(guò)調(diào)度器(Dispatcher)分發(fā)到不同的線程池,也簡(jiǎn)單做一些關(guān)于調(diào)度器(Dispatcher)總結(jié):
Dispatcher調(diào)度器可以配置消息的處理線程:
all所有消息都派發(fā)到線程池,包括請(qǐng)求,響應(yīng),連接事件,斷開事件,心跳等。direct所有消息都不派發(fā)到線程池,全部在 IO 線程上直接執(zhí)行。message只有請(qǐng)求響應(yīng)消息派發(fā)到線程池,其它連接斷開事件,心跳等消息,直接在 IO 線程上執(zhí)行。execution只有請(qǐng)求消息派發(fā)到線程池,不含響應(yīng),響應(yīng)和其它連接斷開事件,心跳等消息,直接在 IO 線程上執(zhí)行。connection在 IO 線程上,將連接斷開事件放入隊(duì)列,有序逐個(gè)執(zhí)行,其它消息派發(fā)到線程池。
通過(guò)看源碼可以知道,Dubbo默認(rèn)使用的線程池是FixedThreadPool ,線程數(shù)默認(rèn)為200 ;
Tomcat請(qǐng)求線程隔離
Tomcat是Servelet的具體實(shí)現(xiàn),在Tomcat請(qǐng)求支持四種請(qǐng)求處理方式分別為:BIO、AIO、NIO、APR
BIO模式:阻塞式I/O操作,表示Tomcat使用的是傳統(tǒng)Java。I/O操作(即Java.io包及其子包)。Tomcat7以下版本默認(rèn)情況下是以bio模式運(yùn)行的,由于每個(gè)請(qǐng)求都要?jiǎng)?chuàng)建一個(gè)線程來(lái)處理,線程開銷較大,不能處理高并發(fā)的場(chǎng)景,在幾種模式中性能也最低。
NIO模式:同步非阻塞I/O操作,是一個(gè)基于緩沖區(qū)、并能提供非阻塞I/O操作的API,它擁有比傳統(tǒng)I/O操作具有更好的并發(fā)性能。
在Tomcat7版本之后,Tomcat把連接介入和業(yè)務(wù)處理拆分成兩個(gè)線程池來(lái)處理,即:

可以使用獨(dú)立的線程池來(lái)維護(hù)servlet的創(chuàng)建。連接器connector能介入的請(qǐng)求肯定比業(yè)務(wù)復(fù)雜的servlet處理的個(gè)數(shù)要多,在中間,Tomcat加入了隊(duì)列,來(lái)等待servlet線程池空閑。這兩步是Tomcat內(nèi)核完成的,在一階段無(wú)法區(qū)分具體業(yè)務(wù)或資源,所以只能在連接介入,servlet初始化完成后我們根據(jù)自己的業(yè)務(wù)線去劃分獨(dú)立的連接池。
這樣做,獨(dú)立的業(yè)務(wù)或資源中如果出現(xiàn)崩潰,不會(huì)影響其他的業(yè)務(wù)線程,從而達(dá)到資源隔離和服務(wù)降級(jí)的效果。
在使用了servlet3之后,系統(tǒng)線程隔離變得更靈活了。可以劃分核心業(yè)務(wù)隊(duì)列和非核心業(yè)務(wù)隊(duì)列:

線程隔離小總結(jié)
資源一旦出現(xiàn)問(wèn)題,雖然是隔離狀態(tài),想要讓資源重新可用,很難做到不重啟jvm。 線程池內(nèi)部線程如果出現(xiàn)OOM、FullGC、cpu耗盡等問(wèn)題也是無(wú)法控制的 線程隔離,只能保證在分配線程這個(gè)資源上進(jìn)行隔離,并不能保證整體穩(wěn)定性
進(jìn)程隔離
進(jìn)程隔離這種思想其實(shí)并不陌生,Linux操作系統(tǒng)中,利用文件管理系統(tǒng)將各個(gè)進(jìn)程的虛擬內(nèi)存與實(shí)際的物理內(nèi)存映射起來(lái),這樣做的好處是避免不同的進(jìn)程之間相互影響,而在分布式系統(tǒng)中,線程隔離不能完全隔離故障避免雪崩,例如某個(gè)線程組耗盡內(nèi)存導(dǎo)致OOM,那么其他線程組勢(shì)必也會(huì)受影響,所以進(jìn)程隔離的思想是,CPU、內(nèi)存等等這些資源也通過(guò)不同的虛擬機(jī)來(lái)做隔離。
具體操作是,將業(yè)務(wù)邏輯進(jìn)行拆分成多個(gè)子系統(tǒng)(拆分原則可以參考:Redis集群拆分原則之AKF),實(shí)現(xiàn)物理隔離,當(dāng)某一個(gè)子系統(tǒng)出現(xiàn)問(wèn)題,不會(huì)影響到其他子系統(tǒng)。
集群隔離
如果系統(tǒng)中某個(gè)業(yè)務(wù)模塊包含像搶購(gòu)、秒殺、存儲(chǔ)I/O密集度高、網(wǎng)絡(luò)I/o高、計(jì)算I/O高這類需求的時(shí)候,很容易在并發(fā)量高的時(shí)候因?yàn)檫@種功能把整個(gè)模塊占有的資源全部耗盡,導(dǎo)致響應(yīng)編碼甚至節(jié)點(diǎn)不可用。像上圖的的拆分之后,如果某一天購(gòu)物人數(shù)瞬間暴增,電商交易功能模塊可能受影響,損壞后導(dǎo)致電商模塊其他的瀏覽查詢也無(wú)法使用,因此就要建立集群進(jìn)行隔離,具體來(lái)說(shuō)就是繼續(xù)拆分模塊,將功能微服務(wù)化。
解決方案
獨(dú)立拆分模塊 微服務(wù)化
可以使用hystrix在微服務(wù)中隔離分布式服務(wù)故障。他可以通過(guò)線程和信號(hào)量進(jìn)行隔離。
線程池隔離與信號(hào)量隔離對(duì)比
這兒同上面的線程隔離,不多贅述,簡(jiǎn)單敘述一下hystrix的兩種隔離方式的區(qū)別:
| 隔離方式 | 是否支持超時(shí) | 是否支持熔斷 | 隔離原理 | 是否是異步調(diào)用 | 資源消耗 |
|---|---|---|---|---|---|
| 線程池隔離 | 支持,可直接返回 | 支持,當(dāng)線程池到達(dá)maxSize后,再請(qǐng)求會(huì)觸發(fā)fallback接口進(jìn)行熔斷 | 每個(gè)服務(wù)單獨(dú)用線程池 | 可以是異步,也可以是同步。看調(diào)用的方法 | 大,大量線程的上下文切換,容易造成機(jī)器負(fù)載高 |
| 信號(hào)量隔離 | 不支持,如果阻塞,只能通過(guò)調(diào)用協(xié)議(如:socket超時(shí)才能返回) | 支持,當(dāng)信號(hào)量達(dá)到maxConcurrentRequests后。再請(qǐng)求會(huì)觸發(fā)fallback | 通過(guò)信號(hào)量的計(jì)數(shù)器 | 同步調(diào)用,不支持異步 | 小,只是個(gè)計(jì)數(shù)器 |
信號(hào)量隔離
說(shuō)人話就是,很多線程涌過(guò)來(lái),要去獲得信號(hào)量,獲得了才能繼續(xù)執(zhí)行,否則先進(jìn)入隊(duì)列等待或者直接fallback回調(diào)

最重要的是,信號(hào)量的調(diào)用是同步的,也就是說(shuō),每次調(diào)用都得阻塞調(diào)用方的線程,直到結(jié)果返回。這樣就導(dǎo)致了無(wú)法對(duì)訪問(wèn)做超時(shí)(只能依靠調(diào)用協(xié)議超時(shí),無(wú)法主動(dòng)釋放)
官網(wǎng)對(duì)信號(hào)量隔離的描述建議
Generally the only time you should use semaphore isolation for 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í)用線程池做隔離開銷過(guò)大 通常這種都是非網(wǎng)絡(luò)調(diào)用的情況下
機(jī)房隔離
機(jī)房隔離主要目的有兩個(gè),一方面是將不同區(qū)域的用戶數(shù)據(jù)隔離到不同的地區(qū),例如湖北的數(shù)據(jù)放在湖北的服務(wù)器,浙江的放在浙江服務(wù)器,等等,這樣能解決數(shù)據(jù)容量大,計(jì)算密集,i/o(網(wǎng)絡(luò))密集度高的問(wèn)題,相當(dāng)于將流量分在了各個(gè)區(qū)域。
另一方面,機(jī)房隔離也是為了保證安全性,所有數(shù)據(jù)都放在一個(gè)地方,如果發(fā)生自然災(zāi)害或者爆炸等災(zāi)害時(shí),數(shù)據(jù)將全都丟失,所以把服務(wù)建立整體副本(計(jì)算服務(wù)、數(shù)據(jù)存儲(chǔ)),在多機(jī)房?jī)?nèi)做異地多活或冷備份、是微服務(wù)數(shù)據(jù)異構(gòu)的放大版本。
如果機(jī)房層面出現(xiàn)問(wèn)題的時(shí)候,可以通過(guò)智能dns、httpdns、負(fù)載均衡等技術(shù)快速切換,讓區(qū)域用戶盡量不收影響。

數(shù)據(jù)讀寫隔離
通過(guò)主從模式,將mysql、redis等數(shù)據(jù)存儲(chǔ)服務(wù)集群化,讀寫分離,那么在寫入數(shù)據(jù)不可用的時(shí)候,也可以通過(guò)重試機(jī)制 臨時(shí)通過(guò)其他節(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問(wèn)中有很多資源是不會(huì)變的,所以沒(méi)必要每次都想從主服務(wù)器上獲取,可以將這些數(shù)據(jù)保存在邊緣服務(wù)器上降低主服務(wù)器的壓力。
有一篇很詳細(xì)的講解參考:全局負(fù)載均衡與CDN內(nèi)容分發(fā)
爬蟲隔離
建立合適的規(guī)則,將爬蟲請(qǐng)求轉(zhuǎn)移到另外的集群 。
目前我們開發(fā)的都是API接口,并且多數(shù)都是開放的API接口。也就是說(shuō),只要有人拿到這個(gè)接口,任何人都可以通過(guò)這個(gè)API接口獲取數(shù)據(jù),如果是網(wǎng)絡(luò)爬蟲請(qǐng)求速度快,獲取的數(shù)據(jù)多,不僅會(huì)對(duì)服務(wù)器造成影響,不用多久,爬蟲方完全可以用我們API的接口來(lái)開發(fā)一個(gè)同樣的網(wǎng)站,開放平臺(tái)的API接口調(diào)用需要限制其頻率,以節(jié)約服務(wù)器資源和避免惡意的頻繁調(diào)用,在大型互聯(lián)網(wǎng)項(xiàng)目中,對(duì)于web服務(wù)和網(wǎng)絡(luò)爬蟲的訪問(wèn)流量能達(dá)到5:1,甚至更高,有的系統(tǒng)有時(shí)候就會(huì)因?yàn)榕老x流量過(guò)高而導(dǎo)致資源耗盡,服務(wù)不可用。解決策略大致兩個(gè)方面:
一是限流,限制訪問(wèn)的頻率;
二是將爬蟲請(qǐng)求轉(zhuǎn)發(fā)到固定地方。
爬蟲限流
登錄/會(huì)話限制 下載限流 訪問(wèn)頻率 ip限制,黑白名單
想要分辨出來(lái)一個(gè)訪問(wèn)是不是爬蟲,可以簡(jiǎn)單的使用nginx來(lái)分析ua處理
UA介紹
User Agent 簡(jiǎn)稱UA,就是用戶代理。通常我們用瀏覽器訪問(wèn)網(wǎng)站,在網(wǎng)站的日志中,我們的瀏覽器就是一種UA。
禁止特定UA訪問(wèn),例如最近有個(gè)網(wǎng)站A抄襲公司主站B的內(nèi)容,除了域名不同,內(nèi)容、圖片等都完全是我們主站的內(nèi)容。出現(xiàn)這種情況,有兩種可能:
一種是:它用爬蟲抓取公司主站B的內(nèi)容并放到自己服務(wù)器上顯示;
另一種是:通過(guò)將訪問(wèn)代理至公司主站B,而域名A是盜用者的,騙取流量。
無(wú)論怎樣,都要禁止這種行為的繼續(xù)。有兩種方法解決:
1)禁止IP
2)禁止UA
從nginx日志觀察,訪問(wèn)者的代理IP經(jīng)常變,但是訪問(wèn)UA卻是固定的,因而可以禁止UA。
nginx不僅可以處理ua來(lái)分離流量,還可以通過(guò)更強(qiáng)大的openresty來(lái)完成更復(fù)雜的邏輯,實(shí)現(xiàn)一個(gè)流量網(wǎng)關(guān),軟防火墻。
2. POI導(dǎo)出excel:設(shè)置字體顏色、行高自適應(yīng)、列寬自適應(yīng)、鎖住單元格、合并單元格
最近面試BAT,整理一份面試資料《Java面試BATJ通關(guān)手冊(cè)》,覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫(kù)、數(shù)據(jù)結(jié)構(gòu)等等。
獲取方式:點(diǎn)“在看”,關(guān)注公眾號(hào)并回復(fù) Java 領(lǐng)取,更多內(nèi)容陸續(xù)奉上。
文章有幫助的話,在看,轉(zhuǎn)發(fā)吧。
謝謝支持喲 (*^__^*)

