前言
最近換了一份工作,而新工作是調(diào)研下目前業(yè)界Api網(wǎng)關(guān)的一些性能情況突想,而在最近過去一年的時間里,我也主要開發(fā)了一個Api網(wǎng)關(guān)來支持協(xié)議適配的需求,但是由于前東家的話整個流量都不大滋戳,而相應(yīng)的性能優(yōu)化也沒有很好的去做,借著讓原來在唯品會的同事把網(wǎng)關(guān)推到了OYO在做性能壓測及我剛?cè)肼毿聠挝唤邮值牡谝豁椚蝿?wù)啥刻,把網(wǎng)關(guān)的性能進行了一下優(yōu)化奸鸯,也踩了一些坑,把這些作為總結(jié)寫下來郑什;本文是https://juejin.im/post/5d19dd5c6fb9a07ec27bbb6e?from=timeline&isappinstalled=0
的補充府喳,主要是介紹整個優(yōu)化步驟;
網(wǎng)關(guān)簡介
Tesla的整個網(wǎng)絡(luò)框架是基于littleproxy[https://github.com/adamfisk/LittleProxy]蘑拯,littleproxy是著名的軟件的后端代理钝满,按照常規(guī)性能應(yīng)該不錯,在此基礎(chǔ)上我們加了些功能申窘,具體代碼在:[https://github.com/spring-avengers/tesla]
刪除UDP代理的功能及SSL的功能弯蚜;
增加了10多個Filter,而這些Filter由一個最大的Filter包裹來執(zhí)行剃法;
- HttpFiltersAdapter的clientToProxyRequest方法碎捺,負責(zé)調(diào)用方到代理方的攔截處理
public HttpResponse clientToProxyRequest(HttpObject httpObject) {
logStart();
HttpResponse httpResponse = null;
try {
httpResponse = HttpRequestFilterChain.doFilter(serveletRequest, httpObject, ctx);
if (httpResponse != null) {
return httpResponse;
}
} catch (Throwable e) {
httpResponse = ProxyUtils.createFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_GATEWAY);
HttpUtil.setKeepAlive(httpResponse, false);
logger.error("Client connectTo proxy request failed", e);
return httpResponse;
}
...
}
- HttpFiltersAdapter的proxyToClientResponse方法,負責(zé)從后端服務(wù)拿到請求返回給調(diào)用方的攔截處理
public HttpObject proxyToClientResponse(HttpObject httpObject) {
if (httpObject instanceof HttpResponse) {
HttpResponse serverResponse = (HttpResponse)httpObject;
HttpResponse response = HttpResponseFilterChain.doFilter(serveletRequest, serverResponse, ctx);
logEnd(serverResponse);
return response;
} else {
return httpObject;
}
}
每個Filter的配置數(shù)據(jù)都是定時從數(shù)據(jù)庫里面拉取(用hazelcast做了緩存)收厨;
為了讓Filter做到熱插拔晋柱,大量的使用了反射去構(gòu)造Filter的實例;
優(yōu)化步驟
由于大量的使用了反射去構(gòu)造實例诵叁,但是沒有去緩存這些實例雁竞,原本的想法是所有的Filter都是有狀態(tài)的,伴隨每一個HTTP請求的消亡而消亡拧额,但是最終運行下來發(fā)現(xiàn)如果并發(fā)量上來整個GC完全接受不了碑诉,大量的對象的產(chǎn)生引起了yong gc的頻率幾乎成倍的增加,進而導(dǎo)致qps上不去侥锦,而解決這個問題的辦法就是緩存實例进栽,讓每一個Filter無狀態(tài),整個JVM內(nèi)存中僅存在一份實例恭垦;
但是這些做下來快毛,發(fā)現(xiàn)網(wǎng)關(guān)的QPS也沒上去,盡管GC情況解決了署照,QPS有所上升祸泪,但是遠遠沒達到Netty所想要的QPS,那只能繼續(xù)往前走建芙;-
線程池的優(yōu)化
我們都知道Netty的線程池分為boss線程池和work線程池没隘,其中boss線程池負責(zé)接收網(wǎng)絡(luò)請求,而work線程池負責(zé)處理io任務(wù)及其他自定義任務(wù)禁荸,對于網(wǎng)關(guān)這個應(yīng)用來說右蒲,boss線程池是必須要的,因為要負責(zé)請求的接入赶熟,但是網(wǎng)關(guān)比較特殊瑰妄,對于真正的調(diào)用方來說,它是一個服務(wù)端映砖,對于后端服務(wù)來說它是一個客戶端间坐,所以他的線程模型應(yīng)該是如下:
這種線程池模型是典型的netty的線程池模型:
Acceptor負責(zé)接收請求,ClientToProxyWorker是負責(zé)代理服務(wù)器的處理IO請求邑退,而ProxyServerWorker負責(zé)轉(zhuǎn)發(fā)請求到后端服務(wù)竹宋,LittleProxy就是使用這種很經(jīng)典的線程模型,其QPS在4核32G的機器下QPS大概能達到9000多地技,但是這種線程模型存在ClientToProxyWorker和ProxyServerWorker線程切換的問題蜈七,我們都知道線程切換是要耗費CPU資源的,那我們是不是可以做一個改變呢莫矗?換成以下這種:
這種線程池模型是將ClientToProxyWorker和ProxyServerWorker復(fù)用同一個線程池飒硅,這種做法在省卻了一個線程切換的時間砂缩,也就是對于代理服務(wù)器來說,netty的服務(wù)端及netty的客戶端在線程池傳入時復(fù)用同一個線程池對象三娩;
做到這一步的話整個代理服務(wù)的性能應(yīng)該能提升不少庵芭,但是有沒有更好的線程模型呢?答案是肯定的雀监;
這個線程模型的話喳挑,整個處理請求及轉(zhuǎn)發(fā)請求都復(fù)用同一個線程,而這種做法的話線程的切換基本沒有滔悉;
而相應(yīng)的代碼如下:
/**
* Opens the socket connection.
*/
private ConnectionFlowStep connectChannel = new ConnectionFlowStep(this, CONNECTING) {
@Override
public boolean shouldExecuteOnEventLoop() {
return false;
}
@Override
public Future<?> execute() {
//復(fù)用整個ClientToProxy的處理IO的線程
Bootstrap cb = new Bootstrap().group(ProxyToServerConnection.this.clientConnection.channel.eventLoop())
.channel(NioSocketChannel.class)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, proxyServer.getConnectTimeout())
.handler(new ChannelInitializer<Channel>() {
public void initChannel(Channel ch) throws Exception {
Object tracingContext =
ProxyToServerConnection.this.clientConnection.channel.attr(KEY_CONTEXT).get();
ch.attr(KEY_CONTEXT).set(tracingContext);
initChannelPipeline(ch.pipeline(), initialRequest);
}
;
});
if (localAddress != null) {
return cb.connect(remoteAddress, localAddress);
} else {
return cb.connect(remoteAddress);
}
}
};
這種做法zuul2也是如此做,文章可以看看這篇介紹比較詳細:http://www.reibang.com/p/cb413fec1632
- 部署壓測:
-
壓測結(jié)果
總結(jié)
- Netty的線程池模型選擇直接決定了性能
- 在Netty的InboundHandler里不要做任何的加鎖動作单绑,Netty的pipline已經(jīng)保證了是單線程運行回官,如果要緩存數(shù)據(jù)的話直接用HashMap就好,別用ConcuurentHashMap或者 Collections.synchronizedMap來做加鎖動作
- 保證Filter是無狀態(tài)的
- 慎用applicationContext.getEnvironment().getProperty()搂橙,在非Web容器環(huán)境下該操作將影響很大的性能