自建API網(wǎng)關(guān)-架構(gòu)設(shè)計篇

閱讀對象

傳統(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/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,837評論 18 139
  • 殘月無聲上小樓,寒星疏影惹人愁。 遠(yuǎn)山迷霧鎖凝眸。 別后長思卿過往,年來又憶去年由。 人生幾多到白頭?
    Harvest收獲閱讀 861評論 91 150
  • 今天很累,很想不寫字了。用一千種理由來說服自己,不用寫了,工作那么忙,生活那么累,事情那么多……。但是心中有個聲音...
    我也不知道該叫啥閱讀 257評論 0 0
  • Ctrl + 1: 注釋/反注釋Ctrl + 4/5: 塊注釋/塊反注釋Ctrl + L: 跳轉(zhuǎn)到行號Tab/Sh...
    深思海數(shù)_willschang閱讀 5,664評論 0 0
  • 喝酒喝到“斷片兒”,好像還真不少見。 這種情形,學(xué)名稱為“酒后失憶”,是譫妄綜合癥的一種。 這是指大量飲酒過程中或...
    喝酒的鋼鐵漢子閱讀 405評論 0 0