日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長提供免費收錄網(wǎng)站服務,提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

本文目錄

- 說在前面

- 喜馬拉雅自研億級API網(wǎng)關技術實踐

- 1、第1版:Tomcat NIO+Async Servlet

- 2、第2版?.NETty+全異步

  - 2.1 接入層

  - 2.2 業(yè)務邏輯層

  - 2.3 服務調(diào)用層

    - 2.3.1 異步 Push

    - 2.3.2 連接池

    - 2.3.3 Connection:close

    - 2.3.4 寫超時

- 3、全鏈路超時機制

- 4、監(jiān)控報警

- 5、性能優(yōu)化實踐

  - 5.1 對象池技術

  - 5.2 上下文切換

  - 5.3 GC優(yōu)化

  - 5.4 日志

- 6、未來規(guī)劃

- 說在最后:有問題可以找老架構取經(jīng)

- 部分歷史案例

?

喜馬拉雅自研億級API網(wǎng)關技術實踐

網(wǎng)關作為一種發(fā)展較為完善的產(chǎn)品,各大互聯(lián)網(wǎng)公司普遍采用它作為中間件,以應對公共業(yè)務需求的不斷浮現(xiàn),并能迅速迭代更新。

如果沒有網(wǎng)關,要更新一個公共特性,就得推動所有業(yè)務方都進行更新和發(fā)布,這無疑是效率極低的。然而,有了網(wǎng)關之后,這一切都不再是問題。

喜馬拉雅也如此,用戶數(shù)量已增長到 6 億級別,Web 服務數(shù)量超過 500 個,目前我們的網(wǎng)關每天處理超過 200 億次的調(diào)用,單機 QPS 峰值可達 4w+。

除了實現(xiàn)基本的反向代理功能,網(wǎng)關還具備許多公共特性,如黑白名單、流量控制、身份驗證、熔斷、API 發(fā)布、監(jiān)控和報警等。根據(jù)業(yè)務方的需求,我們還實現(xiàn)了流量調(diào)度、流量復制、預發(fā)布、智能升級、流量預熱等相關功能。

從技術上來說,喜馬拉雅API網(wǎng)關的技術演進路線圖大致如下

注意:請點擊圖像以查看清晰的視圖!

本文將介紹在喜馬拉雅 API 網(wǎng)關面臨億級流量的情況下,我們?nèi)绾芜M行技術演進,以及我們的實踐經(jīng)驗總結。

1、第1版:Tomcat NIO+Async Servlet

在架構設計中,網(wǎng)關的關鍵之處在于接收到請求并調(diào)用后端服務時,不能發(fā)生阻塞(Block),否則網(wǎng)關的處理能力將受到限制。

這是因為最耗時的操作就是遠程調(diào)用后端服務這個過程。

如果此處發(fā)生阻塞,Tomcat 的工作線程會被全部 block 住了,等待后端服務響應的過程中無法處理其他請求,因此這里必須采用異步處理。

架構圖如下

注意:請點擊圖像以查看清晰的視圖!

在這個版本中,我們實現(xiàn)了一個單獨的 Push 層,用于在網(wǎng)關接收到響應后,響應客戶端,并通過此層實現(xiàn)與后端服務的通信。

該層使用的是 HttpNioClient,支持業(yè)務功能包括黑白名單、流量控制、身份驗證、API 發(fā)布等。

然而,這個版本僅在功能上滿足了網(wǎng)關的要求,處理能力很快成為瓶頸。當單機 QPS 達到 5K 時,會頻繁發(fā)生 Full GC。

通過分析線上堆,我們發(fā)現(xiàn)問題在于 Tomcat 緩存了大量 HTTP 請求。因為 Tomcat 默認會緩存 200 個 requestProcessor,每個處理器都關聯(lián)一個 request。

另外,Servlet 3.0 的 Tomcat 異步實現(xiàn)可能會導致內(nèi)存泄漏。后來我們通過減少這個配置,效果明顯。

然而,這種調(diào)整會導致性能下降。總結一下,基于 Tomcat 作為接入端存在以下問題:

Tomcat 自身的問題

  • 1)緩存過多,Tomcat 使用了許多對象池技術,在有限內(nèi)存的情況下,流量增大時很容易觸發(fā) GC;

  • 2)內(nèi)存 Copy,Tomcat 的默認內(nèi)存使用堆內(nèi)存,因此數(shù)據(jù)需要從堆內(nèi)讀取,而后端服務是 Netty,使用堆外內(nèi)存,需要經(jīng)過多次 Copy;

  • 3)Tomcat 還有個問題是讀 body 是阻塞的, Tomcat 的 NIO 模型和 reactor 模型不同,讀 body 是 block 的。

這里再分享一張 Tomcat buffer 的關系圖

注意:請點擊圖像以查看清晰的視圖!

從上圖中,我們能夠明顯觀察到,Tomcat 的封裝功能相當完善,但在內(nèi)部默認設置下,會有三次 copy。

HttpNioClient 的問題:在獲取和釋放連接的過程中都需要進行加鎖,針對類似網(wǎng)關這樣的代理服務場景,會導致頻繁地建立和關閉連接,這無疑會對性能產(chǎn)生負面影響。

鑒于 Tomcat 存在的這些難題,我們在后續(xù)對接入端進行了優(yōu)化,采用 Netty 作為接入層和服務調(diào)用層,也就是我們的第二版,成功地解決了上述問題,實現(xiàn)了理想的性能。

2、第2版:Netty+全異步

基于 Netty 的優(yōu)勢,我們構建了全異步、無鎖、分層的架構。

先看下我們基于 Netty 做接入端的架構圖

注意:請點擊圖像以查看清晰的視圖!

2.1 接入層

Netty 的 IO 線程主要負責 HTTP 協(xié)議的編解碼工作,同時也監(jiān)控并報警協(xié)議層面的異常情況。

我們對 HTTP 協(xié)議的編解碼進行了優(yōu)化,并對異常和攻擊性請求進行了監(jiān)控和可視化處理。

例如,我們對 HTTP 請求行和請求頭的大小都有限制,而 Tomcat 是將請求行和請求頭一起計算,總大小不超過 8K,而 Netty 是分別對兩者設置大小限制。

如果客戶端發(fā)送的請求超過了設定的閥值,帶有 cookie 的請求很容易超過這個限制,一般情況下,Netty 會直接響應 400 給客戶端。

在優(yōu)化后,我們只取正常大小的部分,并標記協(xié)議解析失敗,這樣在業(yè)務層就可以判斷出是哪個服務出現(xiàn)了這類問題。

對于其他攻擊性的請求,例如只發(fā)送請求頭而不發(fā)送 body 或者只發(fā)送部分內(nèi)容,都需要進行監(jiān)控和報警。

2.2 業(yè)務邏輯層

這一層負責實現(xiàn)一系列支持業(yè)務的公共邏輯,包括 API 路由、流量調(diào)度等,采用責任鏈模式,這一層不會進行 IO 操作。

在業(yè)界和大型企業(yè)的網(wǎng)關設計中,業(yè)務邏輯層通常都被設計成責任鏈模式,公共的業(yè)務邏輯也在這一層實現(xiàn)。

在這一層,我們也執(zhí)行了相似的操作,并支持以下功能

  • 1)用戶認證和登錄驗證,支持接口級別的配置;

  • 2)黑白名單:包括全局和應用的黑白名單,以及 IP 和參數(shù)級別的限制;

  • 3)流量控制:提供自動和手動控制,自動控制可攔截過大流量,通過令牌桶算法實現(xiàn);

  • 4)智能熔斷:在 Histrix 的基礎上進行改進,支持自動升降級,我們采用全自動方式,也支持手動配置立即熔斷,即當服務異常比例達到設定值時,自動觸發(fā)熔斷;

  • 5)灰度發(fā)布:對于新啟動的機器的流量,我們支持類似于 TCP 的慢啟動機制,為機器提供一段預熱時間;

  • 6)統(tǒng)一降級:我們對所有轉發(fā)失敗的請求都會執(zhí)行統(tǒng)一降級操作,只要業(yè)務方配置了降級規(guī)則,都會進行降級,我們支持將降級規(guī)則細化到參數(shù)級別,包括請求頭中的值,非常細粒度,此外,我們還會與 varnish 集成,支持 varnish 的優(yōu)雅降級;

  • 7)流量調(diào)度:支持業(yè)務根據(jù)篩選規(guī)則,將流量分配到對應的機器,也支持僅讓篩選的流量訪問該機器,這在排查問題/新功能發(fā)布驗證時非常有用,可以先通過小部分流量驗證,再大面積發(fā)布上線;

  • 8)流量 copy:我們支持根據(jù)規(guī)則對線上原始請求 copy 一份,將其寫入 MQ 或其他 upstream,用于線上跨機房驗證和壓力測試;

  • 9)請求日志采樣:我們對所有失敗的請求都會進行采樣并保存到磁盤,以供業(yè)務方排查問題,同時也支持業(yè)務方根據(jù)規(guī)則進行個性化采樣,我們采樣了整個生命周期的數(shù)據(jù),包括請求和響應相關的所有數(shù)據(jù)。

上述提到的所有功能都是對流量進行管理,我們每個功能都作為一個 filter,處理失敗都不會影響轉發(fā)流程,而且所有這些規(guī)則的元數(shù)據(jù)在網(wǎng)關啟動時就會全部初始化好。

在執(zhí)行過程中,不會進行 IO 操作,目前有些設計會對多個 filter 進行并發(fā)執(zhí)行,由于我們的操作都是在內(nèi)存中進行,開銷并不大,所以我們目前并未支持并發(fā)執(zhí)行。

另外,規(guī)則可能會發(fā)生變化,所有需要進行規(guī)則的動態(tài)刷新。

我們在修改規(guī)則時,會通知網(wǎng)關服務,進行實時刷新,我們對內(nèi)部自己的這種元數(shù)據(jù)更新請求,通過獨立的線程處理,防止 IO 操作時影響業(yè)務線程。

2.3 服務調(diào)用層

服務調(diào)用對于代理網(wǎng)關服務非常關鍵,這個環(huán)節(jié),性能必須很高:必須采用異步方式,

我們利用 Netty 實現(xiàn)了這一目標,同時也充分利用了 Netty 提供的連接池,實現(xiàn)了獲取和釋放的無鎖操作。

2.3.1 異步 Push

在發(fā)起服務調(diào)用后,網(wǎng)關允許工作線程繼續(xù)處理其他請求,而無需等待服務端返回。

在這個設計中,我們?yōu)槊總€請求創(chuàng)建一個上下文,發(fā)送請求后,將該請求的 context 綁定到相應的連接上,當 Netty 收到服務端響應時,會在連接上執(zhí)行 read 操作。

解碼完成后,再從連接上獲取相應的 context,通過 context 可以獲取到接入端的 session。

這樣,push 通過 session 將響應寫回客戶端,這個設計基于 HTTP 連接的獨占性,即連接和請求上下文綁定。

2.3.2 連接池

連接池的原理如下圖

注意:請點擊圖像以查看清晰的視圖!

服務調(diào)用層除了異步發(fā)起遠程調(diào)用外,還需要管理后端服務的連接。

HTTP 與 RPC 不同,HTTP 連接是獨占的,所以在釋放連接時需要特別小心,必須等待服務端響應完成后才能釋放,此外,連接關閉的處理也需要謹慎。

總結如下幾點

  • 1)Connection:close;
  • 2)空閑超時,關閉連接;
  • 3)讀超時關閉連接;
  • 4)寫超時,關閉連接;
  • 5)Fin、Reset。

上面幾種需要關閉連接的場景,下面主要說下 Connection:close 和空閑寫超時兩種,其他情況如讀超時、連接空閑超時、收到 fin、reset 碼等都比較常見。

2.3.3 Connection:close

后端服務采用的是 Tomcat,它對連接的重用次數(shù)有規(guī)定,默認為 100 次。

當達到 100 次限制時,Tomcat 會在響應頭中添加 Connection:close,要求客戶端關閉該連接,否則再次使用該連接發(fā)送請求會出現(xiàn) 400 錯誤。

還有就是如果前端的請求帶了 connection:close,那 Tomcat 就不會等待該連接重用滿 100 次,即一次就關閉連接。

在響應頭中添加 Connection:close 后,連接變?yōu)槎踢B接。

在與 Tomcat 保持長連接時,需要注意這一點,如果要利用該連接,需要主動移除 close 頭。

2.3.4 寫超時

首先,網(wǎng)關在何時開始計算服務的超時時間?

如果從調(diào)用 writeAndFlush 開始計算,實際上包含了 Netty 對 HTTP 的編碼時間和從隊列中發(fā)送請求即 flush 的時間,這樣對后端服務不公平。

因此,需要在真正 flush 成功后開始計時,這樣最接近服務端,當然還包含了網(wǎng)絡往返時間和內(nèi)核協(xié)議棧處理時間,這是無法避免的,但基本穩(wěn)定。

因此,我們在 flush 成功回調(diào)后啟動超時任務。

需要注意的是:如果 flush 不能快速回調(diào),例如遇到一個大的 POST 請求,body 部分較大,而 Netty 發(fā)送時默認第一次只發(fā)送 1k 大小。

如果尚未發(fā)送完畢,會增大發(fā)送大小繼續(xù)發(fā)送,如果在 Netty 發(fā)送 16 次后仍未發(fā)送完成,將不再繼續(xù)發(fā)送,而是提交一個 flushTask 到任務隊列,待下次執(zhí)行后再發(fā)送。

此時,flush 回調(diào)時間較長,導致此類請求無法及時關閉,后端服務 Tomcat 會一直阻塞在讀取 body 部分,基于上述分析,我們需要設置寫超時,對于大的 body 請求,通過寫超時及時關閉連接。

3、全鏈路超時機制

注意:請點擊圖像以查看清晰的視圖!

上圖是我們在整個鏈路超時處理的機制

  • 1)協(xié)議解析超時;
  • 2)等待隊列超時;
  • 3)建連超時;
  • 4)等待連接超時;
  • 5)寫前檢查是否超時;
  • 6)寫超時;
  • 7)響應超時。
 

4、監(jiān)控報警

對于網(wǎng)關的業(yè)務方來說,他們能看到的是監(jiān)控和警報功能,我們能夠實現(xiàn)秒級的報警和監(jiān)控,將監(jiān)控數(shù)據(jù)定時上傳到我們的管理系統(tǒng),由管理系統(tǒng)負責匯總統(tǒng)計并存儲到 InfluxDB 中。

我們對 HTTP 協(xié)議進行了全面的監(jiān)控和警報,涵蓋了協(xié)議層和服務層的問題。

協(xié)議層

  • 1)針對攻擊性請求,只發(fā)送頭部,不發(fā)送或只發(fā)送部分 body,我們會進行采樣并記錄,還原現(xiàn)場,并觸發(fā)警報;
  • 2)對于 Line 或 Head 或 Body 過大的請求,我們會進行采樣記錄,還原現(xiàn)場,并及時發(fā)出警報。

應用層

  • 1)監(jiān)控耗時:包括慢請求,超時請求,以及 tp99,tp999 等;

  • 2)監(jiān)控 OPS:并及時發(fā)出警報;

  • 3)帶寬監(jiān)控和報警:支持對請求和響應的行,頭,body 單獨監(jiān)控;

  • 4)響應碼監(jiān)控:特別是 400,和 404;

  • 5)連接監(jiān)控:我們對接入端的連接,以及與后端服務的連接,以及后端服務連接上待發(fā)送字節(jié)大小都進行了監(jiān)控;

  • 6)失敗請求監(jiān)控

  • 7)流量抖動報警:這是非常必要的,流量抖動可能是出現(xiàn)問題,或者是問題即將出現(xiàn)的預兆。

總體架構

注意:請點擊圖像以查看清晰的視圖!

5、性能優(yōu)化實踐

5.1 對象池技術

針對高并發(fā)系統(tǒng),不斷地創(chuàng)建對象不僅會占用內(nèi)存資源,還會對垃圾回收過程產(chǎn)生壓力。

為了解決這個問題,我們在實現(xiàn)過程中會對諸如線程池的任務、StringBuffer 等頻繁使用的對象進行重用,從而降低內(nèi)存分配的開銷。

5.2 上下文切換

在高并發(fā)系統(tǒng)中,通常會采用異步設計。異步化后,線程上下文切換的問題必須得到關注。

我們的線程模型如下

注意:請點擊圖像以查看清晰的視圖!

我們的網(wǎng)關沒有涉及 I/O 操作,但在業(yè)務邏輯處理方面仍然采用了 Netty 的 I/O 編解碼線程異步方式。

這主要有兩個原因

  • 1)防止開發(fā)人員編寫的代碼出現(xiàn)阻塞現(xiàn)象;

  • 2)在突發(fā)情況下,業(yè)務邏輯可能會產(chǎn)生大量的日志記錄,我們允許在推送線程時使用 Netty 的 I/O 線程作為替代。這種做法可以減少 CPU 上下文切換的次數(shù),從而提高整體吞吐量。我們不能僅僅為了異步而異步,Zuul2 的設計理念與我們的做法相似。

5.3 GC優(yōu)化

在高并發(fā)系統(tǒng)中,垃圾回收GC的優(yōu)化是必不可少的。

我們采用了對象池技術和堆外內(nèi)存,使得對象很少進入老年代,同時年輕代的設置較大,SurvivorRatio 設置為 2,晉升年齡設置最大為 15,以盡量讓對象在年輕代就被回收。

但監(jiān)控發(fā)現(xiàn)老年代的內(nèi)存仍在緩慢增長。通過dump分析,我們每個后端服務創(chuàng)建一個鏈接,都時有一個socket,socket的AbstractPlAInSocketImpl,而AbstractPlainSocketImpl就重寫了Object類的finalize方法。

實現(xiàn)如下

/**
 * Cleans up if the user forgets to close it.
 */
protected void finalize() throws IOException {
    close();
}

是為了我們沒有主動關閉鏈接,做的一個兜底,在gc回收的時候,先把對應的鏈接資源給釋放了。

由于finalize 的機制是通過 JVM 的 Finalizer 線程處理的,其優(yōu)先級不高,默認為 8。它需要等待 Finalizer 線程把 ReferenceQueue 的對象對應的 finalize 方法執(zhí)行完,并等到下次垃圾回收時,才能回收該對象。這導致創(chuàng)建鏈接的這些對象在年輕代不能立即回收,從而進入了老年代,這也是老年代持續(xù)緩慢增長的原因。

5.4 日志

在高并發(fā)系統(tǒng)中,尤其是 Netty 的 I/O 線程,除了執(zhí)行 I/O 讀寫操作外,還需執(zhí)行異步任務和定時任務。如果 I/O 線程處理不過隊列中的任務,可能會導致新進來的異步任務被拒絕。

在什么情況下可能會出現(xiàn)這種情況呢?異步讀寫問題不大,主要是多耗點 CPU。最有可能阻塞 I/O 線程的是日志記錄。目前 Log4j 的 ConsoleAppender 日志 immediateFlush 屬性默認為 true,即每次記錄日志都是同步寫入磁盤,這對于內(nèi)存操作來說,速度較慢。

同時,AsyncAppender 的日志隊列滿了也會阻塞線程。Log4j 默認的 buffer 大小是 128,而且是阻塞的。即當 buffer 大小達到 128 時,會阻塞寫日志的線程。在并發(fā)寫日志量較大且堆棧較深的情況下,Log4j 的 Dispatcher 線程可能會變慢,需要刷盤。這樣 buffer 就不能快速消費,很容易寫滿日志事件,導致 Netty I/O 線程被阻塞。因此,在記錄日志時,我們需要注意精簡。

6、未來規(guī)劃

目前,我們都在使用基于 HTTP/1 的協(xié)議。

相對于 HTTP/1,HTTP/2 在連接層面實現(xiàn)了服務,即在一個連接上可以發(fā)送多個 HTTP 請求。

這就意味著 HTTP 連接可以像 RPC 連接一樣,建立幾個連接即可,完全解決了 HTTP/1 連接無法復用導致的重復建連和慢啟動的開銷。

我們正在基于 Netty 升級到 HTTP/2,除了技術升級外,我們還在不斷優(yōu)化監(jiān)控報警,以便為業(yè)務方提供準確無誤的報警。此外,我們還在作為統(tǒng)一接入網(wǎng)關與業(yè)務方實施全面的降級措施,以確保全站任何故障都能通過網(wǎng)關第一時間降級,這也是我們的重點工作。

分享到:
標簽:架構 設計
用戶無頭像

網(wǎng)友整理

注冊時間:

網(wǎng)站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨大挑戰(zhàn)2018-06-03

數(shù)獨一種數(shù)學游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數(shù)有氧達人2018-06-03

記錄運動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定