億級流量架構(gòu)之資源隔離思路與方法
為什么要資源隔離
常見的資源,例如磁盤、網(wǎng)絡(luò)、CPU 等等,都會存在競爭的問題,在構(gòu)建分布式架構(gòu)時(shí),可以將原本連接在一起的組件、模塊、資源拆分開來,以便達(dá)到最大的利用效率或性能。
資源隔離之后,當(dāng)某一部分組件出現(xiàn)故障時(shí),可以隔離故障,方便定位的同時(shí),阻止傳播,避免出現(xiàn)滾雪球以及雪崩效應(yīng)。
線程隔離 進(jìn)程隔離 集群隔離 機(jī)房隔離 讀寫隔離 動靜隔離 爬蟲隔離 等等
線程隔離
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)?Dubbo 有自己的線程模型,可以看看官網(wǎng)提供的模型圖:

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
Tomcat請求線程隔離
Tomcat 是 Servelet 的具體實(shí)現(xiàn),在 Tomcat 請求支持四種請求處理方式分別為:BIO、AIO、NIO、APR
BIO 模式:阻塞式 I/O 操作,表示 Tomcat 使用的是傳統(tǒng) Java。I/O 操作(即 Java.io 包及其子包)。Tomcat7 以下版本默認(rèn)情況下是以 bio 模式運(yùn)行的,由于每個(gè)請求都要創(chuàng)建一個(gè)線程來處理,線程開銷較大,不能處理高并發(fā)的場景,在幾種模式中性能也最低。
NIO 非阻塞網(wǎng)絡(luò)編程原理 cnblogs.com/Courage129/p/14223988.html

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

線程隔離小總結(jié)
資源一旦出現(xiàn)問題,雖然是隔離狀態(tài),想要讓資源重新可用,很難做到不重啟jvm。
線程池內(nèi)部線程如果出現(xiàn) OOM、FullGC、cpu 耗盡等問題也是無法控制的
線程隔離,只能保證在分配線程這個(gè)資源上進(jìn)行隔離,并不能保證整體穩(wěn)定性
進(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,那么其他線程組勢必也會受影響,所以進(jìn)程隔離的思想是,CPU、內(nèi)存等等這些資源也通過不同的虛擬機(jī)來做隔離。

集群隔離
如果系統(tǒng)中某個(gè)業(yè)務(wù)模塊包含像搶購、秒殺、存儲 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ù)化。
解決方案
獨(dú)立拆分模塊 微服務(wù)化
可以使用 hystrix 在微服務(wù)中隔離分布式服務(wù)故障。他可以通過線程和信號量進(jìn)行隔離。
線程池隔離與信號量隔離對比

信號量隔離

最重要的是,信號量的調(diào)用是同步的,也就是說,每次調(diào)用都得阻塞調(diào)用方的線程,直到結(jié)果返回。這樣就導(dǎo)致了無法對訪問做超時(shí)(只能依靠調(diào)用協(xié)議超時(shí),無法主動釋放)
官網(wǎng)對信號量隔離的描述建議
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.
隔離的細(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ù)存儲),在多機(jī)房內(nèi)做異地多活或冷備份、是微服務(wù)數(shù)據(jù)異構(gòu)的放大版本。

數(shù)據(jù)讀寫隔離
通過主從模式,將mysql、redis等數(shù)據(jù)存儲服務(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問中有很多資源是不會變的,所以沒必要每次都想從主服務(wù)器上獲取,可以將這些數(shù)據(jù)保存在邊緣服務(wù)器上降低主服務(wù)器的壓力。
有一篇很詳細(xì)的講解參考:
全局負(fù)載均衡與 CDN 內(nèi)容分發(fā)
cnblogs.com/Courage129/p/14363627.html
爬蟲隔離
建立合適的規(guī)則,將爬蟲請求轉(zhuǎn)移到另外的集群。
目前我們開發(fā)的都是 API 接口,并且多數(shù)都是開放的 API 接口。也就是說,只要有人拿到這個(gè)接口,任何人都可以通過這個(gè) API 接口獲取數(shù)據(jù),如果是網(wǎng)絡(luò)爬蟲請求速度快,獲取的數(shù)據(jù)多,不僅會對服務(wù)器造成影響,不用多久,爬蟲方完全可以用我們 API 的接口來開發(fā)一個(gè)同樣的網(wǎng)站。
開放平臺的 API 接口調(diào)用需要限制其頻率,以節(jié)約服務(wù)器資源和避免惡意的頻繁調(diào)用,在大型互聯(lián)網(wǎng)項(xiàng)目中,對于 web 服務(wù)和網(wǎng)絡(luò)爬蟲的訪問流量能達(dá)到5:1,甚至更高,有的系統(tǒng)有時(shí)候就會因?yàn)榕老x流量過高而導(dǎo)致資源耗盡,服務(wù)不可用。
二是將爬蟲請求轉(zhuǎn)發(fā)到固定地方。
爬蟲限流
登錄/會話限制 下載限流 訪問頻率 ip 限制,黑白名單
想要分辨出來一個(gè)訪問是不是爬蟲,可以簡單的使用 nginx 來分析 ua 處理
UA 介紹
User Agent 簡稱 UA,就是用戶代理。通常我們用瀏覽器訪問網(wǎng)站,在網(wǎng)站的日志中,我們的瀏覽器就是一種 UA。
一種是:它用爬蟲抓取公司主站B的內(nèi)容并放到自己服務(wù)器上顯示;
另一種是:通過將訪問代理至公司主站B,而域名A是盜用者的,騙取流量。
無論怎樣,都要禁止這種行為的繼續(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),軟防火墻。
