閱讀對象
傳統(tǒng)企業(yè)正在做微服務(wù)架構(gòu)轉(zhuǎn)型的開發(fā)人員或者架構(gòu)師,希望本文對您能起到一定的引導(dǎo)作用。
API網(wǎng)關(guān)介紹
網(wǎng)關(guān)一詞較早出現(xiàn)在網(wǎng)絡(luò)設(shè)備里面,比如兩個相互獨立的局域網(wǎng)段之間通過路由器或者橋接設(shè)備進(jìn)行通信,這中間的路由或者橋接設(shè)備我們稱之為網(wǎng)關(guān)。
相應(yīng)的API網(wǎng)關(guān)將各系統(tǒng)對外暴露的服務(wù)聚合起來,所有要調(diào)用這些服務(wù)的系統(tǒng)都需要通過API網(wǎng)關(guān)進(jìn)行訪問,基于這種方式網(wǎng)關(guān)可以對API進(jìn)行統(tǒng)一管控,例如:認(rèn)證、鑒權(quán)、流量控制、協(xié)議轉(zhuǎn)換、監(jiān)控等等。
API網(wǎng)關(guān)的流行得益于近幾年微服務(wù)架構(gòu)的興起,原本一個龐大的業(yè)務(wù)系統(tǒng)被拆分成許多粒度更小的系統(tǒng)進(jìn)行獨立部署和維護(hù),這種模式勢必會帶來更多的跨系統(tǒng)交互,企業(yè)API的規(guī)模也會成倍增加,API網(wǎng)關(guān)(或者微服務(wù)網(wǎng)關(guān))就逐漸成為了微服務(wù)架構(gòu)的標(biāo)配組件。
如下是我們整理的API網(wǎng)關(guān)的幾種典型應(yīng)用場景:
1、面向Web或者移動App
這類場景,在物理形態(tài)上類似前后端分離,前端應(yīng)用通過API調(diào)用后端服務(wù),需要網(wǎng)關(guān)具有認(rèn)證、鑒權(quán)、緩存、服務(wù)編排、監(jiān)控告警等功能。
2、面向合作伙伴開放API
這類場景,主要為了滿足業(yè)務(wù)形態(tài)對外開放,與企業(yè)外部合作伙伴建立生態(tài)圈,此時的API 網(wǎng)關(guān)注重安全認(rèn)證、權(quán)限分級、流量管控、緩存等功能的建設(shè)。
3、企業(yè)內(nèi)部系統(tǒng)互聯(lián)互通
對于中大型的企業(yè)內(nèi)部往往有幾十、甚至上百個系統(tǒng),尤其是微服務(wù)架構(gòu)的興起系統(tǒng)數(shù)量更是急劇增加。系統(tǒng)之間相互依賴,逐漸形成網(wǎng)狀調(diào)用關(guān)系不便于管理和維護(hù),需要API網(wǎng)關(guān)進(jìn)行統(tǒng)一的認(rèn)證、鑒權(quán)、流量管控、超時熔斷、監(jiān)控告警管理,從而提高系統(tǒng)的穩(wěn)定性、降低重復(fù)建設(shè)、運維管理等成本。
設(shè)計目標(biāo)
1、純Java實現(xiàn);
2、支持插件化,方便開發(fā)人員自定義組件;
3、支持橫向擴(kuò)展,高性能;
4、避免單點故障,穩(wěn)定性要高,不能因為某個API故障導(dǎo)致整個網(wǎng)關(guān)停止服務(wù);
5、管理控制臺配置更新可自動生效,不需要重啟網(wǎng)關(guān);
應(yīng)用架構(gòu)設(shè)計
整個平臺拆分成3個子系統(tǒng),Gateway-Core(核心子系統(tǒng))、Gateway-Admin(管理中心)、Gateway-Monitor(監(jiān)控中心)。
Gateway-Core負(fù)責(zé)接收客戶端請求,調(diào)度、加載和執(zhí)行組件,將請求路由到上游服務(wù)端,處理上游服務(wù)端返回的結(jié)果等;
Gateway-Admin提供統(tǒng)一的管理界面,用戶可在此進(jìn)行API、組件、系統(tǒng)基礎(chǔ)信息的設(shè)置和維護(hù);
Gateway-Monitor負(fù)責(zé)收集監(jiān)控日志、生成各種運維管理報表、自動告警等;
系統(tǒng)架構(gòu)設(shè)計
說明:
1、網(wǎng)關(guān)核心子系統(tǒng)通過HAProxy或者Nginx進(jìn)行負(fù)載均衡,為避免正好路由的LB節(jié)點服務(wù)不可用,可以考慮在此基礎(chǔ)上增加Keepalived來實現(xiàn)LB的失效備援,當(dāng)LB Node1停止服務(wù),Keepalived會將虛擬IP自動飄移到LB Node2,從而避免因為負(fù)載均衡器導(dǎo)致單點故障。DNS可以直接指向Keepalived的虛擬IP。
2、網(wǎng)關(guān)除了對性能要求很高外,對穩(wěn)定性也有很高的要求,引入Zookeeper及時將Admin對API的配置更改同步刷新到各網(wǎng)關(guān)節(jié)點。
3、管理中心和監(jiān)控中心可以采用類似網(wǎng)關(guān)子系統(tǒng)的高可用策略,如果嫌麻煩管理中心可以省去Keepalived,相對來說管理中心沒有這么高的可用性要求。
4、理論上監(jiān)控中心需要承載很大的數(shù)據(jù)量,比如有1000個API,平均每個API一天調(diào)用10萬次,對于很多互聯(lián)網(wǎng)公司單個API的量遠(yuǎn)遠(yuǎn)大于10萬,如果將每次調(diào)用的信息都存儲起來太浪費,也沒有太大的必要。可以考慮將API每分鐘的調(diào)用情況匯總后進(jìn)行存儲,比如1分鐘的平均響應(yīng)時間、調(diào)用次數(shù)、流量、正確率等等。
5、數(shù)據(jù)庫選型可以靈活考慮,原則上網(wǎng)關(guān)在運行時要盡可能減少對DB的依賴,否則IO延時會嚴(yán)重影響網(wǎng)關(guān)性能。可以考慮首次訪問后將API配置信息緩存,Admin對API配置更改后通過Zookeeper通知網(wǎng)關(guān)刷新,這樣一來DB的訪問量可以忽略不計,團(tuán)隊可根據(jù)自身偏好靈活選型。
非阻塞式HTTP服務(wù)
管理和監(jiān)控中心可以根據(jù)團(tuán)隊的情況采用自己熟悉的Servlet容器部署,網(wǎng)關(guān)核心子系統(tǒng)對性能的要求非常高,考慮采用NIO的網(wǎng)絡(luò)模型,實現(xiàn)純HTTP服務(wù)即可,不需要實現(xiàn)Servlet容器,推薦Netty框架(設(shè)計優(yōu)雅,大名鼎鼎的Spring Webflux默認(rèn)都是使用的Netty,更多的優(yōu)勢就不在此詳述了),內(nèi)部測試在相同的機(jī)器上分別通過Tomcat和Netty生成UUID,Netty的性能大約有20%的提升,如果后端服務(wù)響應(yīng)耗時較高的話吞吐量還有更大的提升。(補充:Netty4.x的版本即可,不要采用5以上的版本,有嚴(yán)重的缺陷沒有解決)
采用Netty作為Http容器首先需要解決的是Http協(xié)議的解析和封裝,好在Netty本身提供了這樣的Handler,具體參考如下代碼:
1、構(gòu)建一個單例的HttpServer,在SpringBoot啟動的時候同時加載并啟動Netty服務(wù)
??????????? int sobacklog =Integer.parseInt(AppConfigUtil.getValue("netty.sobacklog"));
??????????? ServerBootstrap b = newServerBootstrap();
? ??????????b.group(bossGroup, workerGroup)
???????????????????.channel(NioServerSocketChannel.class)
???????????????????.localAddress(new InetSocketAddress(this.portHTTP))
???????????????????.option(ChannelOption.SO_BACKLOG, sobacklog)
???????????????????.childHandler(new ChannelHandlerInitializer(null));
??????????? //綁定端口
??????????? ChannelFuture f =b.bind(this.portHTTP).sync();
???????????logger.info("HttpServer name is " + HttpServer.class.getName()+ " started and listen on " + f.channel().localAddress());
2、初始化Handler
@Override
??? protected voidinitChannel(SocketChannel ch) throws Exception {
??????? ChannelPipeline p =ch.pipeline();
??????? p.addLast(newHttpRequestDecoder());
??????? p.addLast(newHttpResponseEncoder());
??????? int maxContentLength = 2000;
??????? try {
??????????? maxContentLength =Integer.parseInt(AppConfigUtil.getValue("netty.maxContentLength"));
??????? } catch (Exception e) {
???????????logger.warn("netty.maxContentLength配置異常,系統(tǒng)默認(rèn)為:2000KB");
????? ??}
??????? p.addLast(new
HttpObjectAggregator(maxContentLength * 1024));// HTTP 消息的合并處理
??????? p.addLast(newHttpServerInboundHandler());
??? }
HttpRequestDecoder和HttpResponseEncoder分別實現(xiàn)Http協(xié)議的解析和封裝,Http Post內(nèi)容超過一個數(shù)據(jù)包大小會自動分組,通過HttpObjectAggregator可以自動將這些數(shù)據(jù)粘合在一起,對于上層收到是一個完整的Http請求。
3、通過HttpServerInboundHandler將網(wǎng)絡(luò)請求轉(zhuǎn)發(fā)給網(wǎng)關(guān)執(zhí)行器
@Override
??? public voidchannelRead0(ChannelHandlerContext ctx, Object msg)
??????????? throws Exception {
??????? try {
??????????? if (msg instanceofHttpRequest && msg instanceof HttpContent) {
??????????????? CmptRequestcmptRequest = CmptRequestUtil.convert(ctx, msg);
??????????????? CmptResultcmptResult = this.gatewayExecutor.execute(cmptRequest);
??????????????? FullHttpResponseresponse = encapsulateResponse(cmptResult);
??????????????? ctx.write(response);
??????????????? ctx.flush();
??????????? }
??????? } catch (Exception e) {
??????????? logger.error("網(wǎng)關(guān)入口異常," + e.getMessage());
??????????? e.printStackTrace();
??????? }
??? }
設(shè)計上建議將Netty接入層代碼跟網(wǎng)關(guān)核心邏輯代碼分離,不要將Netty收到HttpRequest和HttpContent直接給到網(wǎng)關(guān)執(zhí)行器,可以考慮做一層轉(zhuǎn)換封裝成自己的Request給到執(zhí)行器,方便后續(xù)可以很容易的將Netty替換成其它Http容器。(如上代碼所示,CmptRequest即為自定義的Http請求封裝類,CmptResult為網(wǎng)關(guān)執(zhí)行結(jié)果類)
組件化及自定義組件支持
組件是網(wǎng)關(guān)的核心,大部分功能特性都可以基于組件的形式提供,組件化可以有效提高網(wǎng)關(guān)的擴(kuò)展性。
先來看一個簡單的微信認(rèn)證組件的例子:
如下實現(xiàn)的功能是對API請求傳入的Token進(jìn)行校驗,其結(jié)果分別是認(rèn)證通過、Token過期和無效Token,認(rèn)證通過后再將微信OpenID攜帶給上游服務(wù)系統(tǒng)。
/**
?*微信token認(rèn)證,token格式:
?*{appID:'',openID:'',timestamp:132525144172,sessionKey: ''}
?*/
public class WeixinAuthTokenCmpt extends AbstractCmpt {
??? private static Logger logger =LoggerFactory.getLogger(WeixinAuthTokenCmpt.class);
??? private final CmptResultSUCCESS_RESULT;
??? public WeixinAuthTokenCmpt() {
??????? SUCCESS_RESULT =buildSuccessResult();
??? }
??? @Override
?? ?public CmptResult execute(CmptRequest request,Map config) {
??????? if (logger.isDebugEnabled()){
???????????logger.debug("WeixinTokenCmpt ......");
??????? }
??????? CmptResult cmptResult =null;
??????? //Token認(rèn)證超時間(傳入單位:分)
??????? long authTokenExpireTime =getAuthTokenExpireTime(config);
??????? WeixinTokenDTO authTokenDTO= this.getAuthTokenDTO(request);
???????logger.debug("Token=" + authTokenDTO);
??????? AuthTokenStateauthTokenState = validateToken(authTokenDTO, authTokenExpireTime);
??????? switch (authTokenState) {
??????????? case ACCESS: {
??????????????? cmptResult =SUCCESS_RESULT;
??????????????? Map header = new HashMap<>();
???????????????header.put(HeaderKeyConstants.HEADER_APP_ID_KEY,authTokenDTO.getAppID());
??????????????? header.put(CmptHeaderKeyConstants.HEADER_WEIXIN_OPENID_KEY,authTokenDTO.getOpenID());
???????????????header.put(CmptHeaderKeyConstants.HEADER_WEIXIN_SESSION_KEY,authTokenDTO.getSessionKey());
???????????????cmptResult.setHeader(header);
??????????????? break;
?????? ?????}
??????????? case EXPIRED: {
??????????????? cmptResult =buildCmptResult(RespErrCode.AUTH_TOKEN_EXPIRED, "token過期,請重新獲取Token!");
??????????????? break;
??????????? }
??????????? case INVALID: {
??????????????? cmptResult =buildCmptResult(RespErrCode.AUTH_INVALID_TOKEN, "Token無效!");
??????????????? break;
??????????? }
??????? }
??????? return cmptResult;
??? }
...
}
上面例子看不懂沒關(guān)系,接下來會詳細(xì)闡述組件的設(shè)計思路。
1、組件接口定義
public interface ICmpt {
??? /**
???? *組件執(zhí)行入口
???? *
???? * @param request
???? * @param config,組件實例的參數(shù)配置
???? * @return
???? */
??? CmptResult execute(CmptRequestrequest, Map config);
??? /**
???? *銷毀組件持有的特殊資源,比如線程。
???? */
??? void destroy();
}
execute是組件執(zhí)行的入口方法,request前面提到過是http請求的封裝,config是組件的特殊配置,比如上面例子提到的微信認(rèn)證組件就有一個自定義配置-Token的有效期,不同的API使用該組件可以設(shè)置不同的有效期。
FieldDTO定義如下:
public class FieldDTO {
??? private String title;
??? private String name;
??? private FieldType fieldType =FieldType.STRING;
??? private String defaultValue;
??? private boolean required;
??? private String regExp;
??? private String description;
}
CmptResult為組件執(zhí)行后的返回結(jié)果,其定義如下:
public class CmptResult {
??? RespErrMsg respErrMsg;//組件返回錯誤信息
??? private boolean passed;//組件過濾是否通過
??? private byte[] data;//組件返回數(shù)據(jù)
??? private Map header = new HashMap();//透傳后端服務(wù)響應(yīng)頭信息
??? private MediaType mediaType;//返回響應(yīng)數(shù)據(jù)類型
??? private Integer statusCode =200;//默認(rèn)返回狀態(tài)碼為200
}
2、組件類型定義
執(zhí)行器需要根據(jù)組件類型和組件執(zhí)行結(jié)果判斷是要直接返回客戶端還是繼續(xù)往下面執(zhí)行,比如認(rèn)證類型的組件,如果認(rèn)證失敗是不能繼續(xù)往下執(zhí)行的,但緩存類型的組件沒有命中才繼續(xù)往下執(zhí)行。當(dāng)然這樣設(shè)計存在一些缺陷,比如新增組件類型需要執(zhí)行器配合調(diào)整處理邏輯。(Kong也提供了大量的功能組件,沒有研究過其網(wǎng)關(guān)框架是如何跟組件配合的,是否支持用戶自定義組件類型,知道的朋友詳細(xì)交流下。)
初步定義如下組件類型:
認(rèn)證、鑒權(quán)、流量管控、緩存、路由、日志等。
其中路由類型的組件涵蓋了協(xié)議轉(zhuǎn)換的功能,其負(fù)責(zé)調(diào)用上游系統(tǒng)提供的服務(wù),可以根據(jù)上游系統(tǒng)提供API的協(xié)議定制不同的路由組件,比如:Restful、WebService、Dubbo、EJB等等。
3、組件執(zhí)行位置和優(yōu)先級設(shè)定
執(zhí)行位置:Pre、Routing、After,分別代表后端服務(wù)調(diào)用前、后端服務(wù)調(diào)用中和后端服務(wù)調(diào)用完成后,相同位置的組件根據(jù)優(yōu)先級決定執(zhí)行的先后順序。
4、組件發(fā)布形式
組件打包成標(biāo)準(zhǔn)的Jar包,通過Admin管理界面上傳發(fā)布。
附-組件可視化選擇UI設(shè)計
組件熱插拔設(shè)計和實現(xiàn)
JVM中Class是通過類加載器+全限定名來唯一標(biāo)識的,上面章節(jié)談到組件是以Jar包的形式發(fā)布的,但相同組件的多個版本的入口類名需要保持不變,因此要實現(xiàn)組件的熱插拔和多版本并存就需要自定義類加載器來實現(xiàn)。
大致思路如下:
網(wǎng)關(guān)接收到API調(diào)用請求后根據(jù)請求參數(shù)從緩存里拿到API配置的組件列表,然后再逐一參數(shù)從緩存里獲取組件對應(yīng)的類實例,如果找不到則嘗試通過自定義類加載器載入Jar包,并初始化組件實例及緩存。
附-參考示例
public static ICmpt newInstance(final CmptDef cmptDef) {
??? ICmpt cmpt = null;
??? try { ?
?????? final String jarPath = getJarPath(cmptDef);
??????? if (logger.isDebugEnabled()) {
??????????? logger.debug("嘗試載入jar包,jar包路徑: " + jarPath);
??????? }
??????? //加載依賴jar
??????? CmptClassLoader cmptClassLoader = CmptClassLoaderManager.loadJar(jarPath, true);
??????? // 創(chuàng)建實例
??????? if (null != cmptClassLoader) {
??????????? cmpt = LoadClassUtil.newObject(cmptDef.getFullQualifiedName(), ICmpt.class, cmptClassLoader);
??????? } else { ??????????? logger.error("加載組件jar包失敗! jarPath: " + jarPath); ??????? } ??? }
catch (Exception e) {
??????? logger.error("組件類加載失敗,請檢查類名和版本是否正確。ClassName=" + cmptDef.getFullQualifiedName() + ", Version=" + cmptDef.getVersion()); ??????? e.printStackTrace();
??? }
??? return cmpt;
}
補充說明:
自定義類加載器可直接需要繼承至URLClassLoader,另外必須指定其父類加載器為執(zhí)行器的加載器,否則組件沒法引用網(wǎng)關(guān)的其它類。
API故障隔離及超時、熔斷處理
在詳細(xì)闡述設(shè)計前先講個實際的案例,大概12年的時候某公司自研了一款ESB的中間件(企業(yè)服務(wù)總線跟API網(wǎng)關(guān)很類似,當(dāng)年SOA理念大行其道的時候都推崇的是ESB,側(cè)重服務(wù)的編排和異構(gòu)系統(tǒng)的整合。),剛開始用的還行,但隨著接入系統(tǒng)的增多,突然某天運維發(fā)現(xiàn)大量API出現(xiàn)緩慢甚至超時,初步檢查發(fā)現(xiàn)ESB每個節(jié)點的線程幾乎消耗殆盡,起初判斷是資源不夠,緊急擴(kuò)容后還是很快線程占滿,最終導(dǎo)致上百個系統(tǒng)癱瘓。
最終找到問題的癥結(jié)是某個業(yè)務(wù)系統(tǒng)自身的原因?qū)е路?wù)不可用,下游業(yè)務(wù)系統(tǒng)請求大量堆積到ESB中,從而導(dǎo)致大量線程堵塞。
以上案例說明了一個在企業(yè)應(yīng)用架構(gòu)設(shè)計里面的經(jīng)典原則-故障隔離,由于所有的API請求都要經(jīng)過網(wǎng)關(guān),必須隔離API之間的相互影響,尤其是個別API故障導(dǎo)致整個網(wǎng)關(guān)集群服務(wù)中斷。
接下來分別介紹故障隔離、超時管控、熔斷的實現(xiàn)思路。
1、故障隔離
有兩種方式可以實現(xiàn),一是為每個API創(chuàng)建一個線程池,每個線程分配10~20個線程,這也是常用的隔離策略,但這種方式有幾個明顯的缺點:
1)線程數(shù)會隨著API接入數(shù)量遞增,1000個API就需要2萬個線程,光線程切換對CPU就是不小的開銷,而其線程還需要占用一定的內(nèi)存資源;
2)平均分配線程池大小導(dǎo)致個別訪問量較大且響應(yīng)時間相對較長的API吞吐量上不去;
3)本身就有工作線程池了,再增加API的線程池,導(dǎo)致某些需要ThreadLocal特性的編程變得困難。
二是用信號量隔離,直接復(fù)用Netty的工作線程,上面線程池隔離提到的3個缺點都可以基本避免, 建議設(shè)置單個API的信號量個數(shù)小于等于Netty工作線程池數(shù)量的1/3,這樣既兼顧了單個API的性能又不至于單個API的問題導(dǎo)致整個網(wǎng)關(guān)堵塞。
具體實現(xiàn)可以考慮直接引用成熟的開源框架,推薦Hystrix,可以同時解決超時控制和熔斷。
參考配置如下:
Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey(groupKey))
???????.andCommandKey(HystrixCommandKey.Factory.asKey(commandKey ))
???????.andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
? ??????//艙壁隔離策略-信號量
? ? ? ? .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE)
? ??????//設(shè)置每組command可以申請的信號量最大數(shù)
? ? ? ? .withExecutionIsolationSemaphoreMaxConcurrentRequests(CmptInvoker.maxSemaphore)
? ??????/*開啟超時設(shè)置*/
? ? ? ? .withExecutionIsolationThreadInterruptOnTimeout(true)
? ??????/*超時時間設(shè)置*/
? ? ? ? .withExecutionIsolationThreadTimeoutInMilliseconds(timeout)
? ? ? ? .withCircuitBreakerEnabled(true)//開啟熔斷? ? ? ????????.withCircuitBreakerSleepWindowInMilliseconds(Constants.DEFAULT_CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS)//5秒后會嘗試閉合回路
2、 超時管控
API的超時控制是必須要做的,否則上游服務(wù)即便是間歇性響應(yīng)緩慢也會堵塞大量線程(雖然通過信號量隔離后不會導(dǎo)致整個網(wǎng)關(guān)線程堵塞)。
其次,每個API最好可以單獨配置超時時間,但不建議可以讓用戶隨意設(shè)置,還是要有個最大閾值。(API網(wǎng)關(guān)不適合需要長時間傳輸數(shù)據(jù)的場景,比如大文件上傳或者下載、DB數(shù)據(jù)同步等)
實現(xiàn)上可以直接復(fù)用開源組件的功能,比如:HttpClient可以直接設(shè)置獲取連接和Socket響應(yīng)的超時時間,Hystrix可以對整個調(diào)用進(jìn)行超時控制等。
3、熔斷
熔斷類似電路中的保險絲,當(dāng)超過負(fù)荷或者電阻被擊穿的時候自動斷開對設(shè)備起到保護(hù)作用。在API網(wǎng)關(guān)中設(shè)置熔斷的目的是快速響應(yīng)請求,避免不必要的等待,比如某個API后端服務(wù)正常情況下1s以內(nèi)響應(yīng),但現(xiàn)在因為各種原因出現(xiàn)堵塞大部分請求20s才能響應(yīng),雖然設(shè)置了10s的超時控制,但讓請求線程等待10s超時不僅沒有意義,反而會增加服務(wù)提供方的負(fù)擔(dān)。
為此我們可以設(shè)置單位時間內(nèi)超過多少比例的請求超時或者異常,則直接熔斷鏈路,等待一段時間后再次嘗試恢復(fù)鏈路。
實現(xiàn)層面可以直接復(fù)用Hystrix。
運行時配置更新機(jī)制
前面章節(jié)提到過出于性能考慮網(wǎng)關(guān)在運行時要盡可能減小對DB的訪問,設(shè)計上可以將API、組件等關(guān)鍵內(nèi)容進(jìn)行緩存,這樣一來性能是提升了,但也帶來了新的問題,比如Admin對API或者組件進(jìn)行配置調(diào)整后如何及時更新到集群的各個網(wǎng)關(guān)節(jié)點。
解決方案很多,比如引入消息中間件,當(dāng)Admin調(diào)整配置后就往消息中心發(fā)布一條消息,各網(wǎng)關(guān)節(jié)點訂閱消息,收到消息后刷新緩存數(shù)據(jù)。
我們在具體實現(xiàn)過程中采用的是Zookeeper集群數(shù)據(jù)同步機(jī)制,其實現(xiàn)原理跟消息中間件很類似,只不過網(wǎng)關(guān)在啟動的時候就會向ZK節(jié)點進(jìn)行注冊,也是被動更新機(jī)制。
性能考慮
性能是網(wǎng)關(guān)一項非常重要的衡量指標(biāo),尤其是響應(yīng)時間,客戶端本來可以直連服務(wù)端的,現(xiàn)在增加了一個網(wǎng)關(guān)層,對于一個本身耗時幾百毫秒的服務(wù)接入網(wǎng)關(guān)后增加幾毫秒,影響倒是可以忽略不計;但如果服務(wù)本身只需要幾毫秒,因為接入網(wǎng)關(guān)再增加一倍的延時,用戶感受就會比較明顯。
建議在設(shè)計上需要遵循如下原則:
1、核心網(wǎng)關(guān)子系統(tǒng)必須是無狀態(tài)的,便于橫向擴(kuò)展。
2、運行時不依賴本地存儲,盡量在內(nèi)存里面完成服務(wù)的處理和中轉(zhuǎn)。
3、減小對線程的依賴,采用非阻塞式IO和異步事件響應(yīng)機(jī)制。
4、后端服務(wù)如果是HTTP協(xié)議,盡量采用連接池或者Http2,測試連接復(fù)用和不復(fù)用性能有幾倍的差距。(TCP建立連接成本很高)
附-HttpClient連接池設(shè)置
PoolingHttpClientConnectionManager
cmOfHttp = new PoolingHttpClientConnectionManager();
cmOfHttp.setMaxTotal(maxConn);
cmOfHttp.setDefaultMaxPerRoute(maxPerRoute);
httpClient = HttpClients.custom() .setConnectionManager(cmOfHttp).setConnectionManagerShared(true)
??????? .build();
說明:
httpClient對象可以作為類的成員變量長期駐留內(nèi)存,這個是連接池復(fù)用的前提。
結(jié)語
API網(wǎng)關(guān)作為企業(yè)API服務(wù)的匯聚中心,其良好的性能、穩(wěn)定性和可擴(kuò)展性是基礎(chǔ),只有這個基礎(chǔ)打扎實了,我們才能在上面擴(kuò)展更多的特性。
這篇文章主要介紹網(wǎng)關(guān)的總體架構(gòu)設(shè)計, 后面的篇幅在詳細(xì)探討下各種組件的具體設(shè)計和實現(xiàn)。
有興趣的朋友可以加入QQ交流群:244054462,備注:API網(wǎng)關(guān)架構(gòu)設(shè)計交流。
附-產(chǎn)品介紹
http://www.xbgateway.com/