億級流量架構演進實戰 - 從零構建億級流量 API 網關 02

這不是一個講概念的專欄,而且我也不擅長講概念,每一篇文章都是一個故事,我希望你可以通過這些故事瞭解我當時在實際工作中遇到問題和背後的思考,架構設計是種經驗,我有幸參與到多個億級系統的架構設計中,有所收穫的同時也希望把這些收穫分享與大家。

承接上篇,統一了接口之後並沒有徹底改變被客戶端碾着走的局面,因爲還有一個根本的點沒有被解決,就是網關對上游服務的適配問題,說白了就是每當上游有一個新的 API 要發佈,網關都需要進行開發適配,我們曾經出過一個 API 標準接入的解決方案去推動上游去改造,不過遇到了很大的阻力。這個痛點直到網關實現了 API 的服務泛化調用之後纔有所突破,功能一經上線,API 發佈在網關就不需要再適配一行代碼,完全解耦了網關與平臺的業務邏輯,使網關的效能得到釋放。不過,內部協議直接被轉化成外部協議使得 API 在定義和格式上變得晦澀難懂和似乎不受控制,而且上游 API 的變更讓網關很難處理兼容性問題,這就是所謂的有得必有失吧。再後來隨着開放平臺、共建生態迎來了大潮,這時已經是 2015 年了,我們又反客爲主迅速推動上游進行 API 標準化的接入和改造,這隻能說之前網關更關注 API 接入的效率,後來更關注 API 接入的質量。

1。泛化調用

泛化調用在當時起到了非常重要的作用,雖然現在已經很少在網關直接粗暴的提供泛化調用的 API,但是泛化調用在其它地方有了更廣泛的應用,比如 API 測試等等。

下面我們就結合着泛化調用來說下網關服務調用的那點事,回顧上文我畫的一張 API 網關的架構示意圖。首先,泛化調用是網關服務調用組件提供的一種服務調用方式,而整個服務調用概括的講主要有路由尋址、協議轉換、分發調度三個步驟。

1.1 路由尋址

一個 API 請求在調用上游服務之前,首先要通過請求方法名進行上游服務的路由尋址,尋址包括上游服務端的接口名、方法以及分組。具體來講,網關會通過請求方法名在內存路由表中去尋找對應的服務實例地址,如果沒有找到,就會去 API 元數據庫中讀取配置並完成服務的實例化。這是因爲網關的泛化調用是一種基於配置的實現方式,所有 API 的方法和參數都以配置的方式存儲在元數據庫中。所以,這類 API 服務在網關是一種動態實例化的方式,它不是在服務端啓動時就初始化好的服務,而是一種懶加載的方式。

在 API 服務初始化的時候我曾有過這樣的設計考量,API 服務是否可以改成配置發佈加載的方式?之所以沒有這麼做,主要因爲 API 實在是太多了,有很多 API 都沒有被調用過,另外就是 1~2 次調用 API 只會被初始化在少量的 API 網關上,對整體而言也不會有太多的資源佔用。

1.2 協議轉換

在獲取到對應的服務地址之後,就需要對請求參數進行協議的轉換和解析。當時在沒有泛化調用之前,網關依賴上游提供二方包,並需要依據二方包的接口定義,才能完成請求參數的解析與協議的轉換。而在有了泛化調用之後,網關可以不依賴於上游服務提供的二方包,就可以進行協議轉換了,這是因爲泛化調度在實現上採用了反射和動態代理的方式。

1.3 分發調度

泛化調用不僅可以去掉對上游二方包的依賴,網關還在泛化調用的基礎上實現了一套通用協議的適配模型,基於代理模式實現對上游不同服務的分發調用,解決了之前每個服務在網關都需要開發一套適配邏輯的實現。

1.4 配置中心

在網關實現了統一接口和泛化調用之後,服務端似乎進入開掛的時代,主要是徹底釋放了服務端的研發效能,需要哪個 API 客戶端去配置一下就好了,不過好景不長,此時網關已經進入了深水區,出現的問題就比較難解決了。接下來面對的就是 API 元數據的即時更新問題,由於泛化調用是依賴於配置的,所以當配置變更後,就需要對網關的服務實例進行重新初始化,所以爲了解決這個問題,一個網關 API 元數據配置中心的系統就這樣誕生了,以下簡稱爲配置中心。

配置中心的演進主要有 3 個版本,第一個版本是定時檢查數據庫變更配置的處理方式,第二個版本是消息廣播變更配置的處理方式,第三種是基於 Zookeeper 監聽配置的處理方式。下面我們就逐一來看下這三個不同版本的實現方式。

v1 定時檢查數據庫變更配置

其實無論哪個版本都是採用數據庫作爲元數據的持久化存儲的,不同之處主要是在觸發網關進行服務重載的機制不同。

由於泛化調用是一種懶加載的實現方式,所以只有當一個請求需要獲取元數據信息時,網關纔會去查詢元數據庫,並將獲取到的配置信息緩存到網關的內存中,同時進行服務實例的初始化。而當數據發生變更時,網關是如何感知到配置變更的呢?所以網關採用的是線程輪訓的方式,通過對比數據進行判斷,如果對比數據不一致,就進行服務實例的重載,如果一致,就不做任何處理。不過,這個方案有個硬傷,就是即時性的問題,由於線程輪訓的間隔不能過快,以防止對數據庫造成不必要的壓力,而且,元數據庫變更也不會很頻繁,過於頻繁的輪訓也是一種資源的浪費,所以,我們設置的時間間隔大概是 10 分鐘,當然,網關只有一個輪詢線程,不是每個 API 都有一個。

除了線程輪訓的方式,我也思考過有沒有別的方式,比如使用緩存定時過期的方式,緩存過期了就去數據庫裏查詢一次,Guava 的 LocalCache 就可以實現多種本地緩存策略 ,不過這種方式比較適合在防止熱點數據穿透緩存的場景裏,在網關裏緩存過期了是要與服務重載相關聯的,所以什麼場景下網關去檢查緩存是否過期了,這之間並沒有建立起直接的關係,總不能每調用一次接口就去檢查一下緩存吧。而且緩存穿透本身也是有風險的,尤其是冷數據加載,可能直接將下層的數據庫打爆,這種方式造成的線上問題也是屢見不鮮。

v2 消息廣播變更配置

後來我們又上線了一版消息廣播變更配置的版本,改造點是網關的配置中心客戶端裏將線程輪訓的方式替換消費 MQ 的方式,MQ 是在配置中心 OPS 裏變更配置時進行生產,以廣播的方式發送給網關所有實例。

v3 基於 Zookeeper 監聽配置

再後來終於迎來主流版本 —— 基於 ZooKeeper 構建配置中心的實現方式,ZooKeeper 是一個分佈式的協同系統,它有很多優勢,比如,基於樹型的存儲方式、分佈式的部署架構、Leader 選舉機制、基於長連接的雙向通道等,我還是比較推崇它的。

基於 ZooKeeper 的 Watcher 特性,我們把網關的 API 配置信息存到 ZooKeeper 節點裏,在網關的配置中心客戶端加載到 API 服務後,就去訂閱 Zookeeper 中對應節點的數據變更事件,當 ZooKeeper 數據節點變更後,ZooKeeper 就可以以事件驅動的方式通知到網關實例,從而進行配置變更和服務重載。

如何解決配置中心的一些小問題?

其實在整個演變過程中,除了技術選型有了大的調整外,在細節方面我們也在不斷優化,與線上的各種小問題進行堅持不懈的鬥爭。

小問題 1:首次獲取配置失敗

爲什麼強調一下首次,因爲重啓服務器就會造成內存數據的丟失,網關就需要去配置中心重新獲取配置,這就是首次獲取,不過這時如果獲取配置失敗了,網關就尷尬了。所以爲了應對異常情況下可能無法獲取到配置信息的情況,最開始的解決辦法是接口調用時傳入一個默認值,在異常情況下會返回默認值,簡單又有效。不過這種方式在某些場景下,又往往會產生一些意想不到是小事故,我說的就是那種有新老流程需要切流而設置的開關場景,默認配置設置爲 false 走老流程,可是在逐步切流完成之後,這個配置開關和老流程並應該被遺棄,而是由於各種原因活了下來,隨後又隨着各種演進,老流程逐漸變成了殭屍代碼,然後某一次重啓一個網絡抖動就把系統的殭屍炸醒了,緊接着報警就想起了,而這種情況,我還真的遇到不止一次。

所以,後來在配置中心客戶端裏做了一點過度的設計,爲啥說是過度,因爲廢棄的代碼還是要刪掉的,不能指望着別人去保障你的業務。所謂的過度設計其實只是增加了通過本地 Properties 文件對配置數據進行落地存儲的容災策略,其策略是服務器重啓後,如果無法從 ZooKeeper 獲取配置後,就從本地 Properties 文件獲取上一次的配置數據進行加載,如果也無法從本地 Properties 文件獲取配置,才返回設置的默認值。

這裏再說下在 v1 基於數據庫通過線程輪詢的實現方式裏,網絡抖動導致大量回源查詢,如果查詢沒有設好超時時間和重試次數,就可能會產生大量的異常日誌和線程阻塞,嚴重的還會把服務器拖死了。

小問題 2:更新配置失敗了

這裏的更新失敗不是指數據庫更新失敗,而是指發送 MQ 或寫 Zookeeper 失敗,這裏提醒一下,千萬不要把這步操作放到數據庫事務裏,否則一個網絡抖動你的數據庫可能就死了,這樣寫的我看到過好幾例。大多數場景下配置中心都是要一定先數據庫成功後再進行別的操作,這裏我們取了個巧,就是先發送 MQ 或寫 Zookeeper 然後再保存數據庫,因爲這裏的每個操作都是冪等的,而且數據保存失敗了也容易識別出來,所以即使數據庫保存失敗進行了重試,對 MQ 和 Zookeeper 也不會有任何影響。

之前曾考慮在保存操作完成後在後端啓動一個線程,對兩個數據源的數據進行校對,如果不一致就進行訂正,訂正成功就結束線程,訂正失敗的話,就短暫休眠,然後繼續訂正。對比來看,這種實現方式就已經有些複雜了,更別說分佈式事務了,所以一個先後策略的調整就可以解決的問題,不一定非要把系統做的很複雜,這在我後來的架構修煉之路上,遇到過很多這樣的情況,大道至簡方是王道。

再後來隨着微服務框架的逐步完善和成熟,配置中心已經有越來越的產品被推出,比如 spring cloud config、diamond、apollo、disconf 等等,而不必要自己去開發了。

3。總結

言而總之,本篇文章重點講述了流量調度的配置中心、泛化調用。下篇文章,我將繼續介紹架構演進構建 TCP 長連接網關。如果你覺得有收穫,歡迎你把今天的內容分享給更多的朋友。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/kA7l75wDvRHh--sQactV4w