全國首個政企採購雲平臺:政採雲基於 Dubbo 的混合雲跨網方案實踐

作者:王曉彬:政採雲資深開發工程師,負責基礎服務相關工作

徐錫平:政採雲資深開發工程師,負責基礎服務相關工作

對雲島業務結構的公司來說,雲平臺屬於公司內部、完全可控的局域網,而島端則是有自己安全網絡策略的獨立內部網絡。需要雲島通信時,會基於需求,按客戶要求走流程開通一些端口,這個過程需要一定的成本且不完全可控。業務上,如果這種跨網需求增多,則會逐漸變成痛點。如果可以搭建一個透明的跨網傳輸網絡,配合良好的頂層設計,就可以在業務支撐、安全管控和運維成本中尋求較好的平衡。

本文將介紹政採雲基於 Dubbo 的跨網方案落地過程中面臨的技術挑戰、社區合作以及更深層次抽象的一些思考。 在政採雲這種政企業務場景中的數據跨網,與業界公有云、自建私有云的公司相比,既有共性又有自己的特點,希望能爲大家提供新的思路或者啓發。

前言

穩定、高效、可靠的基礎設施是互聯網企業應對業務高峯流量的底層基石。作爲政採雲的基礎技術平臺,基礎平臺部一直致力於通過業內前沿技術的落地,保障公司內部所有業務在線生產系統所依賴的基礎技術平臺能穩定、安全、低成本、可持續地運行與發展。

由於公司對 Dubbo 框架的重度使用,跨網數據傳輸系統一般基於 Dubbo 特性開發,在政採雲內部就有多個版本的實現。

早在幾年前,政採雲就上線了基於 Dubbo Filter 轉發的方案,它解決了島到雲的單向數據傳輸,安全認證等問題。另外,業務部門也有按照自己的需求,推出網狀點對點的方案,實現了一定程度的透明傳輸。

結合前兩年的探索實踐以及業界相關領域技術的成熟度,2022 年下半年,我們對各跨島方案,進行了整合升級,也就是現在的高速公路方案,保障跨島標準化同時,解決了之前方案實踐過程中面臨的很多業務痛點,包括:

  • 單向傳輸:因爲架構原因,如需雙向需要對等重新部署一套,成本較大。
  • 白名單開通成本高:點對點的網狀架構,需要兩兩開通白名單,因爲政企網絡特殊性,開通流程複雜且慢。
  • 平臺維護成本高:業務各自一套數據傳輸平臺,重複建設且運維成本高。
  • 公共功能的缺失:核心功能,業務可以按需開發,但是數據審計、鏈路追蹤、可觀測性等公共特性,往往沒有足夠投入。

跨網數據傳輸系統演進

1.1 歷史架構

image.png

圖一

自左向右、自下而上進行模塊介紹:

  • 業務 Web:業務 Web 作爲數據發送方,調本地集羣 Provider 時,攜帶跨島信息過去(Dubbo 上下文)。
  • 島業務 Center:本地虛擬 Provider,通過 Filter 攔截跨島請求,通過 http 傳送到雲平臺 Dubbo 網關,返回數據後反序列化返回島業務 web。
  • Dubbo 網關:接收 Http 請求,通過泛化調用雲端 Provider,處理數據後返回業務 Center。
  • 雲業務 Center:普通 Dubbo Provider。

1.2 高速公路架構

image.png

圖二

1.2.1 隧道機制

隧道技術是一種通過使用互聯網絡的基礎設施在網絡之間傳遞數據的方式。使用隧道傳遞的數據 (或負載) 可以是不同協議的數據幀或包。

高速公路架構中,使用了隧道這個概念。兩端(業務層)是 Dubbo 私有協議,跨網傳輸過程中,則使用了 http 協議,http 協議可以更好的被中間設備、網關識別轉發。這個機制的最大便利在於對業務的低侵入性。對於業務集羣的應用完全不需要修改。

image.png

圖三

除了路由標記,出口 / 入口 Dubbo 協議字節流沒有任何業務外信息,所以可以路由任何 Dubbo 請求。

image.png

圖四

1.2.2 主要節點

  • 客戶端 Sdk:不改變用戶使用 Dubbo 的方式,多種形式提供 Dubbo 的路由。
  • Dubbo 出口網關 :代理 Dubbo 流量出口。
  • Dubbo 入口網關 :代理 Dubbo 流量入口。
  • 統一網關 :基於 Apisix,代理跨網間所有流量,可以擴展鑑權、審計、限流等特性。

挑戰與應對之策

如前言中所述,已有的幾個方案設計上存在了一些問題,落地後也限制了使用了場景。在架構上,我們提出了高速公路方案,選擇了全雙工的對等網絡傳輸框架。角色上,雲平臺定位一個特殊的島端應用,遵循 P2P 實施原則。而對用戶而言,高速公路是一個通往島端的隧道,遵循對用戶透明原則。我們可以先來看下在搭建平臺的過程中面臨的一些挑戰以及解法。

2.1 技術挑戰

結合當下跨網數據傳輸系統面臨的處境,並對業界 Dubbo 跨網方案做過一番調研後,在平臺搭建上確定瞭如下三期目標:

  • 一期目標:網絡能力建設,簡單來說是搭建基於 Dubbo 的傳輸通道,上層功能先維持不變。
  • 二期目標:業務上,找業務先行試點,基於反饋,小步快跑,快速迭代;技術上,尋求 Dubbo 社區協作,增強對 Dubbo 相關技術風險的把控,同時抽離通用特性,反饋社區。
  • 三期目標:抽象出更通用的網絡框架,從而使語言層,傳輸協議層、及中間件層獨立擴展,一鍵切換。

在上述三期目標基本落地後,高速公路系統不僅可以跑起來,同時擁有非常強大的擴展性,更好的承接業務需求及共建。在這過程中,我們需要解決不少技術問題。

2.1.1 客戶端路由

如前面歷史方案所述,其場景被限制爲島到雲的單向數據傳輸,特點如下:

  • 客戶端無路由能力:Consumer 端只能指定是否路由到雲平臺,而不能指定其他島端。
  • 基於 filter 的擴展:Dubbo 的 Filter 並不是爲路由設計的,在此基礎上較難擴展。
  • 需要本地 Provider 角色:Consumer 端發出的請求,必須由一個註冊在 Zookeeper 下的 Provider 兜住,然後 Filter 根據上下文決定是否轉發,這就限制了業務方必須部署一個本地 Provider 應用(哪怕是空應用),才能做到跨網訪問。

我們要解決的問題之一,就是打破單向傳輸瓶頸,客戶端可以更自由的路由到目標雲 / 島。我們設計了以下幾種路由方式:

  • 註解方式:使用 @DubboReference 提供的通用 parameters 參數,設置路由目標,可以達到方法粒度的路由。
@DubboReference(check = false, parameters = {"ENV_SHANGHAI", "ALL"}) //all表示所有方法,可以單獨指定
private DemoService demoService;
  • 配置中心指定:把以上 parameters = {"ENV_SHANGHAI", "ALL"} 信息,在配置中心配置,達到同樣的效果,這種方式對代碼完全無侵入。
  • 線程指定:這種方式最靈活。
AddressZoneSpecify.setAddress(Enviroment.SHANGHAI);
demoService.play();

無論哪種路由方式,基於“用戶透明“的原則,都不改變用戶使用 dubbo 的方式。

2.1.2 Dubbo 請求地址切換

客戶端路由最小限度地侵入業務代碼,達到了透明調用遠程服務的目標。但是,用戶仍舊需要部署一套虛擬 Provider 應用,接收請求後按規則進行路由。

爲了避免部署多餘的應用,我們需要有一定的機制,直接把 dubbo 流量切換到遠程。

image.png

圖五

解決了切換問題後,本地的 APP2 不再需要,甚至 zk 也可以移除。當然,如果業務同時有本地和遠程的調用需要,也可以繼續存在。

圖四原先,我們準備通過 Dubbo 的 Route 自定義擴展,去實現動態切換地址的能力。查閱資料後,發現 Dubbo 已經提供了類似能力。

https://cn.dubbo.apache.org/zh-cn/docs3-v2/java-sdk/advanced-features-and-usage/service/specify-ip/

該特性放在 Dubbo 的子工程 dubbo-spi-extensions 中,同樣以 Route 擴展形式實現。

但在實際使用過程中,我們遇到如下問題:

  • 不支持 Dubbo2:使用 Dubbo2 時,直接以異常的形式提醒暫不支持。
  • NPE 異常:某些場景下調用出現了 NPE 異常。
  • 丟失部分信息:Router 下構建新 Invocation 時,丟失了 version、group 等信息。
  • 重試異常:遠程 Provider 如果發生了異常,客戶端在重試的時候,選擇了本地集羣 Provider 調用,造成錯誤。

作爲一個嚐鮮新特性,我們理解功能存在不穩定的情況。但這個功能作爲我們跨網方案的技術要點,又必須解決。所以,我們通過 PR 的形式,把相應補丁提交到 Dubbo 社區。這個過程中,我們聯繫到了 Dubbo PMC 遠雲大佬,一起討論和完善 PR,直到解決所有已知問題。

2.1.3 出口網關的實現

在圖 4 中,通過切換地址,我們似乎可以直接訪問遠程應用,並且架構非常簡單。但是遺憾的是,存在幾個難以解決的問題:

  • 網關組件的限制:在雲島 / 島島間,存在一系列網關組件,來提供轉發、負載均衡的功能,比如 SLB、NGINX、WAF。這些組件並不能識別私有的 Dubbo 流量並轉發。
  • ip 白名單開通成本高:類似 P2P 方案,需要點對點開通 IP 白名單,成本巨大。
  • 升級維護複雜:客戶端通過集成 SDK 的形式轉發,後續如需要劫持流量進行擴展,需要同時對每個接入應用進行升級。

image.png

圖六

針對以上問題,我們的設計中,需要加入 Dubbo 網關的角色,來實現以下目標。

兩端 ip 收斂

  • 顯著減少網關長連接數量
  • 弱化服務註冊發現流程(每個環境只有一個 Dubbo 網關,直接配置即可互相發現)
  • 簡化鑑權、認證流程。一條鏈路可以使用白名單,一羣則只能配置較複雜的鑑權

② 兩端功能收斂

  • 客戶端的 SDK 專注路由功能,基本不用升級
  • 擴展功能放在 Dubbo-Proxy,統一升級,業務端無感知

Dubbo-Proxy 作爲業務網關,可以減輕對業務端的侵入,起到類似分佈式運行時(Dapr)作用。但是,在引入之前,需要解決一些現實的技術問題。其中,最重要的問題是如何接收陌生的 Dubbo 流量,然後進行轉發。做了一些相關調研後,有兩個方案可用:

  • 通用 Provider

直接在 Dubbo-Proxy 註冊一個普通的通用 Service,客戶端的 SDK 利用 Filter,劫持流量,直接調用通用 Service 後處理數據返回。

  • 註冊虛擬節點

該方案來源於遠雲。客戶端在本地 zk 訂閱遠程節點時,通知 Proxy,Proxy 獲取訂閱的信息後(預先訂閱所有 zk 變更),主動註冊相應虛擬 Service(對 zk 來說,註冊一個節點的參數只是字符串)到 zk 上。這樣,可以把客戶端的遠程流量“騙”到 Proxy ,Proxy 再使用服務端泛化,接收請求並轉發。

以上兩種方案,都可以實現出口網關。但是,在設計上,角色間需要多次交互,才能達到目的。那麼,是否有更簡潔的方式,直接支持這種接收和轉發呢?

首先,我們對 Dubbo 源碼進行了調研,看 Provider 接收到陌生流量(無相應 Service)後會如何處理,是否有擴展點可以攔截。發現在 Byte 流解析階段,Dubbo 即對 Service 進行了檢查,不存在直接拋異常返回。

image.png

圖七

在 Provider 處理的生命週期中,Decode 出於非常早期的階段,幾乎沒有什麼擴展點可以攔截處理。因爲快速失敗的理念,早期的檢測確實可以避免後面無謂的代碼執行消耗。但是,對比 Spring ,Dubbo 在擴展性上是有不足的,即對於一個通用的異常,卻沒有相應的擴展機制。

我們決定在 decode 的基礎上,加上對這個異常的擴展。主要思路是,在 decode 被調用處,catch 住這塊異常,通過 SPI 的形式,獲取擴展實現,可以定製異常信息,也可以控制 decode 流程重試。這塊修改難度並不大,私有版本上順利通過測試,同時提交 PR 到社區。這個過程中,遠雲大佬幫忙發現了一個併發安全的 bug,並給了不少減少風險的建議。

//解碼結束後,無論是否異常,都將進入這個方法
void handleRequest(final ExchangeChannel channel, Request req) throws RemotingException {
        if (req.error != null) {
            // Give ExceptionProcessors a chance to retry request handle or custom exception information.
            String exPs = System.getProperty(EXCEPTION_PROCESSOR_KEY);
            if (StringUtils.isNotBlank(exPs)) {
                ExtensionLoader<ExceptionProcessor> extensionLoader = channel.getUrl().getOrDefaultFrameworkModel().getExtensionLoader(ExceptionProcessor.class);
                ExceptionProcessor expProcessor = extensionLoader.getOrDefaultExtension(exPs);
                boolean handleError = expProcessor.shouldHandleError(error);
                if (handleError) {
                    //獲取異常擴展,執行wrapAndHandleException操作,需要重試的場景可以拋出retry異常
                    msg = Optional.ofNullable(expProcessor.wrapAndHandleException(channel, req)).orElse(msg);
                }
            }
        }

        res.setErrorMessage("Fail to decode request due to: " + msg);
        res.setStatus(Response.BAD_REQUEST);

        channel.send(res);
    }
}

 //handleRequest過程中的retry控制
 public void received(Channel channel, Object message) throws RemotingException {
       //解碼 
       decode(message);
        try {
            handler.handleRequest(channel, message);
        } catch (RetryHandleException e) {
            if (message instanceof Request) {
                ErrorData errorData = (ErrorData) ((Request) message).getData();
                //有定製,進行重試
                retry(errorData.getData());
            } else {
                // Retry only once, and only Request will throw an RetryHandleException
                throw new RemotingException(channel, "Unknown error encountered when retry handle: " + e.getMessage());
            }
            handler.received(channel, message);
        }
    }

關於 ExceptionProcessor 擴展,我們在官方擴展包 Dubbo-Spi-Extensions 中,提供了一個默認實現,允許控制重試解碼,並自定義異常處理。

2.1.4 中心網關

圖 6 的架構,已經非常接近最終實現了,但是缺了一箇中心網關角色。引入這個網關 (基於 Apisix ) 的原因:

  • 白名單問題:雖然 Dubbo 網關收斂了終端 IP,但是要實現島島互通,還是得兩兩互開白名單。引入中心網關(雲平臺)後,每個島單獨和雲平臺互開即可。白名單開通複雜度從 O(n*n) 變爲 O(n)。
  • 統一網關的好處:作爲公司級網關,可以統一對所有應用進行限流、鑑權、審計、可觀測性等功能拓展。

更多思考

無論公司內外,能選擇的跨網方案非常多,我們會去選擇一個能解決痛點的,而不是完美的方案。落地方案一般比較保守,但是對於架構的思考,一定是需要更超前的。

http 協議導致的性能損失

前面說到,在 Dubbo 網關和中心網關間,我們使用了 Http 協議。對比 Dubbo 等精簡協議,Http 協議顯然更臃腫。但是,也許這是現階段最合適的方案。除了避免私有協議在網絡設備中的“艱難前行”,Http 協議開發成本更低,相應落地風險也更小。一些新技術,也許是我們後續發展的方向。比如 Higress,支持 Triple 協議(基於 Http2)交換信息,在獲得更高性能的同時,也解決了設備識別問題。但是選擇 Higress,需要面對學習認知成本、新開源 BUG 多等問題,同時它可能更適合內部網絡(即使跨公網也能搭建 VPN),而不是我們各私有島端(客戶自定義安全策略)的網絡互通。

擴展性不足

高速公路是一個基於 Dubbo 的跨網方案,在協議與框架層,與 Dubbo 的綁定比較深,但是它應該能做的更多。也許很快,會接入 Http、Mq 等應用協議的流量,或者 Python、Go 等語言的客戶端,甚至是 Mysql 的數據互通。這個時候,要麼對架構大改,要麼各種兼容,這都不是我們想看到的。參考網絡分層協議,我們也粗略地做了一個分層抽象規劃。

image.png

圖八

  • 物理層打通:主要解決網絡異構問題,即約定不同安全策略的子域如何通信。
  • 通訊協議層加速:前面講到的應用層協議,需要做到允許獨立擴展及切換。
  • 語言層編譯加速:業務網關可能更適合使用 Golang,然後 Java 節點是否可以用 Native 優化性能?
  • 框架層功能升級:比如當前對 Dubbo 的定製開發,使用的 Apisix 中心網關是否可以擴展 dubbo 轉 dubbo?
  • 任務編排:業務的跨網調度,不一定是 A->B->C->D,會不會是 A、B 同時完成後才能 ->C->D?
  • 更上層的控制面 / 治理面 / 運維面

未來規劃

隨着高速公路方案在政採雲的逐漸落地,我們未來會從穩定性、功能增強、新技術探索三個方面去做深、做廣:

1. 穩定性:基礎服務的穩定性是一切的基石,而這往往是不少研發同學容易忽視的一點,研發同學需“在晴天時修屋頂”。

  • 系統自身的健壯性:資源池化隔離、QoS 保障能力建設。
  • 節點實例的穩定性:加固發現能力,持續完善異常檢測工具(除了常規的健康檢測,會從觀測指標的不同緯度綜合決策),自動進行異常實例的替換;加強數據運營,提升反饋能力。

2. 功能增強

  • 協議增強:當前只能對 Dubbo 流量轉發,計劃增加對 Http/Grpc 等協議等支持,從而支持更多的場景(已有業務提此類需求)。
  • 安全性增強:在中心網關 Apisix 開發鑑權、審計等插件,更好的控制跨網的調用與被調。
  • 易用性增強:開發自動工單系統,對需要配置的事項,由業務測提工單,相應人員審覈後自動配置,解放勞動力同時減少出錯概率。

3. 新技術探索

網關場景,通常有個兩個比較明顯的特點:

  • 併發量高:多個應用複用同一個網關
  • 行爲輕量:一般只有轉發、權限校驗等輕量操作

基於這兩個特點,語言層性能開銷在總性能開銷中的佔比,往往會業務應用更大,這個時候, Golang 等語言會比 Java 更有優勢。當前也在對 Dubbo-Go 調研,未來替換基於 Java 版 Dubbo 的網關應用。

另外,Higress 方案看起來不錯,必定會有許多值得我們學習的東西。

歡迎感興趣的同學掃描下方二維碼加入釘釘交流羣,一起參與探討交流。

image.png

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章