1. 程式人生 > >京東京麥開放平臺的高可用架構之路

京東京麥開放平臺的高可用架構之路

京麥是京東商家的多端開放式工作平臺,是京東十萬商家唯一的店鋪運營管理平臺,為京東商家提供在移動和桌面端的操作業務,京麥本身是一個開放的端體系架構,由京東官方和 ISV 為商家提供多樣的應用服務。

京麥開發平臺是京東系統與外部系統通訊的重要平臺,技術架構從早期的單一 Nginx+Tomcat 部署,到現在的單一職責,獨立部署,去中心化,以及自主研發了 JSF/HTTP 等多種協議下的 API 閘道器、TCP 訊息推送、APNs 推送、降級、限流等技術。

京麥開放平臺每天承載海量的 API 呼叫、訊息推送,經歷了 4 年京東 618 的流量洗禮。本文將為您揭開京麥開放平臺高效能 API 閘道器、高可靠的訊息服務的技術內幕。

高效能 API 閘道器


如何將這些內部資料安全可控地開放給外部 ISV 進行服務呼叫,以及如何快速地進行 API 接入實現資料報文轉化,在這個背景下 API 閘道器誕生。京東內部的資料分佈在各個獨立的業務系統中,包括訂單中心、商品中心、商家中心等,各個獨立系統間通過 JSF(Jingdong Service Framework)進行資料交換。而 API 閘道器基於 OAuth2 協議提供,ISV 呼叫是通過 HTTP 的 JSON 協議。

API 閘道器在架構設計上採用了多層介面,到達閘道器的請求首先由閘道器接入層攔截處理,在接入層進行兩個主要環節的處理:

  1. 閘道器防禦校驗:這裡包含降級和限流,以及多級快取等,進行資料正確性校驗;
  2. 閘道器接入分發:閘道器分發會根據網關注冊中心的資料進行協議解析,之後動態構建呼叫例項,完成服務泛化呼叫。

API 閘道器是為了滿足 618 高併發請求下的應用場景,閘道器在服務排程、身份授權、報文轉換、負載與快取、監控與日誌等關鍵點上進行了針對性的架構優化。

API 元資料統一配置

API 的呼叫依賴對元資料獲取,比如 API 的欄位資訊、流控資訊、APP 金鑰、IP 白名單等、許可權配置等。在 618 場景下,元資料獲取效能是 API 閘道器的關鍵點。基於 DB 元資料讀取是不可取的,即使對 DB 做分庫分表處理也不行,因為 DB 就不是用來抗量的。

其次,要考慮到元資料的更新問題,定時的輪訓更新會產生極大延遲性,而且空輪訓也是對系統資源的極大浪費,採用 MQ 廣播通知不失為一種解決辦法,但 MQ 僅僅解決資料同步的問題,資料快取在叢集裡服務如何保證資料一致性和資料容災,又極大的增加了系統複雜度。

所以綜合考慮伺服器效能和網路 IO 等因素,在 API 元資料讀取採用基於 ZooKeeper 的統一配置,並自研實現多級快取容災架構方案,從 ZooKeeper、記憶體和本地檔案等進行多級快取,同時支援資料變更時即時同步,以及系統宕機網路異常等情況下的資料自動容災等策略。

以讀為例,閘道器首先從記憶體中讀取配置,如無資料,從 ZooKeeper 讀取,讀取後同步到記憶體,並非同步儲存本次快照。如果 ZooKeeper 資料變更,通過監聽 ZooKeeper 的 DataChangeWatcher 變更同步資料。如果 ZooKeeper 宕機,重啟伺服器,系統還可以通過本地快照恢復最近一次的元資料配置。

TCP 全雙工的長連結會話通道

API HTTP 閘道器通過介面提供服務呼叫獲取請求資料的,而搭建客戶端與服務平臺的 TCP 閘道器的雙向通道,以保持客戶端與服務平臺的會話狀態,則可以在 HTTP 閘道器基礎上提供更多、更靈活的技術實現和業務實現。

在業務服務呼叫上通過 HTTP 閘道器,在平臺服務呼叫上則通過 TCP 閘道器,實現平臺與業務解耦,並且平臺採用 TCP 通道還可以增加對平臺的控制力,在此背景下誕生了 TCP 閘道器。

TCP 閘道器採用長連線通道,實現全雙工會話。TCP 閘道器採用 Netty 作為 TCP 容器,在 ChannelPipe中載入自定義 ChannelHandler,構建 Container 容器,將每個 TCP Connection 封裝到一個 Session 會話中,儲存在 Container 容器中,由 Container 容器構建 Session 會話層提供邏輯層請求呼叫。

自研構建 Session 會話層是因為 HTTP 屬於 OSI 的應用層,而 TCP 屬於 OSI 的傳輸層,面向連線的程式設計極大的增加程式複雜度,所以將 Connection 封裝在每一個 Session 會話裡,再以微服務的方式提供服務呼叫,極大的精簡了 TCP 程式設計。

斷線重連

客戶端與服務端通過 TCP 長連線進行通訊,但在中國複雜的網路環境下,移動客戶端可能由於網路抖動、弱網路情況下,遭遇非正常網路閃斷,如何處理斷開後的斷線重連,保證客戶端與服務端的通訊穩定呢?

客戶端每通過 TCP 與服務端進行一次建連,都會在服務容器裡建立一個 Session 會話,該會話儲存 Connection 的控制代碼,對應 Netty 的一個 Channel 通道。建連成功後,通過定時的心跳保持 Channel 屬於 Active 活躍。但客戶端進入弱網路環境下,客戶端可能已經掉線,但並未向服務端主動傳送關閉 Channel 請求,而服務端仍認為該 Channel 仍存活。直到在由服務端的會話存活檢測機制檢測到 Channel 已經 InActive,才會由服務端銷燬該 Channel。

服務端的會話存活檢測是 5 分鐘一次,所以存在客戶端掉線後,在 5 分鐘內又重新建連,而這時服務端的建連邏輯,不是重新建立一個 Session,而是去尋找上一次的 Session,並更新標識存活。具體的實現是在每次建連的 Channel 裡存入 SessionId,當網路閃斷後,判斷 Channel 是否存在 Session,之所以實現是得益於 Netty 的 ChannelHandlerContext,可以儲存一個自定義屬性到 Channel 的上下文中。

當然,TCP 閘道器一定是叢集,所以,斷線重連也是極有可能請求到不同的伺服器上,而這種情況按照新 Connection 建立的 Session 處理,只有出現重連到同一伺服器時,才需要考慮上述的處理邏輯。

Protobuf 資料交換格式

HTTP 閘道器基於 JSON 進行資料傳輸,JSON 是 key-value 的鍵值對通訊協議,所以生成報文會很大,所以影響傳輸效能。考慮到報文傳輸大小,在 TCP 閘道器中則通過 Protobuf 定義通訊協議,提升資料傳輸效率。

(點選放大影象)

Protobuf 支援 Java、Objective-C 和 C++ 等語言,現支援了京麥平臺 PC 桌面客戶端、移動 iOS 和 Android 客戶端基於 Protobuf 通過 TCP 與服務端進行通訊。

多維度流量控制

由於各個 API 的服務能力不一致,為了保證各個 API 能夠穩定提供服務,不會被暴漲的請求流量擊垮,那麼多維度流量控制是 API 閘道器的一個重要環節。

目前 API 閘道器是採用令牌桶的方法,實現方式是 Guava RateLimter,簡單有效,再結合統一配置中心,可以動態調整限流閾值。不用重啟伺服器即可實現快速限流策略調整。

在 API 閘道器裡面還有一個設定,就是併發度,這個是方法粒度的,對每一個呼叫介面都有一個併發度數值設定,而且是動態設定,也是通過 ZooKeeper 下發到每一個服務節點上。併發度的具體實現是通過 JDK 的 Semaphore。

高可靠的訊息服務

API 閘道器提供 ISV 獲取資料,但實時資料的獲取,如果通過輪詢閘道器,大量空轉不僅非常的低效且浪費伺服器資源。基於此,開放平臺推出了訊息推送技術,提供一個實時的、可靠的、非同步的雙向資料交換通道,提升 API 閘道器效能。

AnyCall 和推送系統

AnyCall

負責接收各業務中心的訂單、商品、商家等訊息,進行統一的訊息過濾、轉換、儲存,及監控和統計等。各個過程中的訊息狀態,通過訊息採集器儲存到 ElasticSearch 和 HBase 進行儲存。

推送系統

基於 Netty 作為網路層框架,構建海量推送模型,使用靜默長連線通道,實現從訊息接收、推送、確認,整個過程的完全非同步化處理。

解耦訊息接入層和訊息推送層,訊息接入層只負責Request-Response和 Notice-Repley,而訊息解析、適配、推送等邏輯處理都全部由訊息推送層處理,而訊息接入層和訊息推送層之間則有訊息佇列非同步進行通訊。

半推半拉還是半推半查?

半推半拉

半推半拉模式中的“推”指的是由伺服器推送 訊息通知 到客戶端,“拉”指的是客戶端收到通知後再從伺服器拉取 訊息實體 到客戶端本地儲存。

其中訊息通知傳送的僅是一個命令關鍵字,這樣的設計是考慮訊息推送可能存在丟失,通過拉取的方式,確保即使訊息通知未送達,在下次訊息通知觸發下的拉取也能把上一次訊息拉取到本地。採用的半推半拉,每次僅推送通知,推送量小,實時性高。

半推半查

後期京麥訊息推送模式由“拉”改“查”,“查”指的是訊息通知依舊推送,但客戶端收到訊息通知後 不再拉取訊息實體,僅更新訊息未讀數和進行訊息提醒等操作,而訊息內容則是由服務端進行雲端儲存,採用輕客戶端,重服務端的架構方案,只有使用者點選查詢訊息時,才會按需進行資料查詢,在客戶端展示,但不儲存。

這種推送模式的改動主要考慮了客戶端拉取訊息內容到本地儲存,佔用資源,重灌之後客戶端會丟失訊息,以及多端儲存的資料存在不一致等問題。訊息雲端儲存基於 ElasticSearch 進行訊息儲存,並根據業務型別區分索引,通過 Routing 優化查詢效能,支援多維度進行查詢,效能穩定。

訊息確認

評估訊息系統的一個核心指標是訊息送達率。為保證每一條訊息準確送達,為每條訊息都會開啟一個事務,從推送開始,到確認結束,如果超時未確認就會重發這條訊息,這就是訊息確認。

由於網際網路環境複雜,訊息超時時間不能設定太短,尤其在移動弱網路環境下。在本系統的中超時設定為 10 秒。

我們通過實現 Future 自定義 NotifyFuture,為每個下行通知分配一個 seq,並定義 NotifyFuture 的 timeout。即每個下行通知分配一個 seq 儲存快取中,等待客戶端迴應這個應答,如果應答, 則從快取移出這個 seq,否則等待超時,自動從快取中被移出。

APNs 訊息推送

iOS 在系統層面與蘋果 APNs(Apple Push Notification Service)伺服器建立連線,應用通過 Socket 向 APNs Server 推送訊息,然後再由 APNs 進行推送。但是基於 Socket 的 APNs 協議是一種反人類的設計,在推送訊息存在很多問題。

鑑於此,對 APNs 推送服務進行重構,基於 Netty 構建了 HTTP2 協議的推送服務,支援同步和非同步的推送方式;解決 Channel 異常及 InActive 時重連等問題,保證 HTTP2 推送管道的問題;同時通過 IdleStateHandler 保持 HTTP2 長連線的心跳 。

總結和感悟

最後,總結歷次的大促,京麥開發平臺在進行服務化架構的演進過程中,所面臨的技術難點,最重要的還是服務治理,即呼叫關係的梳理。因為我們要打造的不是一個系統,也不是一堆系統,而是一個平臺生態,能夠持續地提高系統的運營能力。

這裡以“精打細算,大道至簡”這句話結束此次京麥開放平臺的總結。

作者介紹

張鬆然,2013 年加入京東,一直從事京東商家麥開放平臺的架構設計和開發工作,熟悉大規模分散式系統架構。在 Web 開發、架構優化上有較豐富的實戰經歷。有多年 NIO 領域的設計、開發經驗,對 HTTP、TCP 長連線有深入研究與領悟。目前主要致力於多端開放平臺技術架構的優化與實現。