放棄 Spring Cloud Gateway!Apache APISIX 在「還唄」業務中的技術實踐

語言: CN / TW / HK

不同行業之間,都會存在一些業務屬性上的差距。對於金融領域的應用軟體來說,因其涉及到錢等因素,所以在業務上會有以下獨特屬性:

  • 穩定性。金融領域跟錢強相關,這對於業務穩定性就有著非常嚴格的要求,穩定性一旦出現問題,它將影響整個交易系統的成敗。

  • 強監管。強監管一般是針對生物醫藥領域、醫療領域和金融領域,因為它們所呈現的內容都與人的生命相關。所以,更高層面的強監管要求勢必會影響一些業務層面的選型和架構呈現。

  • 準確性和有效性。由於跟錢強相關,所以在數字層面的呈現更是要求零偏差。就像股票價格一樣,它的數字呈現都是精確到了每分每秒和固定數位的。

基於以上這些特點,金融行業軟體系統在進行系統設計、機房拓撲以及中介軟體選型時,就會出現一些與其他通用行業不太一樣的地方。

對 Java 的那些愛與恨

金融系統為何獨愛 Java

Java 自誕生以來就深受開發者的喜愛。在中國有將近 50% 的開發者在使用 Java 作為開發語言。這不單單是因為其語言的優勢,也因為 Java 相關的生態非常龐大,尤其是國內的金融系統很多都是基於 Java 的,這導致有段時間大家都誤以為所有的系統都是用 Java 做的。

近 15~20 年以來,大部分金融系統基本都選擇了 Java 技術棧,深究其原因,我們認為主要是因為 Java 技術棧有以下幾點優勢。

正是如此,Java 逐漸得到了金融類軟體系統的青睞。

雲原生時代下的 Java 現狀

隨著技術行業的快速發展,單體架構逐漸被淘汰,微服務和雲原生時代正在風靡四海。然而在近幾年的技術大環境下,作為面向物件的高階語言,Java 也在一些業務場景中開始略顯疲憊:

首先,Java 效能較低,這點對比一下 C 語言相關技術棧就會明白。Java 是基於虛擬機器,它的記憶體管理是交給虛擬機器來解決的,所以當面對一些高效能或動態變化的業務場景時,Java 語言在處理上沒有那麼強勢。

其次,Java 語言需要更多的資源。一個架構的打造如果不考慮成本,很多問題都很好解決,但在雲原生時代下,所有的資源計算變得越來越細、越來越顆粒化。Java 在運作時需要消耗大量的資源,由於 Java 分量重和需要重啟的基礎特性,因此在高 QPS 或者業務連續性要求較高的場景下,該語言會更容易出現問題。

最後就是指標變數的問題。習慣於寫 C/C++ 語言的同學都知道,指標是一個非常好的資源。但 Java 是基於虛擬機器,它把記憶體管理交給了 GC(Garbage Collection),而不是由手動程式進行管理,所以對於一些特定情況或者高併發、高訪問量和高效能的場景下,Java 的實際效能可能就略顯不足了。

還唄為何選擇 APISIX?

數禾科技是一家提供智慧化金融的服務平臺,旗下主要產品有還唄、還享花等。還唄 APP 是一款基於消費多場景的分期服務平臺,通過與持牌金融機構合作,為大眾提供個人消費信貸服務,併為小微企業主提供貸款資金支援。在業務架構層面,還唄的產品實現一直是依賴 Java 技術棧的。

Spring Cloud Gateway 是 Spring Cloud 生態下為更好管理微服務而誕生的閘道器專案,對於公司業務以 Java 為主要開發語言的情況下,Spring Cloud Gateway 通常是個不錯的 API 閘道器選擇。但在近期的 API 閘道器迭代過程中,還唄放棄了使用已久的 Spring Cloud Gateway,而是選擇了 Apache APISIX。

架構的前後變化

在架構層面,還唄在使用 APISIX 前後呈現瞭如下圖所示的變化。

在左側的使用前架構中,還唄一共使用了三套網關係統,並把閘道器分為入口閘道器和出口閘道器兩大類。其中在運營系統閘道器和出口系統閘道器中,都使用了 Spring Cloud Gateway 作為閘道器,而在業務系統閘道器中則使用了 OpenRestry 作為業務系統閘道器。

對於一開始使用 Spring Cloud Gateway 作為運營和出口系統閘道器,主要是看中了 Spring Cloud 龐大的生態系統,以及簡單易部署和易維護的分散式系統開發框架,所以在早期進行業務架構部署時,為了更快搭建起業務而選擇使用 Spring Cloud 全家桶。

但隨著業務慢慢發展,原先架構中的閘道器開始出現一些穩定性的問題,比如記憶體溢位、CPU 使用率過高等情況。為了升級閘道器效能及統一多個閘道器,還唄將架構中的閘道器全部統一替換為了 Apache APISIX。

在新閘道器架構中,業務系統閘道器會優先把請求流量通過服務發現的方式直接轉發到業務系統。如果後端應用在 Consul 中沒有健康 Pod 或者後端應用不支援服務發現等,就會把流量轉發到以前的內網 K8s Ingress,作為兜底的上游來使用。

新架構同時也統一了出口閘道器的兩個應用,新出口閘道器部署在 K8s 叢集外的外聯區。同時也在出口閘道器叢集前新增一個 SLB,可以統一出口閘道器的入口 ,方便沒有服務發現能力的應用或者其他 VPC 內的系統呼叫。

基於 APISIX 的應用實踐

實際業務情況下,由於內部已存在多種閘道器架構,沒辦法直接使用 Apache APISIX,於是還唄基於 APISIX 進行了一些改造和構建。

APISIX 構建部署

在內部進行開發時,將 APISIX 閘道器的程式碼和定製程式碼存放在不同路徑下,兩者協同工作,各自可獨立迭代。在部署時則採用 Docker 映象方式部署,構建一個 APISIX 指定版本的基礎映象,然後再把自定義程式碼打包形成新映象。

自定義程式碼打包時沒有使用 lua_package_path 來指定程式碼目錄,而是直接覆蓋基礎映象 apisix 原始碼目錄,如果有同名檔案則覆蓋原始碼檔案。Dockerfile 如下所示:

FROM registry.xxx.net:5001/apisix-shuhe:v1.5ENV APP_NAME={{APP_NAME}}COPY {{PRODUCT_FILE}} /tmp/deploy2/artifact.tar.gz 
RUN mkdir /tmp/deploy/ && tar -xf /tmp/deploy2/artifact.tar.gz -C /tmp/deploy/ && \cp -R /tmp/deploy/apisix/ /usr/local/apisix/ && \cp /tmp/deploy/bin/apisix /usr/bin/apisix && \cp /tmp/deploy/conf/apisix-$APP_NAME.yaml /usr/local/apisix/conf/apisix.yaml && \cp /tmp/deploy/conf/config-$APP_NAME.yaml /usr/local/apisix/conf/config.yaml && \set -x && \bin='#! /usr/local/openresty/luajit/bin/luajit\npackage.path = "/usr/local/apisix/?.lua;" .. package.path' && \sed -i "[email protected]*@$bin@" /usr/bin/apisix && \rm -rf /tmp/*

複製程式碼

APISIX 的日誌預設儲存在本地(也可以通過 Syslog 等外掛收集),通過調整 nginx 配置模板和判斷啟用的 Profile 來決定日誌儲存在本地還是通過 Syslog 儲存到 FLUENTD 中。同時在構建映象時替換模板中 FLUENTD_HOST 變數。如下所示:

{% if gw_profile and string.find( gw_profile,'local') then %}access_log logs/access.log main;error_log  logs/error.log warn;{%else%}access_log syslog:server=${FLUENTD_HOST}:5141 json_format;error_log  syslog:server=${FLUENTD_HOST}:5142 warn;{%end%}

複製程式碼

在 nginx 配置模板中,還唄不光修改了日誌儲存,還調整了迴圈新增 ENV 環境變數、迴圈新增  lua_shared_dicts 配置及寫死一些 NGINX 其他調優引數。

因為公司是按照業務流量劃分為多種閘道器,這些閘道器的基本功能都差不多,因此還唄內部採取了「一套程式碼部署多個閘道器應用」方案。通過 Profile 功能配置各個閘道器的 config-xxx.yaml 檔案,然後通過公司 DEVOPS 平臺構建映象時,根據應用名構建不同閘道器的 Docker 映象即可。

公司級定製外掛

在內部訪問運營系統頁面時,會呼叫很多後端的 API 獲取資料,這些 API 都需要在 API 閘道器中配置對應的白名單。在頁面中根據登入運營系統使用者的角色不同,能夠訪問的 API 範圍也不一樣,因此許可權系統也需要維護相關 API 列表。每當在頁面上新增後端 API 呼叫時 ,都需要開發人員在閘道器頁面及許可權系統頁面配置兩次,工作冗餘且重複。

為此,還唄把閘道器配置與許可權系統配置打通,只保留許可權配置系統的配置入口,閘道器配置管理系統則定時拉取許可權 API,之後轉換成閘道器 API 白名單配置。這樣做不僅能減少使用者一次配置操作,同時也協助許可權系統進行了許可權管控。可以保證在運營頁面呼叫的後端 API,一定是在許可權系統配置了相關許可權。

在公司的實際業務中,經常會遇到原生外掛不能滿足實際需求的情況,就需要定製開發。好在 APISIX 提供了很多工具類,參照原生外掛就可以輕鬆實現,開發過程也非常簡單。以下列舉了還唄內部基於 APISIX 進行的其他定製外掛:

閘道器流量灰度

之前還唄使用的 K8s 容器是 OpenShift(現已升級為 ACK 叢集),其中 Ingress 是 Haproxy 搭建。由於公網 K8s Ingress 的 Haproxy 不能把一個域名的流量轉發到兩個 Namespace 的路由中,因此考慮把新閘道器部署在和老閘道器相同的 Namespace 下。即域名的路由下掛載多個服務,之後便可以通過路由調整流量比例,控制流量走新閘道器還是老閘道器。

具體實施流程如下圖所示,在老閘道器的 Namespace 下新增 c、d 組用於部署新閘道器,通過路由控制新老閘道器的流量比例。

Java 層面閘道器的考慮因素

很多 Java 工程師在微服務架構中都會選擇 Spring Cloud,主要是語言繫結,並用類庫的方式放在程式碼裡。但是在實踐過程中可能會出現升級困難的情況,如果團隊是多語言就需要維護多個類庫,假設有 10 個版本與 10 種語言,就需要維護 100 個類庫。

此時就可以通過代理的方式(即 API 閘道器)把多版本和多語言的問題輕鬆解決。那 Java 技術棧公司選擇 APISIX 作為 API 閘道器後都有哪些收益?我們根據還唄的實踐經歷,從以下兩個角度進行了總結。

公司角度

1. 功能與效能兼具

還唄在內部使用 4 核虛擬機器無外掛空跑壓測 APISIX 的 QPS 可以達到 80K,很好地解決了 Spring Cloud Gateway 在承接 C 端流量時出現的效能問題,而且在生產環境中發現 APISIX 相較於之前閘道器效能提升了 30% 以上。

其次,得益於雲原生屬性,APISIX 在實際的測試中完全可以滿足公司的需求,比如認證鑑權、可觀測性、服務發現、限流限速以及四層和七層流量轉發。而在功能擴充套件方面,APISIX 也支援了 70 餘款外掛,大部分的業務可以使用其原生外掛,很大程度上減少了開發工作。

2. 業務支出成本下降

在使用 APISIX 之前,如果效能出現了瓶頸,公司只能通過不斷的增加伺服器來解決這個問題,因此相應的硬體成本也會非常的高。

還唄在進行成本計算時發現,使用 APISIX 後,伺服器的數量大概減少了 60% 左右。統一技術棧後,業務上也可以很輕鬆地基於 APISIX 原生框架實現功能的擴充套件,節省了開發成本,加快了專案上線時間速度。

開發者角度

1. 滿足業務需求

業務中所使用的軟體或技術都應該是為需求而服務。從實際測試結果及調研資料來看,APISIX 的穩定性、可觀測性、可擴充套件性會更好。

軟體最終服務於業務。如果業務需要,可以為公司節約資源,那麼無論公司的技術棧是什麼,都會使用最符合公司業務的元件。

2. 降低維護成本

相比之前使用的 OpenResty,APISIX 的學習成本相對較低,維護起來也比較省心。同時,APISIX 豐富的外掛簡化了一些通用功能的實現與部署,大大節約了專案上線的時間。

同時利用 APISIX 強大的日誌和動態除錯功能,業務可以很輕鬆地排查出故障點,從而快速定位、節約時間。

總結:金融業務發展趨勢

在過去的十年裡,網際網路金融從「野蠻生長」開始逐漸向「精耕細作」模式轉變,這個轉變主要涉及到的就是系統的變革。

在野蠻生長階段,業務講究的是效率。為了業務更快速地建設,在基礎架構選擇的時候,負責人更多是選擇自己熟悉的語言架構進行搭建。不同的負責人便會選擇使用不同的技術棧,因此留下了很多技術債務。從 2017 年開始,依舊活躍的金融企業或服務公司大多都會面臨同樣的技術現狀,那就是存在多套技術元件。這時就需要進行基礎設施的統一。

來到精耕細作階段,企業就需要對系統進行垂直拆分,由以前的煙囪式拆分成前臺、中臺及後臺等模式。系統到達一個穩定階段時,就需要把一些東西夯實下來。

而系統建設的根本目的其實就是為了共用。重複性使用越強,系統的運維成本就越低。所以很多公司到了精耕細作階段,要麼是進行系統的垂直拆分,要麼就是進行基礎元件的下沉,進而控制運維成本。

作為企業來說,成本優先依舊是需要考慮的原則。野蠻生長階段可能只需要儘快實現業務,而在目前大環境下,預算範圍之內肯定是成本優先。這樣的話,效率和成本永遠只能保住一項。因此在成本有限的情況下,企業就會少談技術的先進性。技術人員在選型的過程中,就不會考慮當下選擇的這個技術對團隊有多大沖擊、對現有的運維和架構帶來多少收益等等,更多是從成本角度考慮。