轉(zhuǎn)載:京東京麥:微服務(wù)架構(gòu)下的高可用網(wǎng)關(guān)與容錯實(shí)踐(有彩蛋)
本文根據(jù)DBAplus社群第130期線上分享整理而成嫁赏,文末還有好書送哦~
講師介紹
王新棟
京東資深架構(gòu)師
從事京麥平臺的架構(gòu)設(shè)計與開發(fā)工作膳犹。熟悉各種開源軟件架構(gòu)缰猴,在Web開發(fā)邪铲,架構(gòu)優(yōu)化上有較豐富的實(shí)戰(zhàn)經(jīng)歷哭懈。
有多年在NIO領(lǐng)域的設(shè)計俘种、開發(fā)經(jīng)驗嘀韧,對HTTP篇亭、TCP長連接技術(shù)有深入研究與領(lǐng)悟,目前主要致力于移動與PC平臺網(wǎng)關(guān)技術(shù)的優(yōu)化與實(shí)現(xiàn)锄贷。
個人公眾號:程序架道(xindongbook17)
自微服務(wù)概念誕生以來译蒂,眾多的軟件架構(gòu)都在踐行著這一優(yōu)秀的設(shè)計理念。各自的系統(tǒng)在這一指導(dǎo)思想下收獲了優(yōu)雅的可維護(hù)性谊却,但一方面也給接口調(diào)用提出了新的要求柔昼。比如眾多的API調(diào)用急需一個統(tǒng)一的入口來支持客戶端的調(diào)用。在這種情況下API Gateway誕生炎辨,我們將接入捕透、路由、限流等功能統(tǒng)一由網(wǎng)關(guān)負(fù)責(zé)碴萧,各自的服務(wù)提供方專注于業(yè)務(wù)邏輯的實(shí)現(xiàn)乙嘀,從而給客戶端調(diào)用提供了一個穩(wěn)健的服務(wù)調(diào)用環(huán)境。之后破喻,我們在網(wǎng)關(guān)大調(diào)用量的情況下虎谢,還要保證網(wǎng)關(guān)的可降級、可限流曹质、可隔離等等一系列容錯能力婴噩。
今天借社群這個平臺擎场,跟大家分享微服務(wù)下京麥開放平臺的網(wǎng)關(guān)實(shí)現(xiàn),以及我們?nèi)绾慰沽考该А⑷绾卧诖笤L問量的情況下做容錯處理迅办。重點(diǎn)介紹容錯的方法及每種容錯方法的使用場景與經(jīng)驗。
一章蚣、網(wǎng)關(guān)
這里說的網(wǎng)關(guān)是指API網(wǎng)關(guān)礼饱,直面意思是將所有API調(diào)用統(tǒng)一接入到API網(wǎng)關(guān)層,由網(wǎng)關(guān)層統(tǒng)一接入和輸出究驴。一個網(wǎng)關(guān)的基本功能有:統(tǒng)一接入镊绪、安全防護(hù)、協(xié)議適配洒忧、流量管控蝴韭、長短鏈接支持、容錯能力熙侍。有了網(wǎng)關(guān)之后榄鉴,各個API服務(wù)提供團(tuán)隊可以專注于自己的的業(yè)務(wù)邏輯處理,而API網(wǎng)關(guān)更專注于安全蛉抓、流量庆尘、路由等問題。
單體應(yīng)用
業(yè)務(wù)簡單巷送、團(tuán)隊組織很小時驶忌,我們常常把功能都集中于一個應(yīng)用中,統(tǒng)一部署笑跛、統(tǒng)一測試付魔,玩得不易樂乎。但隨著業(yè)務(wù)迅速發(fā)展飞蹂,組織成員日益增多几苍,我們再將所有的功能集中到一個Tomcat中去時,每當(dāng)更新一個功能模塊陈哑,勢必要更新所有的程序妻坝,搞不好,還要牽一發(fā)動全身惊窖,實(shí)在難以維護(hù)刽宪。
微服務(wù)
單體應(yīng)用滿足不了我們逐漸增長的擴(kuò)展需求之后,微服務(wù)就出現(xiàn)了爬坑,它將原來集中于一體的比如商品功能纠屋、訂單功能、用戶功能拆分出去盾计,各自有各自的自成體系的發(fā)布售担、運(yùn)維等,這樣就解決了在單體應(yīng)用下的弊端署辉。
API網(wǎng)關(guān)
進(jìn)行微服務(wù)后族铆,原先客戶端調(diào)用服務(wù)端的地方就要有N多個URL地址,包括商品的哭尝、訂單的哥攘、用戶的。這時就必須要有個統(tǒng)一的入口和出口材鹦,這種情況下逝淹,我們的API Gateway就出現(xiàn)了,很好地幫助我們解決了微服務(wù)下客戶端調(diào)用的問題桶唐。
泛化調(diào)用
對于普通的RPC調(diào)用栅葡,我要拿到服務(wù)端提供的class或者jar包,這樣過于繁重尤泽,更不好維護(hù)欣簇。不過成熟的RPC框架都支持泛化調(diào)用,我們的網(wǎng)關(guān)就是基于這種泛化調(diào)用來實(shí)現(xiàn)的坯约。服務(wù)端開放出來他們的API文檔熊咽,我們拿到接口、參數(shù)闹丐、參數(shù)類型通過泛化調(diào)用到服務(wù)端程序横殴。?
public Object $invoke(String method, String[] parameterTypes, Object[] args);
二、容錯
容錯卿拴,這個詞的理解滥玷,書面意思就是可以容下錯誤,不讓錯誤再次擴(kuò)張巍棱,讓這個錯誤產(chǎn)生的影響在一個固定的邊界之內(nèi)惑畴,“千里之堤,毀于蟻穴”航徙,我們采用容錯的方式就是不讓這種蟻穴繼續(xù)變大如贷。在工作中,降級到踏、限流杠袱、熔斷器、超時重試等都是常見的容錯方法窝稿。
抗量
所謂的抗量楣富,其實(shí)就是增大我們系統(tǒng)的吞吐量。所以容錯的第一步就是系統(tǒng)要能抗量伴榔,沒有量的情況下幾乎用不到容錯纹蝴。我們的容器使用的是Tomcat庄萎,在傳統(tǒng)的BIO模型下,一請求一線程塘安,在機(jī)器線程資源有限的情況下是沒有辦法來實(shí)現(xiàn)我們的目標(biāo)糠涛。NIO給我們提供了這個機(jī)會,基于NIO的機(jī)制兼犯,利用較少的線程來處理更多的連接忍捡。連接多不可怕,通過調(diào)整機(jī)器的參數(shù)一臺8c8g的機(jī)器切黔,超過10w是不成問題的砸脊。Tomcat的Conector修改成NIO后我們再從代碼層面引入了Servlet3,它是從Tomcat7以后支持的纬霞,NIO是Tomcat6以后就支持的凌埂。
利用Servlet3的特性,所有的request和response都由Tomcat的工作線程來處理险领,我們將業(yè)務(wù)邏輯異步到別的業(yè)務(wù)線程中去侨舆。異步環(huán)境下,可以提高單位時間內(nèi)的吞吐量绢陌。所有的Servlet請求都是由Tomcat的Executor線程池的線程處理的挨下,也就是Tomcat的工作線程。這些線程處理的時間越短越好脐湾,越能迅速地將線程歸還給Executor線程池〕舭剩現(xiàn)在Servlet支持異步后就能將耗時的操作,比如有RPC請求的交給業(yè)務(wù)線程池來處理秤掌,使得Tomcat工作線程可立即歸還給Tomcat工作線程池愁铺。另外,將業(yè)務(wù)異步處理之后闻鉴,我們可以對業(yè)務(wù)線程池進(jìn)行線程池隔離茵乱,這樣就避免了因一個業(yè)務(wù)性能問題而影響了其它的業(yè)務(wù)。
總結(jié)一下異步的優(yōu)勢:
1孟岛、可以用來做消息推送瓶竭,通過Nginx做代理,設(shè)置連接超時時間渠羞,客戶端通過心跳探測斤贰。
2、提高吞吐量次询,就像上面說的荧恍。
3、請求線程和業(yè)務(wù)線程分開屯吊,從而可以通過業(yè)務(wù)線程池對業(yè)務(wù)線程做隔離送巡。
關(guān)于Servlet3的異步原理與實(shí)踐可以參看筆者以前寫的文章《Servlet3異步原理與實(shí)踐》摹菠。
脫離DB
這里不是說DB的性能不行,分庫分表授艰、DB集群化之后辨嗽,在一定量的情況下是沒有問題的世落。但是淮腾,如果從抗量的角度說的話,為何不使用Redis呢屉佳?如果軟件架構(gòu)里面有一種銀彈的話谷朝,那么Redis就是這種銀彈。
另外一個脫離DB的原因是武花,每當(dāng)大促備戰(zhàn)前夕我們一項重點(diǎn)的工作就是優(yōu)化慢SQL圆凰,但它就像小強(qiáng)一樣生命力是那樣的頑強(qiáng),殺不絕体箕。如果有那么一個慢SQL专钉,平時是沒有問題的,比如一個查詢大字段的SQL累铅,平時量小不會暴露問題跃须,但量一上來了,就是個災(zāi)難娃兽。
再就是我們的網(wǎng)關(guān)菇民,包括接入、分發(fā)投储、限流等這些功能都應(yīng)該是很輕的第练,所以我們就通過數(shù)據(jù)異構(gòu)的方式把數(shù)據(jù)重新轉(zhuǎn)載到Redis中,而且是將數(shù)據(jù)持久化到Redis里面去玛荞。當(dāng)然娇掏,使用Redis的過程中也需要注意大key,大訪問量下也能讓集群趴下勋眯。
還有一個很重要的原因婴梧,我覺得必須說一下,我們使用的DB是MySQL凡恍,鑒于MySQL的failover機(jī)制生效時間總是要長于Redis集群志秃,最后就是因為DB切換的時候,常常伴隨Web應(yīng)用服務(wù)器要重啟嚼酝,將原來的連接釋放掉浮还,才能方便使用新的數(shù)據(jù)庫連接。
多級緩存
最簡單的緩存就是查一次數(shù)據(jù)庫然后將數(shù)據(jù)寫入緩存闽巩,比如在Redis中設(shè)置過期時間钧舌。因為有過期失效担汤,因此我們要關(guān)注下緩存的穿透率,這個穿透率的計算公式洼冻,比如查詢方法queryOrder(調(diào)用次數(shù)1000/1s)里面嵌套查詢DB方法query Product From Db(調(diào)用次數(shù)300/s)崭歧,那么Redis的穿透率就是300/1000,在這種使用緩存的方式下撞牢,是要重視穿透率的率碾,穿透率大了說明緩存的效果不好。
還有一種使用緩存的方式就是將緩存持久化屋彪,也就是不設(shè)置過期時間所宰,這個會面臨一個數(shù)據(jù)更新的問題。
一般有兩種辦法畜挥,一個是利用時間戳仔粥,查詢默認(rèn)以Redis為主,每次設(shè)置數(shù)據(jù)的時候放入一個時間戳蟹但,每次讀取數(shù)據(jù)的時候用系統(tǒng)當(dāng)前時間和上次設(shè)置的這個時間戳做對比,比如超過5分鐘华糖,那么就再查一次數(shù)據(jù)庫,這樣可以保證Redis里面永遠(yuǎn)有數(shù)據(jù)磕蛇,一般是對DB的一種容錯方法。
還有一個就是讓Redis真正作為DB來使用十办。就是圖里畫的通過訂閱數(shù)據(jù)庫的binlog秀撇,通過數(shù)據(jù)異構(gòu)系統(tǒng)將數(shù)據(jù)推送給緩存,同時將將緩存設(shè)置為多級向族『茄啵可以通過使用jvmcache作為應(yīng)用內(nèi)的一級緩存,一般是體積小件相,訪問頻率大的更適合這種jvmcache方式再扭,將一套Redis作為二級remoto緩存,另外的最外層三級Redis作為持久化緩存夜矗。
超時與重試
超時與重試機(jī)制也是容錯的一種方法泛范,凡是發(fā)生RPC調(diào)用的地方,比如讀取Redis紊撕、DB罢荡、MQ等,因為網(wǎng)絡(luò)故障或者是所依賴的服務(wù)故障了,長時間不能返回結(jié)果区赵,就會導(dǎo)致線程增加惭缰,加大CPU負(fù)載,甚至導(dǎo)致雪崩笼才。所以對每一個RPC調(diào)用都要設(shè)置超時時間漱受。
對于強(qiáng)依賴RPC調(diào)用資源的情況,還要有重試機(jī)制骡送,但重試的次數(shù)建議1-2次昂羡,另外如果有重試,超時時間還要相應(yīng)都調(diào)小各谚,比如重試1次紧憾,那么一共是發(fā)生2次調(diào)用到千。如果超時時間配置的是2s昌渤,那么客戶端就要等待4s才能返回。因此重試+超時的方式憔四,超時時間要調(diào)小。
這里也再談一下一次PRC調(diào)用的時間都消耗在哪些環(huán)節(jié)潜支。一次正常的調(diào)用統(tǒng)計的耗時主要包括:
①調(diào)用端RPC框架執(zhí)行時間 + ②網(wǎng)絡(luò)發(fā)送時間 + ③服務(wù)端RPC框架執(zhí)行時間 + ④服務(wù)端業(yè)務(wù)代碼時間。
調(diào)用方和服務(wù)方都有各自的性能監(jiān)控络断,比如調(diào)用方tp99是500ms弱判,服務(wù)方tp99是100ms昌腰,找了網(wǎng)絡(luò)組的同事確認(rèn)網(wǎng)絡(luò)沒有問題遭商。那么時間都花在什么地方了呢劫流?兩種原因:客戶端調(diào)用方大审,還有一個原因是網(wǎng)絡(luò)發(fā)生TCP重傳徒扶,所以要注意這兩點(diǎn)姜骡。RPC耗時詳細(xì)內(nèi)容可以參看筆者寫的另外一篇文章《一次RPC調(diào)用時間都去哪兒了》圈澈。
熔斷
熔斷技術(shù)可以說是一種“智能化的容錯”康栈,當(dāng)調(diào)用滿足失敗次數(shù)啥么,失敗比例就會觸發(fā)熔斷器打開悬荣,有程序自動切斷當(dāng)前的RPC調(diào)用氯迂,來防止錯誤進(jìn)一步擴(kuò)大嚼蚀。實(shí)現(xiàn)一個熔斷器主要是考慮三種模式:關(guān)閉驰坊、打開拳芙、半開。各個狀態(tài)的轉(zhuǎn)換如下圖悴务。
在了解了熔斷器的狀態(tài)機(jī)制后,我們可以自己來實(shí)現(xiàn)一個熔斷器染服。當(dāng)然也可以使用開源的解決方案,比如Hystrix中的breaker秉颗。下圖是一個熔斷器打開關(guān)閉的示意圖送矩。
這里要談的是熔斷器的使用注意項菇怀。我們在處理異常時敏释,要根據(jù)具體的業(yè)務(wù)情況來決定處理方式,比如我們調(diào)用商品接口靠汁,對方只是臨時做了降級處理,那么作為網(wǎng)關(guān)調(diào)用就要切到可替換的服務(wù)上來執(zhí)行或者獲取托底數(shù)據(jù)踢星,給用戶友好提示隙咸。還有要區(qū)分異常的類型藏否,比如依賴的服務(wù)崩潰了副签,這個可能需要花費(fèi)比較久的時間來解決淆储,也可能是由于服務(wù)器負(fù)載臨時過高導(dǎo)致超時。作為熔斷器應(yīng)該能夠甄別這種異常類型慈鸠,從而根據(jù)具體的錯誤類型調(diào)整熔斷策略青团。增加手動設(shè)置,在失敗的服務(wù)恢復(fù)時間不確定的情況下娃肿,管理員可以手動強(qiáng)制切換熔斷狀態(tài)料扰。最后,熔斷器的使用場景是調(diào)用可能失敗的遠(yuǎn)程服務(wù)程序或者共享資源拯钻。如果是本地緩存本地私有資源粪般,使用熔斷器則會增加系統(tǒng)的額外開銷亩歹。還要注意小作,熔斷器不能作為應(yīng)用程序中業(yè)務(wù)邏輯的異常處理替代品。
關(guān)于熔斷的原理與實(shí)踐可以參照這篇文章《Hystrix熔斷器技術(shù)解析》础拨。
線程池隔離
在抗量這個環(huán)節(jié)滔蝉,Servlet3異步時蝠引,有提到過線程隔離螃概。線程隔離的之間優(yōu)勢就是防止級聯(lián)故障吊洼,甚至是雪崩。當(dāng)網(wǎng)關(guān)調(diào)用N多個接口服務(wù)的時候豺鼻,我們要對每個接口進(jìn)行線程隔離谬莹,比如我們有調(diào)用訂單届良、商品、用戶送悔。那么訂單的業(yè)務(wù)不能夠影響到商品和用戶的請求處理爪模。如果不做線程隔離欠啤,當(dāng)訪問訂單服務(wù)出現(xiàn)網(wǎng)絡(luò)故障導(dǎo)致延時,線程積壓最終導(dǎo)致整個服務(wù)CPU負(fù)載滿屋灌,就是我們說的服務(wù)全部不可用了洁段,有多少機(jī)器都會被此刻的請求塞滿。那么共郭,有了線程隔離就會使得我們的網(wǎng)關(guān)能保證局部問題不會影響全局祠丝。
降級疾呻、限流
關(guān)于降級限流的方法業(yè)界都已經(jīng)有很成熟的方法了,比如Failback機(jī)制写半,限流方法令牌桶铃慷、漏桶赁温、信號量等稚疹。這里談一下我們的一些經(jīng)驗。
降級一般都是由統(tǒng)一配置中心的降級開關(guān)來實(shí)現(xiàn)的柱恤,那么當(dāng)有很多個接口來自同一個提供方仑鸥,這個提供方的系統(tǒng)或這機(jī)器所在機(jī)房網(wǎng)絡(luò)出現(xiàn)了問題摄职,我們就要有一個統(tǒng)一的降級開關(guān)迫悠,不然就要一個接口一個接口地來降級鞠抑,也就是要對業(yè)務(wù)類型有一個大閘刀。
還有就是降級切記暴力降級,什么是暴力降級?比如把論壇功能降調(diào),結(jié)果用戶顯示一個大白板哥倔,我們要實(shí)現(xiàn)緩存住一些數(shù)據(jù)缭黔,也就是有托底數(shù)據(jù)喇伯。限流一般分為分布式限流和單機(jī)限流箩朴,如果實(shí)現(xiàn)分布式限流的話就要一個公共的后端存儲服務(wù)比如Redis,在大Nginx節(jié)點(diǎn)上利用Lua讀取Redis配置信息兽泣。我們現(xiàn)在的限流都是單機(jī)限流,并沒有實(shí)施分布式限流屋剑。
網(wǎng)關(guān)監(jiān)控與統(tǒng)計
API網(wǎng)關(guān)是一個串行的調(diào)用芋簿,每一步發(fā)生的異常都要記錄下來肪康,統(tǒng)一存儲到一個地方比如Elasticserach中抵皱,便于后續(xù)對調(diào)用異常的分析。鑒于公司Docker申請都是統(tǒng)一分配,而且分配之前Docker上已經(jīng)存在3個agnet了,不再允許增加蜕乡。我們自己實(shí)現(xiàn)了一個agnet程序奸绷,來負(fù)責(zé)采集服務(wù)器上面的日志輸出,然后發(fā)送到kafka集群层玲,再消費(fèi)到Elasticserach中号醉,通過Web查詢。現(xiàn)在做的追蹤功能還比較簡單辛块,這塊還需要繼續(xù)豐富畔派。
三、總結(jié)
網(wǎng)關(guān)基本功能有統(tǒng)一接入憨降、安全防護(hù)父虑、協(xié)議適配等。這篇文章里我們并沒有講如何來實(shí)現(xiàn)這些基本的功能授药,因為現(xiàn)在有很多成熟的解決方案可以直接拿過來使用士嚎,比如Spring Cloud這種全家桶里面的很多組件,Mashape的API層Kong等悔叽。我們更關(guān)注的是實(shí)現(xiàn)了這些網(wǎng)關(guān)的基本功能之后莱衩,如何保證一個網(wǎng)關(guān)的運(yùn)行,在大訪問量的情況下如何能更好的支持客戶端的調(diào)用娇澎,在突發(fā)情況下又是如何及時地響應(yīng)這種突然的異常笨蚁,如何將錯誤最小化,防止級聯(lián)故障。我們的重點(diǎn)關(guān)注的是網(wǎng)關(guān)容錯方面的經(jīng)驗與實(shí)踐括细。
參考資料
https://martinfowler.com/articles/microservices.htm
lhttps://tomcat.apache.org/tomcat-7.0-doc/config/http.html
http://www.cnblogs.com/davenkin/p/async-servlet.html
《Tomcat內(nèi)核設(shè)計剖析》
Q&A
Q1:異構(gòu)數(shù)據(jù)庫推送數(shù)據(jù)到Redis伪很,是表數(shù)據(jù)還是聯(lián)合查詢后的數(shù)據(jù)?
A1:熱點(diǎn)數(shù)據(jù)奋单,不會是全量查詢表锉试,根據(jù)業(yè)務(wù)條件查詢出來的結(jié)果集合。你可以理解為視圖那樣的方式览濒,只是落地了另外一個庫呆盖。
Q2:8c8g的機(jī)器,連接超過10w贷笛,機(jī)器調(diào)整哪些參數(shù)应又?
A2:
#?查看當(dāng)前用戶允許TCP打開的文件句柄最大數(shù)
ulimit?-n
#?修改文件句柄
vim?/etc
curity
mits.conf
*?soft?nofile?655350
*?hard?nofile?655350
注意:
soft?nofile?(軟限制)是指Linux在當(dāng)前系統(tǒng)能夠承受的范圍內(nèi)進(jìn)一步限制用戶同時打開的文件數(shù);
hard?nofile?(硬限制)是根據(jù)系統(tǒng)硬件資源狀況(主要是系統(tǒng)內(nèi)存)計算出來的系統(tǒng)最多可同時打開的文件數(shù)量乏苦;
通常軟限制小于或等于硬限制株扛。
Q3:業(yè)務(wù)線程池使用JDK自帶的還是貴公司研發(fā)的?如何優(yōu)雅地關(guān)停邑贴,尤其是還有事務(wù)在處理席里?
A3:JDK自帶的。事務(wù)拢驾,我們主要是考慮最終一致性就好了奖磁。互聯(lián)網(wǎng)應(yīng)用CAP里面 繁疤,保證AP咖为。
Q4:Tomcat上還要調(diào)整那些參數(shù)?
A4:Tomcat 改成 NIO稠腊。1000并發(fā)躁染,目前線上是這樣配置的。
直播鏈接
https://m.qlchat.com/topic/details?topicId=2000000447502593