一個API網(wǎng)關(guān)性能優(yōu)化實踐之路

前言

最近換了一份工作,而新工作是調(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í)行剃法;

  1. 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;
     }
     ...
 }
  1. 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)該是如下:


    image.png

    這種線程池模型是典型的netty的線程池模型:
    Acceptor負責(zé)接收請求,ClientToProxyWorker是負責(zé)代理服務(wù)器的處理IO請求邑退,而ProxyServerWorker負責(zé)轉(zhuǎn)發(fā)請求到后端服務(wù)竹宋,LittleProxy就是使用這種很經(jīng)典的線程模型,其QPS在4核32G的機器下QPS大概能達到9000多地技,但是這種線程模型存在ClientToProxyWorker和ProxyServerWorker線程切換的問題蜈七,我們都知道線程切換是要耗費CPU資源的,那我們是不是可以做一個改變呢莫矗?換成以下這種:


    image.png

    這種線程池模型是將ClientToProxyWorker和ProxyServerWorker復(fù)用同一個線程池飒硅,這種做法在省卻了一個線程切換的時間砂缩,也就是對于代理服務(wù)器來說,netty的服務(wù)端及netty的客戶端在線程池傳入時復(fù)用同一個線程池對象三娩;
    做到這一步的話整個代理服務(wù)的性能應(yīng)該能提升不少庵芭,但是有沒有更好的線程模型呢?答案是肯定的雀监;
    image.png

這個線程模型的話喳挑,整個處理請求及轉(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

  • 部署壓測:
image.png
  • 壓測結(jié)果


    image.png

總結(jié)

  • Netty的線程池模型選擇直接決定了性能
  • 在Netty的InboundHandler里不要做任何的加鎖動作单绑,Netty的pipline已經(jīng)保證了是單線程運行回官,如果要緩存數(shù)據(jù)的話直接用HashMap就好,別用ConcuurentHashMap或者 Collections.synchronizedMap來做加鎖動作
  • 保證Filter是無狀態(tài)的
  • 慎用applicationContext.getEnvironment().getProperty()搂橙,在非Web容器環(huán)境下該操作將影響很大的性能
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末歉提,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子区转,更是在濱河造成了極大的恐慌苔巨,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件废离,死亡現(xiàn)場離奇詭異侄泽,居然都是意外死亡,警方通過查閱死者的電腦和手機蜻韭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門悼尾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人肖方,你說我怎么就攤上這事闺魏。” “怎么了俯画?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵析桥,是天一觀的道長。 經(jīng)常有香客問我艰垂,道長泡仗,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任材泄,我火速辦了婚禮沮焕,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘拉宗。我一直安慰自己辣辫,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著畴嘶,像睡著了一般集晚。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上偷拔,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天蒋院,我揣著相機與錄音,去河邊找鬼莲绰。 笑死欺旧,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的踏枣。 我是一名探鬼主播茵瀑,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼躬厌!你這毒婦竟也來了扛施?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤荒揣,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體喉悴,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年今魔,在試婚紗的時候發(fā)現(xiàn)自己被綠了勺像。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡错森,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出涩维,到底是詐尸還是另有隱情殃姓,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布瓦阐,位于F島的核電站蜗侈,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏睡蟋。R本人自食惡果不足惜踏幻,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望戳杀。 院中可真熱鬧该面,春花似錦夭苗、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蚕泽,卻和暖如春晌梨,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背须妻。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工仔蝌, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人荒吏。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓敛惊,卻偏偏與公主長得像,于是被迫代替她去往敵國和親绰更。 傳聞我的和親對象是個殘疾皇子瞧挤,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345