唯品会API网关设计与实践--转
原文地址:https://609518.kuaizhan.com/86/70/p4108366952248f
劉璟宇Leo
唯品會資深研發(fā)工程師,在大型高性能分布式系統(tǒng)設(shè)計和開發(fā)方面有豐富的經(jīng)驗(yàn)。目前在唯品會平臺與架構(gòu)部負(fù)責(zé)唯品會API網(wǎng)關(guān)和服務(wù)安全方面的設(shè)計、開發(fā)、運(yùn)營工作。
內(nèi)容解析
1. 為什么引入網(wǎng)關(guān)
唯品會是一家專門做特賣的網(wǎng)站,唯品會網(wǎng)站是一個巨大型的網(wǎng)站,每張頁面背后,都有多個服務(wù)提供靜態(tài)資源和動態(tài)數(shù)據(jù)。
這是唯品會網(wǎng)站上一張商品詳情頁面,內(nèi)容是一款女式針織衫。頁面里,除去靜態(tài)頁面、圖片之外,有些動態(tài)內(nèi)容:商品價格、促銷提示語、產(chǎn)品介紹、商品庫存等。每個部分都會從后端的一個或幾個服務(wù)拉取數(shù)據(jù)。
在唯品會公司內(nèi)部,已經(jīng)采用服務(wù)化的方式把服務(wù)進(jìn)行了拆分,內(nèi)部服務(wù)之間采用基于thrift的二進(jìn)制協(xié)議通訊。這些服務(wù)不能直接對外部提供服務(wù)。
在引入API網(wǎng)關(guān)前,我們在外部app、瀏覽器和內(nèi)部服務(wù)之間會做一層webapp,起到兩個作用:
一個是從外部的http協(xié)議,適配到內(nèi)部的二進(jìn)制協(xié)議。
另一個是對數(shù)據(jù)進(jìn)行聚合。
另外這些webapp里面還集成了如oauth等的一些公共服務(wù)。
由于唯品會網(wǎng)站的業(yè)務(wù)眾多、業(yè)務(wù)量也非常大,這種webapp的數(shù)量有數(shù)百個,實(shí)例數(shù)量數(shù)千個。
在數(shù)量達(dá)到這種規(guī)模后,產(chǎn)生了一些問題,我們設(shè)想一個場景,比如某種安全防護(hù)技術(shù)需要升級一下,那么安全開發(fā)組需要先跟業(yè)務(wù)開發(fā)團(tuán)隊(duì)協(xié)商開發(fā)時間,等排期開發(fā),然后需要測試,再排期發(fā)版。這樣幾十個業(yè)務(wù)開發(fā)團(tuán)隊(duì)升級下來,幾個月可能就過去了。
再設(shè)想一個場景,例如,我可能想app支持一下二進(jìn)制協(xié)議,可以提升數(shù)據(jù)交換效率。
一般我們做webapp,都是tomcat+springmvc這種結(jié)構(gòu)進(jìn)行開發(fā),支持二進(jìn)制協(xié)議就很困難。
所以,目前這種webapp的架構(gòu),對于公共服務(wù)集成升級和公共技術(shù)的升級不是很友好。
我們對架構(gòu)進(jìn)行了優(yōu)化,引入了網(wǎng)關(guān)。網(wǎng)關(guān)的主要作用有三個:
一個是協(xié)議適配
另一個是公共服務(wù)接入
最后是公共接入技術(shù)優(yōu)化
在外網(wǎng)和內(nèi)網(wǎng)中間有了網(wǎng)關(guān),網(wǎng)關(guān)本身和業(yè)務(wù)程序分離,就可以獨(dú)立的對這些技術(shù)進(jìn)行集成和升級。
http://microservices.io/ 總結(jié)的微服務(wù)模式中,網(wǎng)關(guān)已經(jīng)成為服務(wù)化中的一種標(biāo)準(zhǔn)模式。http://microservices.io/patterns/apigateway.html
網(wǎng)關(guān)模式,被一些大型的互聯(lián)網(wǎng)公司采用。國內(nèi)主要有唯品會、百度、阿里、京東、攜程、有贊等,國外主要有Netflix, Amazon, Mashape等。
2. 選型和設(shè)計
開源網(wǎng)關(guān)按照平臺可以分為基于nginx平臺的網(wǎng)關(guān)和自研網(wǎng)關(guān)
基于nginx平臺的網(wǎng)關(guān)有:
KONG
API Umbrella
自研的網(wǎng)關(guān)有:
apigee
StrongLoop
Zuul
Tyk
按照語言分類,可以見上圖,有基于lua(nginx平臺), nodejs, java, go等語言的網(wǎng)關(guān)。
基于nginx平臺的網(wǎng)關(guān)和自研網(wǎng)關(guān)的優(yōu)勢和劣勢如下:
| ? | 基于nginx | 自研 |
| 優(yōu)勢 | 1.?nginx有完善的處理http協(xié)議的能力 2.?全異步高性能基礎(chǔ)處理能力 3.?http處理過程中多個擴(kuò)展點(diǎn)可進(jìn)行擴(kuò)展 4.?開箱即用,基于openresty開發(fā)相對簡單 | 1.?可以完全掌控對http協(xié)議的處理過程 2.?可以完全掌控異步化業(yè)務(wù)處理過程 3.?對內(nèi)部協(xié)議支持可以較好掌控 4.?和內(nèi)部的配置中心、注冊中心結(jié)合較好 |
| 劣勢 | 1.?nginx工作流程復(fù)雜,對大多數(shù)人來說,只能當(dāng)作黑盒子用,出問題難以真正在代碼級理解根本原因,擴(kuò)展核心功能較為困難。 2.?基于openresty擴(kuò)展,本身有性能開銷,對java、erlang、go的性能優(yōu)勢不明顯 3.?對內(nèi)部協(xié)議和基礎(chǔ)組件支持不方便 | 1.?對http協(xié)議處理有較多的坑需要踩 2.?需要大量的性能優(yōu)化過程,不像nginx經(jīng)過大量實(shí)踐,本身有較好的性能基礎(chǔ) |
唯品會網(wǎng)關(guān)是基于netty自研的API網(wǎng)關(guān)。
唯品會網(wǎng)關(guān)參考各種開源網(wǎng)關(guān)的實(shí)現(xiàn),和業(yè)內(nèi)各大電商網(wǎng)站的成熟經(jīng)驗(yàn),網(wǎng)關(guān)邏輯上可以分為四層;
第一層是接入層,負(fù)責(zé)接入技術(shù)的優(yōu)化。
第二層是業(yè)務(wù)層,負(fù)責(zé)實(shí)現(xiàn)網(wǎng)關(guān)本身的一些業(yè)務(wù)實(shí)現(xiàn)。
第三層是網(wǎng)關(guān)依賴的基于netty實(shí)現(xiàn)的各種公共組件。
最底層是netty負(fù)責(zé)NIO、內(nèi)存管理、提供各種基礎(chǔ)庫、異步化框架等。
業(yè)務(wù)層前面跟大家分享過,主要包括路由、協(xié)議轉(zhuǎn)換、安全、認(rèn)證驗(yàn)簽、加密解密等,大家一看估計就可以看出,這些業(yè)務(wù)邏輯已經(jīng)劃分的比較獨(dú)立,可以按照模塊進(jìn)行劃分。實(shí)際上我們也是這樣做的。
業(yè)務(wù)層設(shè)計需要考慮哪些方面呢?
一方面,是流程的組織。
另一方面,網(wǎng)關(guān)需要依賴外部服務(wù),需要考慮怎樣異步化的調(diào)用外部服務(wù)。
最后,網(wǎng)關(guān)需要考慮高可用,高可用在程序設(shè)計方面主要是不停機(jī)發(fā)布。唯品會網(wǎng)關(guān)的所有業(yè)務(wù)配置,都可以通過管理界面動態(tài)管理、動態(tài)下發(fā)、動態(tài)生效,并且支持灰度。
業(yè)務(wù)層實(shí)現(xiàn),最重要的一點(diǎn),是將邏輯和數(shù)據(jù)分離,我們的實(shí)現(xiàn)方式,是業(yè)務(wù)邏輯實(shí)現(xiàn)在模塊里,數(shù)據(jù)通過context傳遞,context通過模塊之間相互調(diào)用時,通過接口傳遞。在異步化調(diào)用其他服務(wù)時,context保存在Channel的AttributeMap里,在異步完成時,回調(diào),取出context。
有了最基本的模塊設(shè)計,我們再來看唯品網(wǎng)關(guān)怎樣設(shè)計把這些流程串在一起。
大家看一下上面的圖,在執(zhí)行業(yè)務(wù)邏輯時,有些業(yè)務(wù)邏輯需要串行,比如,路由校驗(yàn)、參數(shù)校驗(yàn)、IP黑白名單、WAF等,由于性能方面考慮,一般情況下,我們會先執(zhí)行黑白名單模塊,因?yàn)檫@塊是cpu消耗最小、能攔掉部分請求的模塊。
后面再執(zhí)行路由、參數(shù)等的校驗(yàn)。這部分是內(nèi)存運(yùn)算,效率也比較高,也能攔掉一些非法請求,所以先執(zhí)行。
然后進(jìn)入outh、風(fēng)控、設(shè)備指紋等的外部服務(wù)調(diào)用,這些調(diào)用將會并發(fā)的執(zhí)行。
執(zhí)行后,將進(jìn)行結(jié)果合并校驗(yàn),如果在認(rèn)證驗(yàn)簽或風(fēng)控等校驗(yàn)未通過的情況下,將會直接返回,如果校驗(yàn)通過,再進(jìn)入后續(xù)的服務(wù)調(diào)用。
服務(wù)調(diào)用過程,又進(jìn)行了多選一的流程,可能用二進(jìn)制協(xié)議也可能用HTTP協(xié)議等。最終進(jìn)行后處理。
大家可能會想,這些模塊看上去可以使用actor模式進(jìn)行封裝,為何沒有使用開源異步框架呢?我們也對開源的異步框架進(jìn)行了詳細(xì)的調(diào)研。在將異步框架結(jié)合進(jìn)網(wǎng)關(guān)時發(fā)現(xiàn)對網(wǎng)關(guān)的性能產(chǎn)生了一些影響。
目前較為流行的異步框架,主要有akka和quasar fibers。他們的實(shí)現(xiàn)形式不同,但原理基本差不多。
為什么唯品網(wǎng)關(guān)沒有引入異步框架呢。
一方面是引入異步框架后,網(wǎng)關(guān)的抖動增加。
一方面是成熟度問題,quasar fibiers quasar fibers的模式,更加友好一些,可以以接近同步編程的模式實(shí)現(xiàn)異步編程。但最新的release是0.7.6,沒有大規(guī)模的驗(yàn)證過,我們也在實(shí)際使用踩了一些坑,例如,注解的問題、代碼織入沖突問題、長時間運(yùn)行突然響應(yīng)變慢問題,強(qiáng)烈建議大家如果生產(chǎn)使用,需要慎重再慎重。
我們總結(jié)了一下異步化框架適用于,大量依賴其他服務(wù),經(jīng)常被block的情況。
網(wǎng)關(guān)的瓶頸在cpu運(yùn)算,因?yàn)橛序?yàn)簽、加解密、協(xié)議轉(zhuǎn)換等cpu密集運(yùn)算,其他的調(diào)用已經(jīng)是全異步的,所以,引入異步框架的收益并不明顯。
上面分享了業(yè)務(wù)層的設(shè)計,下面分享一下公共組件的設(shè)計。
網(wǎng)關(guān)不論調(diào)用依賴的服務(wù)還是后端的服務(wù),都會遇到大量并發(fā)調(diào)用的情況。如果對連接不加以復(fù)用和控制,將造成大量的資源消耗和性能問題。因此,唯品網(wǎng)關(guān)自己設(shè)計優(yōu)化了連接池。
下面就分享一下唯品網(wǎng)關(guān)在連接池方面的設(shè)計。
連接復(fù)用主要是指,一個連接可以被多個使用者同時使用,且互相之間不受影響,可以并發(fā)的發(fā)送多個請求,而應(yīng)答是異步的,可復(fù)用的連接一般用于私有協(xié)議的連接,因?yàn)榭蓮?fù)用的連接,請求可以一直發(fā)送,應(yīng)答也不一定是按照請求順序進(jìn)行應(yīng)答,就帶來了一個問題,應(yīng)答怎樣才能和請求對應(yīng)上。私有協(xié)議就比較容易在協(xié)議包內(nèi),增加sequence id,所以能達(dá)到連接復(fù)用的要求。唯品會網(wǎng)關(guān)調(diào)用唯品會內(nèi)部的私有協(xié)議服務(wù)時,就采用的這種連接復(fù)用模式。
連接復(fù)用還有一種實(shí)現(xiàn)模式,是spymemcache的模式,memcached本身不支持sequenceid,但同一個連接上的操作會保證順序性,所以,spymemcache通過把請求緩存在queue中的形式,順序匹配返回結(jié)果,達(dá)到連接復(fù)用。
獨(dú)占的連接模式,主要是指,一個連接同一時間只能被一個使用者使用,在一個連接上,發(fā)送完一個請求后,必須等待應(yīng)答后,才能發(fā)送第二個請求。一般使用HTTP協(xié)議時,比較多使用這種獨(dú)占的模式。因?yàn)槿绻鸋TTP協(xié)議需要支持連接復(fù)用,需要在HTTP協(xié)議頭上增加sequence id,一般的服務(wù)端都不支持這種擴(kuò)展,所以,我們針對HTTP協(xié)議,使用的是獨(dú)占連接模式。
連接池的異步化,在連接池使用的所有階段都應(yīng)該異步化。我們在設(shè)計網(wǎng)關(guān)的連接池時,考慮了以下幾個方面:
獲取連接的異步化。從連接池獲取連接,一般情況被認(rèn)為是個沒有block的動作,實(shí)際上分解來看,獲取連接池,可能需要鎖連接池對象所在的隊(duì)列,操作連接池計數(shù)器時,可能會遇到鎖、超時等問題。后面我會跟大家分享我們怎樣去做的優(yōu)化。
連接使用就是說實(shí)際用連接去調(diào)用其他服務(wù),這塊的異步化,大家基本都會考慮到。
歸還連接的異步化。歸還連接時,也會操作連接池中的連接隊(duì)列,有時連接已經(jīng)異常還會執(zhí)行關(guān)閉連接等動作,所以也會產(chǎn)生鎖的問題。和獲取連接時類似,我們也把操作封裝為task,交由netty做cpu親緣性路由。
3. 實(shí)踐經(jīng)驗(yàn)
上面是給大家分享了我們在連接池設(shè)計中的幾個關(guān)鍵點(diǎn),接下來跟大家分享一下我們在實(shí)踐過程中實(shí)際進(jìn)行的優(yōu)化。
jvm啟動后,會在/tmp下建立一個文件,是一個內(nèi)存映射文件,JVM用來導(dǎo)出狀態(tài)數(shù)據(jù)給其它進(jìn)程使用,比如jstat,jconsole等。當(dāng)?shù)竭_(dá)安全點(diǎn)時,JVM會把安全點(diǎn)的相關(guān)信息寫入到這個文件中去。安全點(diǎn)是說,jvm會在這個點(diǎn)上,把所有其他線程都停下來,自己安全的做一些事情,GC是一種安全點(diǎn),還有其他種類的安全點(diǎn)。而gc log和這種監(jiān)控數(shù)據(jù)的寫入,就是在安全點(diǎn)上進(jìn)行寫入。當(dāng)IO頻發(fā)且負(fù)載均重時,可能寫數(shù)據(jù)動作剛好趕上操作系統(tǒng)將磁盤緩存刷到磁盤的過程,此時寫性能數(shù)據(jù)文件的操作就會被block。最終表現(xiàn)為jvm暫停。解決方法,是將這些性能數(shù)據(jù)寫到內(nèi)存文件中,避免和其他操作搶占磁盤io。
StringBuffer在寫日志等處理字符串拼接的場景下經(jīng)常用到,大多數(shù)情況下,我們會new一個StringBuffer,向里面追加字符串,在高并發(fā)場景,這個過程會產(chǎn)生大量的內(nèi)存重新分配并拷貝內(nèi)容的動作,造成cpu熱點(diǎn)。我們的優(yōu)化方法是,在threadlocal緩存使用過的stringbuffer,在下次使用時,直接復(fù)用。
我們在初期實(shí)際使用網(wǎng)關(guān)時觀察到,網(wǎng)關(guān)的OLD區(qū)使用會緩慢上升,大概兩天會產(chǎn)生一次FGC,經(jīng)過仔細(xì)的分析,發(fā)現(xiàn),java NIO的server socket類由finalize最后進(jìn)行釋放。而GC過程是第一次GC先將沒有引用的對象放入finalize隊(duì)列,下次GC的時候,調(diào)用finalize,并將對象釋放。而在高并發(fā)的情況下,server socket的finalize并不保證被調(diào)用,所以存活時間可能超過了升級閾值,就會有對象不斷進(jìn)入old區(qū)。
即使ref queue很快被執(zhí)行,也可能跨兩次ygc,比如創(chuàng)建后接著一次ygc1,然后用完后在下一次ygc2中添加到ref queue,ref queue沒有堆積的情況下,需要在ygc3中釋放這些對象。
由于網(wǎng)關(guān)會并發(fā)接受大量的請求,所以寫日志的量非常大。我們實(shí)際壓測的時候發(fā)現(xiàn),寫日志的IO操作,會周期性的被block,從而產(chǎn)生抖動。經(jīng)過分析發(fā)現(xiàn),被block的時候,操作系統(tǒng)在刷磁盤緩存。linux默認(rèn)是臟數(shù)據(jù)超過10%,或5s刷一次緩存,而這時可能會有大量數(shù)據(jù)在緩存里等待寫入磁盤,操作系統(tǒng)再去刷盤的時候,就會消耗比較多的時間,而這些時間內(nèi),應(yīng)用無法將數(shù)據(jù)寫入磁盤緩存,發(fā)生block。有兩個參數(shù)可以調(diào)整,一個是臟數(shù)據(jù)占比,一個是臟數(shù)據(jù)兩個取較小值生效。我們通過調(diào)小臟數(shù)據(jù)比率,讓刷盤動作在數(shù)據(jù)量較小的時候就開始,減小了毛刺率。
轉(zhuǎn)載于:https://www.cnblogs.com/davidwang456/p/6845178.html
總結(jié)
以上是生活随笔為你收集整理的唯品会API网关设计与实践--转的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 微服务可靠性设计--转
- 下一篇: excel工具类