天天看點

積跬步至千裡:QUIC 協定在螞蟻集團落地之綜述QUIC 背景介紹一套落地架構兩個落地場景三篇關鍵專利四項關鍵技術做個總結未來規劃延伸閱讀

作者:孔令濤

來源:金融級分布式架構公衆号

自 2015 年以來,QUIC 協定開始在 IETF 進行标準化并被國内外各大廠商相繼落地。鑒于 QUIC 具備“0RTT 建聯”、“支援連接配接遷移”等諸多優勢,并将成為下一代網際網路協定:HTTP3.0 的底層傳輸協定,螞蟻集團支付寶用戶端團隊與接入網關團隊于 2018 年下半年開始在移動支付、海外加速等場景落地 QUIC。

本文是綜述篇,介紹 QUIC 在螞蟻的整體落地情況。之是以是綜述,是因為 QUIC 協定過于複雜,如果對标已有的協定,QUIC 近似等于 HTTP + TLS +TCP,無法詳細的畢其功于一役,是以我們通過綜述的方式将落地的重點呈現給讀者,主要介紹如下幾個部分:

  • QUIC背景:簡單全面的介紹下 QUIC 相關的背景知識
  • 方案選型設計:詳細介紹螞蟻的落地方案如何另辟蹊徑、優雅的支撐 QUIC 的諸多特性,包括連接配接遷移等
  • 落地場景:介紹 QUIC 在螞蟻的兩個落地場景,包括:支付寶用戶端鍊路以及海外加速鍊路
  • 幾項關鍵技術:介紹落地 QUIC 過程中核心需要解決的問題,以及我們使用的方案,包括:“支援連接配接遷移”、“提升 0RTT 比例", "支援 UDP 無損更新”以及“用戶端智能選路” 等
  • 幾項關鍵的技術專利

本文也是 QUIC 協定介紹的第一篇,後續我們會把更多的落地細節、體驗優化手段、性能優化手段、安全與高可用、QUIC 新技術等呈現給大家。

【注】螞蟻 QUIC 開發團隊包括:支付寶用戶端團隊的梅男、蒼茫、述言,以及接入網關的伯琴、子荃、毅絲。

QUIC 背景介紹

鑒于讀者的背景可能不同,在開始本文之前,我們先簡單介紹下 QUIC 相關的背景知識,如果您對這個協定的更多設計細節感興趣,可以參見相關 Draft:

https://datatracker.ietf.org/wg/quic/documents/

一、QUIC 是什麼?

簡單來說,QUIC (Quick UDP Internet Connections) 是一種基于 UDP 封裝的安全 可靠傳輸協定,他的目标是取代 TCP 并自包含 TLS 成為标準的安全傳輸協定。下圖是 QUIC 在協定棧中的位置,基于 QUIC 承載的 HTTP 協定進一步被标準化為 HTTP3.0。

積跬步至千裡:QUIC 協定在螞蟻集團落地之綜述QUIC 背景介紹一套落地架構兩個落地場景三篇關鍵專利四項關鍵技術做個總結未來規劃延伸閱讀

二、為什麼是 QUIC ?

在 QUIC 出現之前,TCP 承載了 90% 多的網際網路流量,似乎也沒什麼問題,那又為何會出現革命者 QUIC 呢?這主要是因為發展了幾十年的 TCP 面臨 “協定僵化問題”,表現在幾方面:

  1. 網絡裝置支援 TCP 時的僵化,表現在:對于一些防火牆或者 NAT 等裝置,如果 TCP 引入了新的特性,比如增加了某些 TCP OPTION 等,可能會被認為是攻擊而丢包,導緻新特性在老的網絡裝置上無法工作。
  2. 網絡作業系統更新困難導緻的 TCP 僵化,一些 TCP 的特性無法快速的被演進。
  3. 除此之外,當應用層協定優化到 TLS1.3、 HTTP2.0 後, 傳輸層的優化也提上了議程,QUIC 在 TCP 基礎上,取其精華去其糟粕具有如下的硬核優勢:
積跬步至千裡:QUIC 協定在螞蟻集團落地之綜述QUIC 背景介紹一套落地架構兩個落地場景三篇關鍵專利四項關鍵技術做個總結未來規劃延伸閱讀

三、QUIC 生态圈發展簡史

下圖是 QUIC 從建立到現在為止的一些比較重要的時間節點,2021 年,QUIC V1 即将成為 RFC,結束百花齊放的态勢。

積跬步至千裡:QUIC 協定在螞蟻集團落地之綜述QUIC 背景介紹一套落地架構兩個落地場景三篇關鍵專利四項關鍵技術做個總結未來規劃延伸閱讀

介紹完 QUIC 相關背景,之後我們來介紹螞蟻的整個落地的内容,這裡為了便于闡述,我們用螞蟻 QUIC 的 一、二、三、四 來進行概括總結,即 “一套落地架構”、“兩個落地場景”、“三篇創新專利保護”、“四項關鍵技術”。

一套落地架構

螞蟻的接入網關是基于多程序的 NGINX 開發的 (内部稱為 Spanner,協定解除安裝的扳手),而 UDP 在多程序程式設計模型上存在諸多挑戰,典型的像無損更新等。為了設計一套完備的架構,我們在落地前充分考慮了服務端在雲上部署上的友善性、擴充性、以及性能問題,設計了如下的落地架構以支撐不同的落地場景:

積跬步至千裡:QUIC 協定在螞蟻集團落地之綜述QUIC 背景介紹一套落地架構兩個落地場景三篇關鍵專利四項關鍵技術做個總結未來規劃延伸閱讀

在這套架構中,包括如下兩個元件:

  1. QUIC LB 元件:基于 NGINX 4層 UDP Stream 子產品開發,用來基于 QUIC DCID 中攜帶的服務端資訊進行路由,以支援連接配接遷移。
  2. NGINX QUIC 伺服器:開發了 NGINX_QUIC_MODULE,每個 Worker 監聽兩種類型的端口:

(1)BASE PORT ,每個 Worker 使用的相同的端口号,以 Reuseport 的形式監聽,并暴露給 QUIC LB,用以接收用戶端過來的第一個 RTT 中的資料包,這類包的特點是 DCID 由用戶端生成,沒有路由資訊。

(2)Working PORT,每個 Worker 使用的不同的端口号,是真正的工作端口,用以接收第一個 RTT 之後的 QUIC 包,這類包的特定是 DCID 由服務端的程序生成攜帶有服務端的資訊。

目前架構支援的能力包括如下:

  1. 在不用修改核心的情況下,完全在使用者态支援 QUIC 的連接配接遷移,以及連接配接遷移時 CID 的 Update
  2. 在不用修改核心的情況下,完全在使用者态支援 QUIC 的無損更新以及其他運維問題
  3. 支援真正意義上的 0RTT ,并可提升 0RTT 的比例

為何能支援上述能力,我們後面會展開叙述

兩個落地場景

我們由近及遠的兩個落地場景如下:

場景一、支付寶移動端落地

如下為我們落地架構的示意圖,支付寶手機用戶端通過 QUIC 攜帶 HTTP 請求,通過 QUIC LB 等四層網關将請求轉發到 Spanner (螞蟻内部基于 NGINX 開發的 7 層網關),在 Spanner 上我們将 QUIC 請求 Proxy 成 TCP 請求,發送給業務網關(RS)。

積跬步至千裡:QUIC 協定在螞蟻集團落地之綜述QUIC 背景介紹一套落地架構兩個落地場景三篇關鍵專利四項關鍵技術做個總結未來規劃延伸閱讀

具體的方案選型如下:

  • 支援的 QUIC 版本是 gQUIC Q46。
  • NGINX QUIC MODULE 支援 QUIC 的接入和 PROXY 成 TCP 的能力。
  • 支援包括移動支付、基金、螞蟻森林在内的所有的 RPC 請求。
  • 目前選擇 QUIC 鍊路的方式有兩種 :
    • Backup 模式,即在 TCP 鍊路無法使用的情況下,降級到 QUIC 鍊路。
    • Smart 模式,即 TCP和 QUIC 競速,在 TCP 表現力弱于 QUIC 的情況下,下次請求主動使用 QUIC 鍊路。

在此場景下,通過使用 QUIC 可以獲得的紅利包括:

  1. 在用戶端連接配接發生遷移的時候,可以不斷鍊繼續服務
  2. 用戶端在首次發起連接配接時,可以節省 TCP 三次握手的時間
  3. 對于弱網情況,QUIC 的傳輸控制可以帶來傳輸性能提升

場景二、海外加速落地

螞蟻集團從 2018 年開始自研了海外的動态加速平台 AGNA(Ant Global Network Accelerator)以替換第三方廠商的加速服務。AGNA 通過在海外部署接入點:Local Proxy(LP) 以及在國内部署接出點:Remote Proxy (RP)的方式,将使用者的海外請求通過 LP 和 RP 的加速鍊路回源國内。如下圖所示,我們将 QUIC 部署在 LP 和 RP 之間的鍊路。

積跬步至千裡:QUIC 協定在螞蟻集團落地之綜述QUIC 背景介紹一套落地架構兩個落地場景三篇關鍵專利四項關鍵技術做個總結未來規劃延伸閱讀

在海外接入點上(LP),每一個 TCP 連接配接都被 Proxy 成 QUIC 上的一個 Stream 進行承載,在國内接出點上(RP), 每一個 QUIC Stream 又被 Proxy 成一個 TCP 連接配接,LP 和 RP 之間使用 QUIC 長連接配接。

  1. 通過 QUIC 長連接配接的上的 Stream 承載 TCP 請求,避免每次的跨海建聯。
  2. 對于跨海的網絡,QUIC 的傳輸控制可以帶來傳輸性能提升。

三篇關鍵專利

到目前為止,我們把落地過程中一些創新的技術點通過申請專利進行了保護,并積極在 IETF 進行标準化分享我們的研究成果,包括:

專利一

我們将落地場景 2 中,通過 QUIC Stream 進行四層代理的手段來進行海外回源的加速方法進行專利保護,提出:“一種基于 QUIC 協定代理的的鍊路加速方法”,目前此專利已經獲得美國專利授權,專利号:CN110213241A。

專利二

将我們落地架構中的 QUIC LB 元件作為專利進行保護,提出:“一種無狀态、一緻性、分布式的 QUIC 負載均衡裝置”,目前此專利還在受理中。由于通過 QUIC LB 可以很好的支援 QUIC 協定的連接配接遷移問題,是以目前 IETF QUIC WG 上有關于 QUIC LB 相關的草案,我們目前已經參與到 Draft 的讨論和制定中,後序相關的方案也會繼續推廣到雲上産品。

專利三

将我們解決的 UDP 的無損更新方法進行專利保護,提出 “一種 QUIC 伺服器無損更新方案”,目前此專利還在受理中。由于 UDP 無損更新問題是一個業界難題,目前有些手段需要在使用者态進行跳轉,性能損失較大,我們的方案可以在我們的落地架構中解決目前問題,關于這個方案的細節我們再後面的關鍵技術中進行介紹。

四項關鍵技術

在整個的落地中,我們設計的方案圍繞解決幾個核心問題展開,形成了四項關鍵技術,分别如下

技術點1.優雅的支援連接配接遷移能力

先說 連接配接遷移面臨的問題 ,上文有提到,QUIC 有一項比較重要的功能是支援連接配接遷移。這裡的連接配接遷移是指:如果用戶端在長連接配接保持的情況下切換網絡,比如從 4G 切換到 Wifi , 或者因為 NAT Rebinding 導緻五元組發生變化,QUIC 依然可以在新的五元組上繼續進行連接配接狀态。QUIC 之是以能支援連接配接遷移,一個原因是 QUIC 底層是基于無連接配接的 UDP,另一個重要原因是因為 QUIC 使用唯一的 CID 來辨別一個連接配接,而不是五元組。

如下圖所示,是 QUIC 支援連接配接的一個示意圖,當用戶端出口位址從 A 切換成 B 的時候,因為 CID 保持不變,是以在 QUIC 伺服器上,依然可以查詢到對應的 Session 狀态。

積跬步至千裡:QUIC 協定在螞蟻集團落地之綜述QUIC 背景介紹一套落地架構兩個落地場景三篇關鍵專利四項關鍵技術做個總結未來規劃延伸閱讀

然而,理論很豐滿,落地卻很艱難,在端到端的落地過程中,因為引入了負載均衡裝置,會導緻在連接配接遷移時,所有依賴五元組 Hash 做轉發或者關聯 Session 的機制失效。以 LVS 為例,連接配接遷移後, LVS 依靠五元組尋址會導緻尋址的伺服器存在不一緻。即便 LVS 尋址正确,當封包到達伺服器時,核心根據五元組關聯程序,依然會尋址出錯。同時,IETF Draft 要求,連接配接遷移時 CID 需要更新掉,這就為僅依靠 CID 來轉發的計劃同樣變的不可行。

再說 我們的解決方法,為了解決此問題,我們設計了開篇介紹的落地架構,這裡我們将方案做一些簡化和抽象,整體思路如下圖所示:

  1. 在四層負載均衡上,我們設計了 QUIC LoadBalancer 的機制:
    1. 我們在 QUIC 的 CID 中擴充了一些字段(ServerInfo)用來關聯 QUIC Server 的 IP 和 Working Port 資訊。
    2. 在發生連接配接遷移的時候,QUIC LoadBalancer 可以依賴 CID 中的 ServerInfo 進行路由,避免依賴五元組關聯 Session 導緻的問題。
    3. 在 CID 需要 Update 的時候,NewCID 中的 ServerInfo 保留不變,這樣就避免在 CID 發生 Update 時,僅依賴 CID Hash 挑選後端導緻的尋址不一緻問題。
  2. 在 QUIC 伺服器多程序工作模式上,我們突破了 NGINX 固有的多 Worker 監聽在相同端口上的桎梏,設計了多端口監聽的機制,每個 Worker 在工作端口上進行隔離,并将端口的資訊攜帶在對 First Initial Packet 的回包的 CID 中,這樣代理的好處是:
    1. 無論是否連接配接遷移,QUIC LB 都可以根據 ServerInfo,将封包轉發到正确的程序。
    2. 而業界普遍的方案是修改核心,将 Reuse port 機制改為 Reuse CID 機制,即核心根據 CID 挑選程序。即便後面可以通過 ebpf 等手段支援,但我們認為這種修改核心的機制對底層過于依賴,不利于方案的大規模部署和運維,尤其在公有雲上。
    3. 使用獨立端口,也有利于多程序模式下,UDP 無損更新問題的解決,這個我們在技術點 3 中介紹。
積跬步至千裡:QUIC 協定在螞蟻集團落地之綜述QUIC 背景介紹一套落地架構兩個落地場景三篇關鍵專利四項關鍵技術做個總結未來規劃延伸閱讀

技術點2.提升 0RTT 握手比例

這裡先 介紹 QUIC 0RTT 原理。前文我們介紹過, QUIC 支援傳輸層握手和安全加密層握手都在一個 0RTT 内完成。TLS1.3 本身就支援加密層握手的 0RTT,是以不足為奇。而 QUIC 如何實作傳輸層握手支援 0RTT 呢?我們先看下傳輸層握手的目的,即:服務端校驗用戶端是真正想握手的用戶端,位址不存在欺騙,進而避免僞造源位址攻擊。在 TCP 中,服務端依賴三次握手的最後一個 ACK 來校驗用戶端是真正的用戶端,即隻有真正的用戶端才會收到 Sever 的 syn_ack 并回複。

積跬步至千裡:QUIC 協定在螞蟻集團落地之綜述QUIC 背景介紹一套落地架構兩個落地場景三篇關鍵專利四項關鍵技術做個總結未來規劃延伸閱讀

QUIC 同樣需要對握手的源位址做校驗,否則便會存在 UDP 本身的 DDOS 問題,那 QUIC 是如何實作的?依賴 STK(Source Address Token) 機制。這裡我們先聲明下,跟 TLS 類似,QUIC 的 0RTT 握手,是建立在已經同一個伺服器建立過連接配接的基礎上,是以如果是純的第一次連接配接,仍然需要一個 RTT 來擷取這個 STK。如下圖所示,我們介紹下這個原理:

  1. 類似于 Session Ticket 原理,Server 會将用戶端的位址和目前的 Timestamp 通過自己的 KEY 加密生成 STK。
  2. Client 下次握手的時候,将 STK 攜帶過來,由于 STK 無法篡改,是以 Server 通過自己的 KEY 解密,如果解出來的位址和用戶端此次握手的位址一緻,且時間在有效期内,則表示用戶端可信,便可以建立連接配接。
  3. 由于用戶端第一次握手的時候,沒有這個 STK,是以服務度會回複 REJ 這次握手的資訊,并攜帶 STK。
積跬步至千裡:QUIC 協定在螞蟻集團落地之綜述QUIC 背景介紹一套落地架構兩個落地場景三篇關鍵專利四項關鍵技術做個總結未來規劃延伸閱讀

理論上說,隻要用戶端緩存了這個 STK,下次握手的時候帶過來,服務端便可以直接校驗通過,即實作傳輸層的 0RTT。但是真實的場景卻存在如下兩個問題:

  1. 因為 STK 是服務端加密的,是以如果下次這個用戶端路由到别的伺服器上了,則這個伺服器也需要可以識别出來。
  2. STK 中 encode 的是上一次用戶端的位址,如果下一次用戶端攜帶的位址發生了變化,則同樣會導緻校驗失敗。此現象在移動端發生的機率非常大,尤其是 IPV6 場景下,用戶端的出口位址會經常發生變化。

再介紹下我們的解決方法。第一個問題比較好解,我們隻要保證叢集内的機器生成 STK 的秘鑰一緻即可。第二個問題,我們的解題思路是:

  1. 我們在 STK 中擴充了一個 Client ID, 這個 Clinet ID 是用戶端通過無線保镖黑盒生成并全局唯一不變的,類似于一個裝置的 SIMID,用戶端通過加密的 Trasnport Parameter 傳遞給服務端,服務端在 STK 中包含這個 ID。
  2. 如果因為 Client IP 發生變化導緻校驗 STK 校驗失敗,便會去校驗 Client ID,因為 ID 對于一個 Client 是永遠不變的,是以可以校驗成功,當然前提是,這個用戶端是真實的。為了防止 Client ID 的洩露等,我們會選擇性對 Client ID 校驗能力做限流保護。
積跬步至千裡:QUIC 協定在螞蟻集團落地之綜述QUIC 背景介紹一套落地架構兩個落地場景三篇關鍵專利四項關鍵技術做個總結未來規劃延伸閱讀

技術點3. 支援 QUIC 無損更新

我們知道 UDP 無損更新是業界難題。無損更新是指在 reload 或者更新二進制時,老的程序可以處理完存量連接配接上的資料後優雅退出。以 NGINX 為例,這裡先介紹下 TCP 是如何處理無損更新的,主要是如下的兩個步驟:

  1. 老程序先關閉 listening socket,待存量連接配接請求都結束後,再關閉連接配接套接字
  2. 新程序從老程序繼承 listening socket , 開始 accept 新的請求

而 UDP 無法做到無損更新是因為 UDP 隻有一個 listening socket 沒有類似 TCP 的連接配接套接字,所有的收發資料包都在這個 socket 上,導緻下面的熱更新步驟會存在問題:

  1. 在熱更新的時候,old process fork 出 new process 後,new process 會繼承 listening socket 并開始 recv msg。
  2. 而 old process 此時如果關閉 listenging socket, 則在途的資料包便無法接收,達不到優雅退出的目的。
  3. 而如果繼續監聽,則新老程序都會同時收取新連接配接上的封包,導緻老程序無法退出。
積跬步至千裡:QUIC 協定在螞蟻集團落地之綜述QUIC 背景介紹一套落地架構兩個落地場景三篇關鍵專利四項關鍵技術做個總結未來規劃延伸閱讀

這裡介紹下相關的解決方法。針對此問題,業界有一些方法,比如:在資料包中攜帶程序号,當資料包收發錯亂後,在新老程序之間做一次轉發。考慮到接入層上的性能等原因,我們不希望資料再做一次跳轉。結合我們的落地架構,我們設計了如下的 基于多端口輪轉的無損更新方案,簡單來說,我們讓新老程序監聽在不同的端口組并攜帶在 CID 中,這樣 QUIC LB 就可以根據端口轉發到新老程序。為了便于運維,我們采用端口輪轉的方式,新老程序會在 reload N 次之後,重新開始之前選中的端口。如下圖所示:

  1. 無損更新期間,老程序的 Baseport 端口關閉,這樣不會再接受 first intial packet, 類似于關閉了 tcp 的 listening socket。
  2. 老程序的工作端口,繼續工作,用來接收目前程序上殘餘的流量。
  3. 新程序的 Baseport 開始工作,用來接收 first initial packet, 開啟新的連接配接,類似于開啟了 tcp 的 listening socket。
  4. 新程序的 working port = (I + 1) mod N, N 是指同時支援新老程序的狀态的次數,例如 N = 4, 表示可以同時 reload 四次,四種 Old, New1, New2, New3 四種狀态同時并存,I 是上一個程序工作的端口号,這裡 + 1 是因為隻有一個 worker, 如果 worker 數有 M 個,則加 M。
  5. 建好的連接配接便被 Load Balancer 轉移到新程序的監聽端口的 Working Port 上。
積跬步至千裡:QUIC 協定在螞蟻集團落地之綜述QUIC 背景介紹一套落地架構兩個落地場景三篇關鍵專利四項關鍵技術做個總結未來規劃延伸閱讀

技術點4.用戶端智能選路

盡管落地 QUIC 的願望是好的,但是新事物的發展并不是一帆風順的。由于 QUIC 是基于 UDP 的,而 UDP 相比于 TCP 在營運商的支援上并非友好,表現在:

  1. 在帶寬緊張的時候,UDP 會經常被限流。
  2. 一些防火牆對于 UDP 包會直接 Drop。
  3. NAT 網關針對 UDP 的 Session 存活時間也較短。

同時,根據觀察發現,不同的手機廠商對于 UDP 的支援能力也不同,是以在落地過程中,如果盲目的将所有流量完全切為 QUIC 可能會導緻一些難以預料的結果。為此,我們在用戶端上,設計了開篇介紹的 TCP 和 QUIC 互相 Backup 的鍊路,如下圖所示,我們實時探測 TCP 鍊路和 QUIC 鍊路的 RTT、丢包率、請求完成時間、錯誤率等名額情況,并根據一定的量化方法對兩種鍊路進行打分,根據評分高低,決定選擇走哪種鍊路,進而避免尋址隻走一條鍊路導緻的問題。

積跬步至千裡:QUIC 協定在螞蟻集團落地之綜述QUIC 背景介紹一套落地架構兩個落地場景三篇關鍵專利四項關鍵技術做個總結未來規劃延伸閱讀

做個總結

本文主要綜述性的介紹了 QUIC 在螞蟻的落地方案、場景以及一些關鍵技術。關鍵技術上,主要介紹了我們如何通過創造性的提出 QUIC LB 元件、以及多端口監聽的機制來優雅的支援 QUIC 的連接配接遷移機制、QUIC 服務端的無損更新等,依賴這套方案我們的接入網關不需要像業界一樣依賴底層核心的改動,這極大的友善了我們方案的部署,尤其在公有雲場景下。除了連接配接遷移以外,我們還提出了 0RTT 建聯提升方案、用戶端智能選路方案,以最大化 QUIC 在移動端上的收益。截止目前,QUIC 已經在支付寶移動端以及全球加速鍊路兩個場景上平穩運作,并帶來了較好的業務收益。

未來規劃

兩年來,我們主要以社群的 gQuic 為基礎,充分發揮 QUIC 的協定優勢,并結合螞蟻的業務特征以最大化移動端收益為目标,創造性的提出了一些解決方案,并積極向社群和 IETF 進行推廣。在未來,随着螞蟻在更多業務上的開展和探索以及HTTP3.0/QUIC 即将成為标準,我們會主要圍繞以下幾個方向繼續深挖 QUIC 的價值:

  1. 我們将利用 QUIC 在應用層實作的優勢,設計一套統一的具備自适應業務類型和網絡類型的 QUIC 傳輸控制架構,對不同類型的業務和網絡類型,做傳輸上的調優,以優化業務的網絡傳輸體驗。
  2. 将 gQUIC 切換成 IETF QUIC,推進标準的 HTTP3.0 在螞蟻的進一步落地。
  3. 将螞蟻的 QUIC LB 技術點向 IETF QUIC LB 進行推進,并最終演變為标準的 QUIC LB。
  4. 探索并落地 MPQUIC(多路徑 QUIC) 技術,最大化在移動端的收益。
  5. 繼續 QUIC 的性能優化工作,使用 UDP GSO, eBPF,io_uring 等核心技術。
  6. 探索 QUIC 在内網承載東西向流量的機會。

延伸閱讀

積跬步至千裡:QUIC 協定在螞蟻集團落地之綜述QUIC 背景介紹一套落地架構兩個落地場景三篇關鍵專利四項關鍵技術做個總結未來規劃延伸閱讀

繼續閱讀