Rust和Wasm如何驅動Cloudflare的1.1.1.1

來源:Cloudflare
作者:Cloudflare
時間:2023-05-08
1044
2018年4月1日,Cloudflare發(fā)布了1.1.1.1公共DNS解析器。過去幾年來,我們在平臺上不斷添磚加瓦,包括用于故障排除的調(diào)試頁面,緩存清除,Cloudflare區(qū)域的0 TTL,上游TLS,以及1.1.1.1 for Families。本文想分享一些幕后的細節(jié)和變化。

640.png

2018年4月1日,Cloudflare發(fā)布了1.1.1.1公共DNS解析器。過去幾年來,我們在平臺上不斷添磚加瓦,包括用于故障排除的調(diào)試頁面,緩存清除,Cloudflare區(qū)域的0 TTL,上游TLS,以及1.1.1.1 for Families。本文想分享一些幕后的細節(jié)和變化。

項目啟動時,Knot Resolver被選定為DNS解析器。我們開始在其基礎上構建一個完整的系統(tǒng),以便它適合Cloudflare的用例。擁有一個經(jīng)過實戰(zhàn)考驗的DNS遞歸解析器,以及一個DNSSEC驗證器非常棒,這樣我們可以把精力花在其他地方,而不用擔心DNS協(xié)議的實現(xiàn)。

就其基于Lua的插件系統(tǒng)而言,Knot Resolver非常靈活。它允許我們快速擴展核心功能,以支持各種產(chǎn)品特性,如DoH/DoT、日志記錄、基于bpf的攻擊緩解、緩存共享和迭代邏輯覆蓋。隨著流量增長,我們達到了一定的限制。

我們吸取的教訓

在深入討論之前,讓我們先鳥瞰一下簡化的Cloudflare數(shù)據(jù)中心設置,這可以幫助我們理解后面將要討論的內(nèi)容。Cloudflare的每臺服務器都是相同的:運行在一臺服務器上的軟件棧與另一臺服務器上的軟件棧完全相同,僅配置可能不同。這種設置大大降低了維護的復雜性。

640 (1).png

圖1:數(shù)據(jù)中心布局

解析器以后臺進程kresd的形式運行,而且它并不是單獨工作的。請求,特別是DNS請求,由Unimog進行負載平衡后分配給數(shù)據(jù)中心內(nèi)的服務器。DoH請求在我們的TLS終止器終止。配置和其他小塊數(shù)據(jù)可以通過Quicksilver在幾秒鐘內(nèi)傳遞到世界各地。憑借以上所有幫助,解析器可以專注于自己的目標——解析DNS查詢,而不用擔心傳輸協(xié)議的細節(jié)?,F(xiàn)在讓我們來談談我們想要改進的3個關鍵領域:插件阻塞I/O,更有效地使用緩存空間,以及插件隔離。

阻塞事件循環(huán)的回調(diào)函數(shù)

Knot Resolver有一個非常靈活的插件系統(tǒng)來擴展它的核心功能。這些插件被稱為模塊,它們是基于回調(diào)的。在請求處理期間的某些點,這些回調(diào)將通過當前查詢上下文調(diào)用。這為某個模塊提供檢查、修改和甚至生成請求/響應的能力。從設計來看,這些回調(diào)應該是簡單的,以避免阻塞底層的事件循環(huán)。這一點很重要,因為服務是單線程的,而事件循環(huán)負責同時處理多個請求。因此,即使只有一個請求在回調(diào)中被擱置,也意味著在回調(diào)完成之前,其他并發(fā)請求也無法被處理。

這種設置一直工作得足夠好,直到我們需要進行阻塞操作,例如,在響應客戶端前從Quicksilver拉取數(shù)據(jù)。

緩存效率

由于對一個域的請求可能落在數(shù)據(jù)中心內(nèi)的任何節(jié)點上,在另一個節(jié)點已經(jīng)有答案的情況下重復解析查詢將是一種浪費。根據(jù)直覺,如果緩存可以在服務器之間共享,則延遲可以得到改善,因此我們創(chuàng)建了一個緩存模塊,該模塊對新添加的緩存條目進行組播。然后,同一數(shù)據(jù)中心內(nèi)的節(jié)點可以訂閱事件并更新其本地緩存。

Knot Resolver的默認緩存實現(xiàn)是LMDB。對于中小型部署來說,它快速又可靠。但是在我們的例子中,緩存清除很快成為一個問題。緩存本身不跟蹤任何TTL、流行度等。緩存填滿時,它會清除所有條目并重新開始。像分區(qū)枚舉這樣的場景可能會用以后不太可能被檢索的數(shù)據(jù)填充緩存。

此外,我們的組播緩存模塊導致情況進一步惡化,將不太有用的數(shù)據(jù)放大到所有節(jié)點,并使它們同時達到緩存高水位。然后我們看到了一個延遲峰值,因為所有的節(jié)點都放棄了緩存,并在同一時間重新開始。

模塊隔離

隨著Lua模塊列表增加,調(diào)試問題變得越來越困難。這是因為單個Lua狀態(tài)在所有模塊之間共享,因此一個行為不當?shù)哪K可能會影響另一個模塊。例如,當Lua狀態(tài)內(nèi)部出現(xiàn)問題時,例如有太多的協(xié)程,或者內(nèi)存不足,程序崩潰已經(jīng)算是幸運了,但是所產(chǎn)生的堆棧跟蹤很難讀取。強制拆除或升級運行中的模塊也很困難,因為它在Lua運行時中不僅有狀態(tài),還有FFI,因此內(nèi)存安全無法保證。

BigPineapple應運而生

我們找不到任何現(xiàn)有軟件能滿足我們這個有點小眾的要求,因此最終我們自己動手打造了一個。第一個嘗試是用Rust編寫的一個瘦服務包裹Knot Resolver的核心(修改版edgedns)。

由于必須不斷地在存儲和C/FFI類型之間進行轉換,以及一些其他的問題(例如,從緩存中查找記錄的ABI期望返回的記錄在下一次調(diào)用或讀事務結束之前是不可變的),這樣做證明是很困難的。但是,從嘗試實現(xiàn)這種分離功能——其中主機(服務)向客機(解析器核心庫)提供一些資源,以及如何使接口變得更好,但我們從中學到了很多。

在之后的迭代中,我們用一個基于異步運行時的新庫替換了整個遞歸庫;并添加了一個重新設計的模塊系統(tǒng),隨著時間的推移,隨著我們換出越來越多的組件,逐漸將服務重寫為Rust。異步運行時就是tokio,其提供了簡潔的線程池接口,用于運行非阻塞和阻塞任務,以及一個與其他部件(Rust庫)一起工作的良好生態(tài)系統(tǒng)。

在那以后,隨著futures combinators變得繁瑣,我們開始將一切轉換為async/await。這是async/await特性通過Rust 1.39提供以前,導致我們使用了一段時間的nightly(Rust測試版),遇到了一些問題。當async/await穩(wěn)定后,它讓我們能夠高效地編寫請求處理例程,類似于Go。

所有的任務都可以并發(fā)運行,某些I/O繁重的任務可以被分解成更小的部分,從而受益于更細粒度的調(diào)度。由于運行時在線程池(而不是單個線程)上執(zhí)行任務,它還可以受益于工作竊?。╳ork stealing)。這避免了我們之前遇到的一個問題,即單個請求需要花費大量時間來處理,阻塞事件循環(huán)中的所有其他請求。

640 (2).png

圖2:組件概覽

最后,我們打造了一個自己滿意的平臺,稱之為BigPineapple。上圖顯示了平臺的主要組件及組件之間數(shù)據(jù)流的概覽。在BigPineapple內(nèi)部,服務器模塊從客戶端獲取入站請求,驗證并將其轉換為統(tǒng)一的幀流,然后由worker模塊處理。worker模塊有一組worker,它們的任務是為請求中的問題找到答案。每個worker與緩存模塊交互,以檢查答案是否存在并且仍然有效,否則將驅動遞歸模塊進行遞歸迭代查詢。遞歸器不做任何I/O,當它需要任何東西時,它將子任務委托給conductor模塊。然后,conductor使用出站查詢從上游名稱服務器獲取信息。在整個過程中,一些模塊可以與沙盒模塊交互,通過運行里面的插件來擴展它的功能。

讓我們更詳細地看看其中的一些模塊,看看它們是如何幫助我們克服以前遇到的問題的。

更新的I/O架構

DNS解析器可以看作是客戶端和幾個權威名稱服務器之間的代理:它從客戶端接收請求,遞歸地從上游名稱服務器獲取數(shù)據(jù),然后組合響應并將它們發(fā)送回客戶端。因此,它同時有入站和出站流量,分別由服務器和conductor組件處理。

服務器使用不同傳輸協(xié)議偵聽一個接口列表。這些隨后被抽象為“幀”流。每一幀都是一個DNS消息的高級表示,帶有一些額外的元數(shù)據(jù)。在底層,它可以是UDP包、TCP流的一段或HTTP請求的有效載荷,但它們都以相同的方式處理。然后,幀被轉換成一個異步任務,接著由一組負責解析這些任務的worker接收。完成的任務被轉換回響應,并發(fā)送回客戶端。

這種對協(xié)議及其編碼的“幀”抽象簡化了用于規(guī)范幀源的邏輯,例如加強公平性以防止“餓死”,并控制節(jié)奏以保護服務器不被壓垮。我們從之前的實踐中了解到的一件事是,對于一個向公眾開放的服務,I/O峰值性能的重要性低于公平調(diào)度客戶端的能力。這主要是因為每個遞歸請求的時間和計算成本有很大的不同(例如緩存命中/不命中),并且很難事先猜測。遞歸服務中的緩存不命中不僅消耗Cloudflare的資源,還消耗被查詢的權威名稱服務器的資源,因此我們需要注意這一點。

服務器的另一方面是conductor,其管理所有出站連接。它幫助在向上游連接之前回答一些問題:就延遲而言,連接到哪個名稱服務器最快?如果所有的名稱服務器都不可達該怎么辦?連接使用什么協(xié)議,以及是否有更好的選項?conductor能夠通過跟蹤上游服務器的指標(例如RTT、QoS等)來做出這些決策。了解這些情況,它也可以猜測像上游容量、UDP包丟失等信息,并進行必要的操作,例如,在認為此前的UDP包沒有到達上游時進行重試。

640 (3).png

圖3:I/O conductor

圖3顯示了關于conductor的簡化數(shù)據(jù)流。它被上面提到的交換器調(diào)用,以上游請求作為輸入。這些請求將首先進行去重:這意味著在一個小窗口中,如果有很多請求來到這個conductor并詢問相同的問題,只有一個會通過,其他請求被放入一個等待隊列中。這在緩存條目過期時很常見,可以減少不必要的網(wǎng)絡流量。然后,根據(jù)請求和上游指標,連接instructor要么選擇一個可用的已打開連接,要么生成一組參數(shù)。使用這些參數(shù),I/O執(zhí)行器就可以直接連接到上游,甚至可以使用我們的Argo Smart Routing技術,采取一條經(jīng)另一個Cloudflare數(shù)據(jù)中心的路線。

緩存

在遞歸服務中進行緩存是非常關鍵的,因為服務器可以在1毫秒內(nèi)返回緩存的響應,而緩存不命中時則需要數(shù)百毫秒來進行響應。由于內(nèi)存是有限的資源(在Cloudflare的架構中也是共享資源),更有效地使用緩存空間是我們想要改進的關鍵領域之一。新的緩存使用一種緩存替代數(shù)據(jù)結構(ARC),而非KV存儲。這樣可以很好地利用單個節(jié)點上的空間,因為熱門程度較低的條目會逐步被剔除,而且該數(shù)據(jù)結構可抗掃描。

此外,與我們之前使用組播在整個數(shù)據(jù)中心復制緩存不同,BigPineapple知道它在同一個數(shù)據(jù)中心中的對等節(jié)點,如果它在自己的緩存中找不到條目,就將查詢從一個節(jié)點轉發(fā)給另一個節(jié)點。這是通過將查詢一致地散列到每個數(shù)據(jù)中心的健康節(jié)點來實現(xiàn)的。例如,針對相同注冊域的查詢將通過相同的節(jié)點子集,這不僅提高了緩存命中率,而且有助于基礎架構緩存,后者存儲有關名稱服務器性能和特性的信息。

640 (4).png

圖4:更新的數(shù)據(jù)中心布局

異步遞歸庫

遞歸庫是BigPineapple的DNS大腦,它知道如何為查詢中的問題找到答案。它從根開始,將客戶端查詢分解為子查詢,并使用它們從互聯(lián)網(wǎng)上的各種權威名稱服務器中遞歸地收集知識。這個過程的結果就是答案。得益于async/await,它可以被抽象為這樣的函數(shù):

async fn resolve(Request,Exchanger)→Result;

該函數(shù)包含對給定請求生成響應需要的所有邏輯,但它自己不做任何I/O。相反,我們傳遞一個Exchanger trait(Rust接口),它知道如何與上游權威名稱服務器異步交換DNS消息。exchanger通常在不同的await被調(diào)用——例如,當遞歸開始時,它最初做的事情之一就是為該域查找最近的緩存委托。如果它在緩存中沒有最終的委托,則需要詢問哪些名稱服務器負責這個域,并等待響應,然后才能繼續(xù)進行下去。

得益于這種設計,將“等待某些響應”部分從遞歸DNS邏輯中分離出來,通過提供exchanger的模擬實現(xiàn),測試起來就容易得多。此外,它使遞歸迭代代碼(以及,特別是DNSSEC驗證邏輯)更具可讀性,因為它是按順序編寫的,而不是分散在許多回調(diào)中。

有趣的事實:從頭開始寫一個DNS遞歸解析器一點都不好玩!

不僅因為DNSSEC驗證的復雜性,還因為各種不兼容RFC的服務器、轉發(fā)器、防火墻等提供必須的“變通方法”。因此,我們將deckard移植到Rust中來幫助測試它。此外,當我們開始遷移到這個新的異步遞歸庫時,我們首先在“影子”模式下運行它:處理來自生產(chǎn)服務的真實世界查詢樣本,并比較差異。我們過去在Cloudflare的權威DNS服務上也這樣做過。遞歸服務要稍微困難一些,因為遞歸服務必須在互聯(lián)網(wǎng)上查找所有數(shù)據(jù),而且,由于本地化、負載平衡等原因,權威名稱服務器通常會對相同的查詢給出不同的答案,導致許多誤報。

2019年12月,我們終于在一個公共測試端點上啟用了新服務(查看公告)以解決剩余的問題,然后慢慢將生產(chǎn)端點遷移到新服務。即使做了所有這些工作之后,我們繼續(xù)檢測到了DNS遞歸(特別是DNSSEC驗證)的邊緣情況,但由于庫的新架構,修復和再現(xiàn)這些問題變得更容易了。

沙盒中的插件

動態(tài)擴展核心DNS功能的能力對我們來說很重要,因此BigPineapple重新設計了它的插件系統(tǒng)。以前,Lua插件運行在與服務本身相同的內(nèi)存空間中,通??梢宰杂傻刈鏊鼈兿胱龅氖虑?。這很方便,因為我們可以使用C/FFI在服務和模塊之間自由傳遞內(nèi)存引用。例如,直接從緩存讀取響應,而不必先復制到緩沖區(qū)。但這也很危險,因為模塊可以讀取未初始化的內(nèi)存、使用錯誤的函數(shù)簽名調(diào)用主機ABI、阻塞本地套接字或做其他不希望做的事情,此外,服務沒有辦法限制這些行為。

所以我們考慮用JavaScript或原生模塊替換嵌入式的Lua運行時,WebAssembly(簡稱Wasm)的嵌入式運行時開始出現(xiàn)了。WebAssembly程序的兩個優(yōu)點是,它允許我們用與服務其他部分相同的語言編寫程序,以及它們在一個隔離的內(nèi)存空間中運行。因此,我們開始圍繞WebAssembly模塊的限制對客機/主機接口進行建模,以了解其工作原理。

BigPineapple的Wasm運行時目前由Wasmer驅動。開始時,我們嘗試了幾種不同的運行時,例如Wasmtime和WAVM,發(fā)現(xiàn)對我們的用例而言,使用Wasmer更簡單。該運行時允許每個模塊在其自己的實例中運行,具有隔離的內(nèi)存和信號陷阱,這自然解決了我們前面描述的模塊隔離問題。除此之外,我們還可以同時運行同一個模塊的多個實例。通過仔細控制,應用可以從一個實例熱交換到另一個,而不會錯過任何一個請求!這很好,因為應用程序可以在不重啟服務器的情況下進行動態(tài)升級。由于Wasm程序是通過Quicksilver分發(fā)的,BigPineapple的功能幾秒鐘內(nèi)就能在全球范圍內(nèi)安全地改變!

要更好地理解WebAssembly沙盒,首先需要介紹幾個術語:

主機:運行Wasm運行時的程序。與內(nèi)核類似,它可以通過該運行時完全控制客機應用程序。

客機應用程序:沙盒內(nèi)的Wasm程序。在一個受限環(huán)境中,它只能訪問由運行時提供的自有內(nèi)存空間,并調(diào)用導入的主機調(diào)用。我們將其簡稱為“應用”。

主機調(diào)用:在主機中定義、可被客機調(diào)用的函數(shù)。與系統(tǒng)調(diào)用類似,這是客機應用訪問沙盒之外資源的唯一方式。

客機運行時:一個讓客機應用程序輕松與主機交互的庫。它實現(xiàn)了一些常見的接口,以便應用可以只使用異步、套接字、日志和跟蹤,而不知道底層的細節(jié)。

現(xiàn)在我們可以深入介紹一下沙盒了。首先,讓我們從客機側開始,看看一個普通應用的生命周期是什么樣子的。在客機運行時的幫助下,可以編寫與常規(guī)程序類似的客機應用。因此,像其他可執(zhí)行文件一樣,應用以start函數(shù)作為入口點開始,在加載時由主機調(diào)用。它還會被提供一些參數(shù),如同來自命令行。此時,該實例通常會進行一些初始化,更重要的是,為不同的查詢階段注冊回調(diào)函數(shù)。這是因為,在遞歸解析器中,查詢必須經(jīng)過幾個階段,才能收集足夠的信息來產(chǎn)生響應,例如,緩存查找,或生成子請求來解析域的委托鏈,因此能夠連接到這些階段是應用在不同用例中發(fā)揮作用的必要條件。start函數(shù)還可以運行一些后臺任務來補充階段回調(diào),并存儲全局狀態(tài)。例如——報告指標,或者從外部數(shù)據(jù)源預取共享數(shù)據(jù),等等。同樣,一如我們寫一個普通程序。

但是程序參數(shù)來自何處呢?客機應用如何發(fā)送日志和指標呢?答案是外部函數(shù)。

640 (5).png

圖5:基于Wasm的沙盒

在圖5中,我們可以看到中間有一個屏障,即沙盒邊界,它將客機與主機分開。一側要到達另一側,唯一途徑是通過由對方預先導出的一組函數(shù)。如圖所示,“hostcalls”(主機調(diào)用)由主機導出,由客機導入和調(diào)用;而“trampoline”是主機知道的客機函數(shù)。

后者稱為trampoline,是因為它用于調(diào)用客機實例中未導出的函數(shù)或閉包。階段回調(diào)是我們?yōu)槭裁葱枰猼rampoline函數(shù)的一個例子:每個回調(diào)返回一個閉包,因此不能在實例化時導出??蜋C應用要注冊一個回調(diào),調(diào)用一個帶有回調(diào)地址“hostcall_register_callback(pre_cache,#30987)”的主機調(diào)用,當需要調(diào)用回調(diào)時,主機不能直接調(diào)用該指針,因為它指向客機的內(nèi)存空間。取而代之,它利用前面提到的trampoline之一,并提供回調(diào)閉包的地址:“trampoline_call(#30987)”。

隔離開銷

就像硬幣有兩面一樣,新的沙盒確實也會帶來一些額外的開銷。WebAssembly提供的可移植性和隔離性帶來了額外代價。這里,我們將列出兩個例子。

首先,客機應用不允許讀取主機內(nèi)存。其工作的方式是,客機通過一個主機調(diào)用提供一個內(nèi)存區(qū)域,然后主機將數(shù)據(jù)寫入客機內(nèi)存空間。這會引入一個內(nèi)存副本,如果我們在沙盒之外,則不需要該內(nèi)存副本。壞消息是,在我們的用例中,客戶應用程序應該對查詢和/或響應進行一些操作,因此它們幾乎總是需要在每次請求時從主機讀取數(shù)據(jù)。另一方面,好消息是在請求的生命周期中,數(shù)據(jù)不會改變。因此,我們在客機應用實例化后立即在客機內(nèi)存空間中預分配大量內(nèi)存。分配的內(nèi)存并不會被使用,而是用于在地址空間中占一個坑。一旦主機獲得了地址的詳細信息,它就會將一個包含客機所需公共數(shù)據(jù)的共享內(nèi)存區(qū)域映射到客戶空間中。當客機代碼開始執(zhí)行時,它只需要訪問共享內(nèi)存覆蓋層中的數(shù)據(jù),而不需要復制。

我們遇到的另一個問題是,我們想在BigPineapple中添加對現(xiàn)代協(xié)議oDoH的支持。它的主要工作是解密客戶端查詢,解析它,然后在發(fā)送回之前加密答案。從設計上講,它不屬于核心DNS,應該使用Wasm應用進行擴展。然而,WebAssembly指令集沒有提供一些密碼學原語,例如AES和SHA-2,這使得它無法獲得主機硬件的好處。有一些進行中的工作通過WASI-crypto將這一功能引入Wasm。在那以前,我們對此的解決方案是簡單地通過主機調(diào)用將HPKE委托給主機,與在Wasm中執(zhí)行相比,我們已經(jīng)看到了4倍的性能提升。

Wasm中的異步

還記得我們之前討論過的回調(diào)函數(shù)可能阻塞事件循環(huán)的問題嗎?本質(zhì)上,問題在于如何異步運行沙盒中的代碼。因為無論請求處理回調(diào)函數(shù)有多復雜,只要它能返回結果,我們就可以設定允許阻塞的時間上限。幸運的是,Rust的異步框架既優(yōu)雅又輕量。它讓我們有機會使用一組客機調(diào)用來實現(xiàn)“Future”。

在Rust中,F(xiàn)uture是異步計算的基礎組件。從用戶的角度來看,為了創(chuàng)建一個異步程序,必須考慮兩件事:實現(xiàn)一個驅動狀態(tài)轉換的可輪詢函數(shù),并放置一個waker作為回調(diào)函數(shù),當可輪詢函數(shù)因某些外部事件(例如時間經(jīng)過,套接字變得可讀,等等)而應被再次調(diào)用時,用來喚醒自己。前者是為了能夠逐步推進程序,例如從I/O讀取緩沖的數(shù)據(jù),并返回一個表示任務狀態(tài)的新狀態(tài):finished或yielded。后者在任務放棄的情況下很有用,因為當任務等待的條件滿足時,它會觸發(fā)Future被輪詢,而不是一直忙著循環(huán)直到任務完成。

讓我們看看這是如何在我們的沙盒中實現(xiàn)的。對于客機需要執(zhí)行一些I/O操作的場景,它必須通過主機調(diào)用來完成,因為它處于一個受限制的環(huán)境中。假設主機提供了一組簡化的主機調(diào)用,它們反映了基本的套接字操作:打開、讀取、寫入和關閉,那么客戶可按如下定義其偽輪詢器:

fn poll(&mut self, wake: fn()) -> Poll {match hostcall_socket_read(self.sock, self.buffer) {        HostOk  => Poll::Ready,        HostEof => Poll::Pending,}}

在這里,主機調(diào)用從套接字讀取數(shù)據(jù)到緩沖區(qū),根據(jù)其返回值,函數(shù)可以將自己移動到我們前面提到的狀態(tài)之一:finished(就緒)或yielded(等待)。神奇的事情發(fā)生在主機調(diào)用中。還記得在圖5中,這是訪問資源的唯一方法嗎?客機應用并不擁有套接字,但它可以通過“hostcall_socket_open”獲得一個“句柄”,這將在主機側創(chuàng)建一個套接字,并返回一個句柄。理論上,句柄可以是任何東西,但實際上,使用整數(shù)套接字句柄可以很好地映射到主機側的文件描述符,或vector或slab中的索引。通過引用返回的句柄,客機應用能夠遠程控制真正的套接字。由于主機側是完全異步的,它可以簡單地將套接字狀態(tài)轉發(fā)給客戶端。如果您注意到上面沒有使用waker函數(shù),非常棒!這是因為當調(diào)用主機調(diào)用時,它不僅開始打開一個套接字,還注冊了當前的waker,以便在套接字打開時調(diào)用(或者不調(diào)用)。因此,當套接字就緒時,主機任務將被喚醒,它將從其上下文中找到相應的客機任務,并使用trampoline函數(shù)將其喚醒,如圖5所示。在其他情況下,客機任務需要等待另一個客機任務,例如一個異步互斥。這里的機制類似:使用主機調(diào)用來注冊waker。

以上復雜操作都封裝在我們的客戶異步運行時中,提供易于使用的API,以便客機應用可以訪問常規(guī)的異步函數(shù),而不必考慮底層的細節(jié)。

寫在最后

希望本文能讓您對支持1.1.1.1的創(chuàng)新平臺有一個大致的了解。這個平臺仍在發(fā)展。截至今天,我們的幾個產(chǎn)品都是由BigPineapple上運行的客機應用支持,例如1.1.1.1 for Families、AS112和Gateway DNS。我們期待著將新技術引入其中。如果您有任何想法,請通過社區(qū)或電子郵件告訴我們。

立即登錄,閱讀全文
原文鏈接:點擊前往 >
文章來源:Cloudflare
版權說明:本文內(nèi)容來自于Cloudflare,本站不擁有所有權,不承擔相關法律責任。文章內(nèi)容系作者個人觀點,不代表快出海對觀點贊同或支持。如有侵權,請聯(lián)系管理員(zzx@kchuhai.com)刪除!
優(yōu)質(zhì)服務商推薦
更多
掃碼登錄
打開掃一掃, 關注公眾號后即可登錄/注冊
加載中
二維碼已失效 請重試
刷新
賬號登錄/注冊
個人VIP
小程序
快出海小程序
公眾號
快出海公眾號
商務合作
商務合作
投稿采訪
投稿采訪
出海管家
出海管家