隨著 OpenDAL 社區(qū)的不斷發(fā)展,新的抽象在不斷增加,為新的貢獻(xiàn)者參與開發(fā)帶來了不少負(fù)擔(dān),不少維護(hù)者都希望對 OpenDAL 的內(nèi)部實現(xiàn)有更深入的了解。與此同時,OpenDAL 的核心設(shè)計已經(jīng)很長時間沒有大幅度的變化,為寫一個內(nèi)部實現(xiàn)系列提供了可能。我想現(xiàn)在是時候?qū)懸幌盗嘘P(guān)于 OpenDAL 內(nèi)部實現(xiàn)的文章,從維護(hù)者的角度來闡述 OpenDAL 如何設(shè)計,如何實現(xiàn)以及如何擴展。在 OpenDAL v0.40 即將發(fā)布之際,希望這系列文章能夠更好的幫助社區(qū)理解過去,掌握現(xiàn)在,并確定未來。
第一篇文章會先聊聊 OpenDAL 最常使用的數(shù)據(jù)讀取功能,我會從最外層的接口開始,然后按照 OpenDAL 的調(diào)用順序來逐步展開。讓我們開始吧!
整體框架
在開始介紹具體的 OpenDAL 接口之前,我們首先熟悉一下 OpenDAL 項目。
OpenDAL[1]是一個 Apache Incubator 項目,旨在幫助用戶從各種存儲服務(wù)中以統(tǒng)一的方式便捷高效訪問數(shù)據(jù)。它的項目愿景[2]是 “自由訪問數(shù)據(jù)”:
?Free from services: 任意服務(wù)都能通過原生接口自由訪問
?Free from implementations: 無論底層實現(xiàn)如何,都可以通過統(tǒng)一的方式調(diào)用
?Free to integrate: 能夠自由地與各種服務(wù),語言集成
?Free to zero cost: 用戶不需要為用不到的功能付出開銷
在這套理念的基礎(chǔ)上,OpenDAL Rust Core 可以主要分成以下組成部分:
?Operator: 對用戶暴露的外層接口
?Layers: 不同中間件的具體實現(xiàn)
?Services: 不同服務(wù)的具體實現(xiàn)
所以從宏觀的角度上來看,OpenDAL 的數(shù)據(jù)讀取調(diào)用??雌饋頃袷沁@樣:
所有 Layers 和 Services 都實現(xiàn)了統(tǒng)一了 Accessor 接口,在進(jìn)行 Operator 構(gòu)建時會抹除所有的類型信息。對 Operator 來說,不管用戶使用什么服務(wù)或者增加了多少中間件,所有的調(diào)用邏輯都是一致的。這一設(shè)計將 OpenDAL 的 API 拆分成了 Public API 和 Raw API 兩層,其中 Public API 直接暴露給用戶,提供便于使用的上層接口,而 Raw API 則是面向 OpenDAL 內(nèi)部開發(fā)者提供,維護(hù)統(tǒng)一的內(nèi)部接口,并提供一些便利的實現(xiàn)。
Operator
OpenDAL 的 Operator API 會盡可能遵循一致的調(diào)用范式,減少用戶的學(xué)習(xí)和使用成本。以read為例,OpenDAL 提供了以下 API:
?op.read(path): 將指定文件全部內(nèi)容讀出
?op.reader(path): 創(chuàng)建一個 Reader 用來做流式讀取
?op.read_with(path).range(1..1024): 使用指定參數(shù)來讀取文件內(nèi)容,比如說 range
?op.reader_with(path).range(1..1024): 使用指定參數(shù)來創(chuàng)建 Reader 做流式讀取
不難看出read更像是一個語法糖,用來方便用戶快速地進(jìn)行文件讀取而不需要考慮AsyncRead等各種 trait。而reader則給予了用戶更多的靈活度,實現(xiàn)了AsyncSeek,AsyncRead等社區(qū)廣泛使用的 trait,允許用戶更靈活的讀取數(shù)據(jù)。read_with和reader_with則通過 Future Builder 系列函數(shù),幫助用戶以更自然的方式來指定各種參數(shù)。
Operator 內(nèi)部的邏輯看起來會是這樣:
它的主要工作是面向用戶封裝接口:
?完成OpRead的構(gòu)建
?調(diào)用Accessor提供的read函數(shù)
?將返回的值包裹為Reader并在Reader的基礎(chǔ)上實現(xiàn)AsyncSeek,AsyncRead等接口
Layers
這里有一個隱藏的小秘密是 OpenDAL 會自動為 Service 套上一些 Layer 以實現(xiàn)一些內(nèi)部邏輯,截止到本文完成的時候,OpenDAL 自動增加的 Layer 包括:
?ErrorContextLayer: 為所有的 Operation 返回的 error 注入 context 信息,比如scheme,path等
?CompleteLayer: 為服務(wù)補全必須的能力,比如說為 s3 增加 seek 支持
?TypeEraseLayer: 實現(xiàn)類型擦除,將Accessor中的關(guān)聯(lián)類型統(tǒng)一擦除,讓用戶使用時不需要攜帶泛型參數(shù)
這里的ErrorContextLayer和TypeEraseLayer都比較簡單不再贅述,重點聊聊CompleteLayer,它旨在以零開銷的方式為 OpenDAL 返回的Reader增加seek或者next支持,讓用戶不需要再重復(fù)實現(xiàn)。OpenDAL 在早期版本中通過不同的函數(shù)調(diào)用來返回Reader和SeekableReader,但是用戶的實際反饋并不是很好,幾乎所有用戶都在使用SeekableReader。因此后續(xù) OpenDAL 在重構(gòu)中將 seek 支持作為第一優(yōu)先級加入了內(nèi)部的Readtrait 中:
pubtraitRead:Unpin+Send+Sync{ ///Readbytesasynchronously. fnpoll_read(&mutself,cx:&mutContext<'_>,buf:&mut[u8])->Poll>; ///Seekasynchronously. /// ///Returns`Unsupported`errorifunderlyingreaderdoesn'tsupportseek. fnpoll_seek(&mutself,cx:&mutContext<'_>,pos:io::SeekFrom)->Poll >; ///Stream[`Bytes`]fromunderlyingreader. /// ///Returns`Unsupported`errorifunderlyingreaderdoesn'tsupportstream. /// ///ThisAPIexistsforavoidingbytescopyinginsideasyncruntime. ///Userscanpollbytesfromunderlyingreaderanddecidewhento ///read/consumethem. fnpoll_next(&mutself,cx:&mutContext<'_>)->Poll
在 OpenDAL 中實現(xiàn)一個服務(wù)的讀取能力就需要實現(xiàn)這個 trait,這是一個內(nèi)部接口,不會直接暴露給用戶,其中:
?poll_read是最基礎(chǔ)的要求,所有服務(wù)都必須實現(xiàn)這一接口。
?當(dāng)服務(wù)原生支持seek時,可以實現(xiàn)poll_seek,OpenDAL 會進(jìn)行正確的 dispatch,比如說 local fs;
?而當(dāng)服務(wù)原生支持next,即返回流式的 Bytes 時,可以實現(xiàn)poll_next,比如說基于 HTTP 的服務(wù),他們底層是一個 TCP Stream,hyper 會將其封裝為一個 bytes stream。
通過Readtrait,OpenDAL 確保所有服務(wù)都能盡可能地暴露自己的原生支持能力,從而提供對不同服務(wù)都能實現(xiàn)高效的讀取。
在此 trait 的基礎(chǔ)上,OpenDAL 會根據(jù)各個服務(wù)支持的能力來進(jìn)行補全:
?seek/next 都支持:直接返回
?不支持 next: 使用StreamableReader進(jìn)行封裝以模擬 next 支持
?不支持 seek: 使用ByRangeSeekableReader進(jìn)行封裝以模擬 seek 支持
?seek/next 均不支持:同時進(jìn)行兩種封裝
ByRangeSeekableReader主要利用了服務(wù)支持 range read 的能力,當(dāng)用戶進(jìn)行 seek 的時候就 drop 當(dāng)前 reader 并在指定的位置發(fā)起新的請求。
OpenDAL 通過CompleteLayer暴露出一個統(tǒng)一的 Reader 實現(xiàn),用戶不需要考慮底層服務(wù)是否支持 seek,OpenDAL 總是會選擇最優(yōu)的方式來發(fā)起請求。
Services
經(jīng)過 Layers 的補全之后,就到調(diào)用 Service 具體實現(xiàn)的地方,這里分別以最常見的兩類服務(wù)fs和s3來舉例說明數(shù)據(jù)是如何讀取的。
Service fs
tokio::File實現(xiàn)了tokio::AsyncRead和tokio::AsyncSeek,通過使用async_compat::Compat,我們將其轉(zhuǎn)化為了futures::AsyncRead和futures::AsyncSeek。在此基礎(chǔ)上,我們提供了內(nèi)置的函數(shù)oio::into_read_from_file將其轉(zhuǎn)化為實現(xiàn)了oio::Read的類型,最終的類型名為:oio::FromFileReader
oio::into_read_from_file實現(xiàn)中沒有什么特別復(fù)雜的地方,read 和 seek 基本上都是在調(diào)用傳入的 File 類型提供的函數(shù)。比較麻煩的地方是關(guān)于 seek 和 range 的正確處理:seek 到 range 右側(cè)是允許的行為,此時不會報錯,read 也只會返回空,但是 seek 到 range 左側(cè)是非法行為,Reader 必須返回InvalidInput以便于上層正確處理。
有趣的歷史:當(dāng)初這塊實現(xiàn)的時候有問題,還是在 fuzz 測試中發(fā)現(xiàn)的。
Services s3
S3 是一個基于 HTTP 的服務(wù),opendal 提供了大量基于 HTTP 的封裝以幫助開發(fā)者重用邏輯,只需要構(gòu)建請求,并返回構(gòu)造好的 Body 即可。OpenDAL Raw API 封裝了一套基于 reqwest 的接口,HTTP GET 接口會返回一個Response
///IncomingAsyncBodycarriesthecontentreturnedbyremoteservers. pubstructIncomingAsyncBody{ ///#TODO /// ///hyperreturns`implStream- >`butwecan't ///writethetypesinstable.Sowewillboxhere. /// ///After[TAIT](https://rust-lang.github.io/rfcs/2515-type_alias_impl_trait.html) ///hasbeenstable,wecanchange`IncomingAsyncBody`into`IncomingAsyncBody
`. inner:oio::Streamer, size:Option, consumed:u64, chunk:Option , }
這個 body 內(nèi)部包含的 stream 是 reqwest 返回的 bytes stream,opendal 在此基礎(chǔ)上實現(xiàn)了 content length 檢查和 read 支持。
這里額外提一嘴關(guān)于 reqwest/hyper 的小坑:reqwets 和 hyper 并沒有檢查返回的 content length,所以一個非法的 server 可能會返回與預(yù)期的 content length 不符的數(shù)據(jù)量而非報錯,進(jìn)而導(dǎo)致數(shù)據(jù)的行為不符合預(yù)期。OpenDAL 在這里專門增加了檢查,在數(shù)據(jù)不足時返回ContentIncomplete,并在數(shù)據(jù)超出預(yù)期時返回ContentTruncated,避免用戶收到非法的數(shù)據(jù)。
總結(jié)
本文自頂向下介紹了 OpenDAL 如何實現(xiàn)數(shù)據(jù)讀?。?/p>
?Operator 負(fù)責(zé)對用戶暴露易用的接口
?Layers 負(fù)責(zé)對服務(wù)的能力進(jìn)行補全
?Services 負(fù)責(zé)不同服務(wù)的具體實現(xiàn)
在整個鏈路中 OpenDAL 都盡可能遵循零開銷的原則,優(yōu)先使用服務(wù)原生提供能力,其次再考慮通過其他的方法進(jìn)行模擬,最后才會返回不支持的報錯。通過這三層的設(shè)計,用戶不需要了解底層服務(wù)的細(xì)節(jié),也不需要接入不同服務(wù)的 SDK 就可以輕松地調(diào)用op.read(path)來訪問任意存儲服務(wù)中的數(shù)據(jù)。
這就是: HowOpenDALread data freely!
-
接口
+關(guān)注
關(guān)注
33文章
8998瀏覽量
153708 -
API
+關(guān)注
關(guān)注
2文章
1613瀏覽量
64011 -
函數(shù)
+關(guān)注
關(guān)注
3文章
4379瀏覽量
64839 -
數(shù)據(jù)讀取
+關(guān)注
關(guān)注
0文章
9瀏覽量
6616
原文標(biāo)題:OpenDAL 內(nèi)部實現(xiàn):數(shù)據(jù)讀取
文章出處:【微信號:Rust語言中文社區(qū),微信公眾號:Rust語言中文社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
如何在MCS中實現(xiàn)ADC數(shù)據(jù)讀取?
ADS1299的配套軟件不支持讀取級聯(lián)的其他芯片的數(shù)據(jù),如何實現(xiàn)讀取級聯(lián)的多個芯片的數(shù)據(jù)呢?
FPGA怎么實現(xiàn)控制CF從SDRAM中讀取數(shù)據(jù)及實現(xiàn)CF卡向SDRAM傳數(shù)據(jù)
LabvIEW中如何實現(xiàn)大容量數(shù)據(jù)的快速讀取呢?
實現(xiàn) Labview 和SQL server進(jìn)行數(shù)據(jù)的讀取和寫入
MOVC實現(xiàn)讀取程序存儲區(qū)域的靜態(tài)數(shù)據(jù)
基于CPLD的Flash讀取控制的設(shè)計與實現(xiàn)
衛(wèi)星SAR數(shù)據(jù)讀取與保存方法研究與軟件實現(xiàn)
TensorFlow數(shù)據(jù)讀取機制分析

Windows Server實現(xiàn)RAID技術(shù),保證數(shù)據(jù)的讀取速度和安全
使用STM32單片機實現(xiàn)AD7606并行讀取數(shù)據(jù)的代碼免費下載

用STM32實現(xiàn)MPU6050原始數(shù)據(jù)的讀取

內(nèi)存是怎么讀取數(shù)據(jù)的
基于C#實現(xiàn)文本讀取的7種方式是什么

評論