最近工作中架構升級,將原來的 EMR 集群遷移到基於開源的自建集群上,原來使用的一些組件自然也需要改造,其中就包括 s3。在我們的自建集群中,選用的開源 hadoop 中 s3a client(或者 s3a connector,下面簡寫成 s3a,意義基本相同)來連接原有的 s3 存儲。接下來我就會分享我在用 s3a client 時總結的一些經驗
s3 協議的類型#
首先介紹一下 s3,s3 全稱應該叫AWS S3
,又稱AWS Amazon Simple Storage Service
,是亞馬遜公司開發的對象存儲服務,廣義上講是一款包含了 Web 服務在內的完整存儲產品,狹義上也能特指 AWS 的對象存儲。它通過自定的 s3 協議訪問存儲上的文件,其路徑類似:s3://xxx/yyy/zzz
鑒於 AWS 在雲市場的占有地位,s3 存儲在各個行業也有廣泛的應用。然而 s3 協議是 AWS 的私有協議,只有 AWS 產品可用,為了能鞏固和擴大 s3 在市場的地位,讓用戶能任何地方連上 s3 存儲,放心地把數據存儲到 s3 上,AWS 向 Hadoop 提供了開源版本的 s3 client 來連接 s3,也即是s3n client,在 s3n 協議上的文件路徑類似:s3n://xxx/yyy/zzz
後來 Hadoop 在升級中淘汰了 s3n client,採用了全新的s3a client,在 s3a 協議下的文件路徑類似:s3a://xxx/yyy/zzz
(Hadoop 最初推出的 s3 client,也採用 s3 協議,即文件路徑類似:s3://
,但是年代久遠,早已廢棄,這裡就不提了)
總結一下三種文件協議的區別
- s3 協議是 AWS 最初也是私有的協議,廣泛用在 AWS 的產品中,例如 EMR,EC2 等等。如果你們公司使用的是 AWS 全家桶,基本都是通過 s3 協議訪問 s3
- s3n 和 s3a 是 hadoop 上基於 AWS s3 SDK 開發的開源 s3 client,能夠擺脫 AWS 產品的限制,在自建的服務中自由訪問 s3 存儲,適用於僅使用了 AWS 的 s3 存儲服務,需要和多方產品交互的場景
- 在性能上,三者差別不大,考慮到 s3 由 AWS 自己維護,在版本迭代的速度上可能要快於開源的 s3n 和 s3a
- 在兼容性上,作為 s3 協議的派生,原則上 s3n 和 s3a 都是兼容 s3 協議的;而 s3n 和 s3a 之間一般情況也是兼容(即原來採用的 s3n 協議,後來升級到了 s3a),但是 s3n 和 s3a 之間在目錄創建上存在一些區別(下一 part 會提及),在一些場景中需要注意
s3a 在目錄處理上的不同#
實際上很多人並不認同 s3 為文件系統,因此 s3 是對象存儲,和傳統的文件系統有較大的區別。在官方的文檔也說:
Amazon S3 is not a filesystem, it is an object store.
兩者的一個重要區別就是對目錄概念的解釋。
- 傳統的 Unix 風格的文件系統,例如 HDFS,其文件系統的組成是目錄文件樹,即生成的目錄都是 “一直存在” 的,無論目錄內是否存在文件。
- s3 “文件系統”,由於其底層是通過對象存儲(或者稱之為塊存儲),其目錄是 “虛擬” 的,舉個例子:如果兩個對象有相同的路徑前綴:
a/b/file1
和a/b/file2
,那麼 s3 會認為這裡存在目錄a/b/
兩者在這方面的差異就會導致在實際使用中可能出現各種坑
在具體場景的區別#
讓我們再細分一下場景:
創建目錄
- HDFS 等目錄文件樹會創建一個空目錄,可以往裡面添加文件和目錄,也可以在任意時候(無論目錄中有沒有文件或其他目錄)通過 ls 來發現這個目錄
- 實際上,要是 s3 也能做到這一點,那它也可以被視為實現了目錄文件樹,
可惜它做不到。由於目錄是 s3 是通過前綴來識別目錄,因此當目錄中沒有任何文件時,需要通過目錄標記(Directory Marker,後面簡稱 DM)來標記目錄。當目錄中創建了文件後,就會刪除這個 DM;反之,當目錄刪除成為空目錄時,又會添加這個 DM- 在 s3a 中,會在空目錄的場景中生成以
path_name+/
為名稱的文件來作為 DM,例如執行mkdir(s3a://bucket/a/b)
會創建一個標記對象a/b/
- 在更老的 s3n 中,則是以
path_name_$folder$
方式作為 DM
- 在 s3a 中,會在空目錄的場景中生成以
相信從這裡就能看出一絲不妙了吧,當新老版本的產品在某些約定上存在分歧時,往往會出問題。簡單舉個例子,當你用aws s3
命令創建了目錄,但是卻用 s3a 去連接時,往往會找不到該目錄,因為在aws s3
工具中創建的目錄沒有創建 s3a 的 DM,因此在 s3a 協議中找不到目錄。同理,當一個集群從低版本的 hadoop 升級到高版本時,也需要格外留意空目錄的存在問題,因為有可能升級的同時將 s3n 升級到了 s3a,識別的 DM 發生變化,就檢查不到原來的空目錄。
創建文件
- 在一般目錄文件樹的文件系統中,只需要按照目錄所在路徑,創建單一文件即可
- 在 s3 中,創建文件的操作可能會伴隨著一系列 DM 的刪除操作,s3a 需要在一個請求中包含刪除所有父級 DM 的請求
刪除目錄和文件
- HDFS 目錄文件樹的刪除目錄操作和刪除文件操作基本相同,其刪除的語義也更符合一般邏輯
- 在 s3 中,由於 DM 的存在,當刪除文件 / 目錄的時候如果父級目錄變成了空目錄,則需要將 DM 補充添加
存在的問題#
s3 對目錄的處理雖是無奈之舉,但也確實成為了許多問題的源頭
- s3n 和 s3a 上採用的 DM 不同,意味著 s3a 所在的 hadoop 版本無法向下兼容
- 用 s3a 創建或刪除文件的時候一般會需要刪除或創建一批 DM,這會導致實際的請求量較大。而且,s3 中每個對象的讀寫視為一次操作,因此可能會帶來較大的開銷(結合 s3 的讀寫限制,這是 s3 性能不佳的主要原因,具體內容下節討論)
- 在使用 list 操作的時候,由於每個請求中列出的對象的數量是父目錄的數量,因此目錄層級越深,請求越長。
- 在正式版本的 s3 桶中,即使沒有對象沒刪除,邏輯刪除的標記也會寫進索引裡,這對使大型目錄的查詢變慢
s3a 的性能問題#
在 s3a 的官方文檔上,展示了 s3a 和 HDFS 的一些不同,我將它貼了過來:
總的來說,以下幾點原因導致了 s3a 的性能問題
- 由於桶分片導致 IOPS 被限制。
- 不同類型的 AWS EC2 虛擬機可能進行網絡 IO 進行不同的限制。
- 對象和數據越多,目錄重命名和複製操作花費的時間就越長。rename () 的性能更加緩慢。
- 在讀取 s3 時使用 seek () 操作會強制新的 HTTP 請求。這可能會增大使讀取 Parquet/ORC 文件的開銷。
另外,還需要注意的是,由於 AWS s3 在讀寫上做出了頻率限制,按照一般約定,當對 s3 的一個分區每秒超過讀 5500 次或寫 3500 次時,s3 就會拒絕請求,顯示錯誤 503
可見,若是使用 s3 進行大量讀寫還是存在著不小的挑戰,在大數據領域中使用 s3 還是更多看重的是它的計費成本和性價比。如果你的場景追求性能,那我的建議是:快跑!
解決方法
當然,實際場景下很多選擇往往不是我們能左右的,假如我們必須要在一些 OLAP 或者其他大規模數據處理場景中使用 s3,這裡依然會有一些優化建議。
- 使用 s3a committer。hadoop 內置了多種 s3a commiter 來優化 s3 文件的提交,其核心思路就是利用 s3 的 multipart Upload 機制來加速文件上傳,同時不同的 committer 也有不同的優化思路,具體可以參考這篇文檔
- 一些參數的調優,例如合理配置線程數和連接數,加大塊讀取的大小等等
更多優化的場景和細節,可以查看官方文檔
總結#
總的來說,s3 本身在某些場景還是很有優勢的,作為 AWS 生態的重要一環,人們更看重它的安全可靠,物美價廉,以及與 AWS 其他雲產品之間聯動帶來的疊加優勢。但我們也要承認在不適當的場景中使用 s3 依然會不小的副作用。在實際生產中,也可能存在各種妥協因素,無法做到釜底抽薪,但我們至少可以了解產品特性,盡最大可能優化性能。
因此我就簡單寫了一些 s3a 常見的可能會踩坑的點以及如何採取措施,其實大多來自於官方文檔的整理,希望對你有所幫助~