輸出:OutputStreamSyncableStreamCapabilities

簡介

這份文件涵蓋 Hadoop 檔案系統規格 中的輸出串流。

它使用 Hadoop 檔案系統模型 中定義的檔案系統模型,以及 符號 中定義的符號。

目標受眾為:1. API 使用者。雖然 java.io.OutputStream 是標準介面,但這份文件會說明 HDFS 和其他地方如何實作它。Hadoop 特定的介面 SyncableStreamCapabilities 是新的;Syncable 特別提供高於 OutputStream 的耐用性和可見性保證。1. 檔案系統和用戶端的實作人員。

資料如何寫入檔案系統

透過 Hadoop 檔案系統 API 將資料寫入檔案的主要機制,是透過呼叫 FileSystem.create()FileSystem.append()FSDataOutputStreamBuilder.build() 取得的 OutputStream 子類別。

這些都會傳回 FSDataOutputStream 的執行個體,資料可透過各種 write() 方法寫入其中。在呼叫串流的 close() 方法後,寫入串流的所有資料都必須保留到檔案系統中,而且所有其他嘗試透過 FileSystem.open() 從該路徑讀取資料的用戶端都必須能看到這些資料。

除了寫入資料的作業外,Hadoop 的 OutputStream 實作也提供方法將緩衝資料回寫到檔案系統,以確保資料可靠地保留和/或讓其他呼叫者看到。這是透過 Syncable 介面完成的。最初預期這個介面的存在可以解釋為串流支援其方法的保證。然而,這已被證明無法保證,因為介面的靜態特性與同步語意可能因儲存/路徑而異的檔案系統不相容。例如,HDFS 中的抹除編碼檔案不支援同步作業,即使它們是實作為 Syncable 輸出串流的子類別。

新的介面:StreamCapabilities。這讓呼叫者可以探查串流的確切功能,甚至透過串流鏈傳遞。

輸出串流模型

對於此規格,輸出串流可視為儲存在客戶端中的一串位元組;hsync()hflush() 是將資料傳播至檔案其他讀取器可見和/或持久化的動作操作。

buffer: List[byte]

旗標 open 追蹤串流是否開啟:串流關閉後,不得再寫入資料。

open: bool
buffer: List[byte]

串流的目的地路徑 path 可追蹤以形成三元組 path, open, buffer

Stream = (path: Path, open: Boolean, buffer: byte[])

已快取資料的可見性

在將資料快取至檔案系統的 Syncable 操作(立即)之後,串流目的地路徑中的資料必須與 buffer 相符。亦即,必須符合下列條件

FS'.Files(path) == buffer

任何在路徑讀取資料的客戶端都必須看到新的資料。Syncable 操作在持久性保證上有所不同,而非資料可見性。

Filesystem.create() 之後串流和檔案系統的狀態

檔案系統 FSFileSystem.create(path)FileSystem.createFile(path).build() 傳回的輸出串流可建模為包含一個空資料陣列的三元組

Stream' = (path, true, [])

檔案系統 FS' 必須在路徑中包含一個 0 位元組檔案

FS' = FS where data(FS', path) == []

因此,Stream'.buffer 的初始狀態隱含地與檔案系統中的資料一致。

物件儲存:請參閱下方「物件儲存」區段中的注意事項。

Filesystem.append() 之後串流和檔案系統的狀態

檔案系統 FSFileSystem.append(path, buffersize, progress) 呼叫傳回的輸出串流可建模為一個串流,其 buffer 初始化為原始檔案的 buffer

Stream' = (path, true, data(FS, path))

持續化資料

當串流將資料寫回其儲存時,無論是在任何受支援的快取操作、close() 操作,或串流選擇執行的任何其他時間,檔案的內容都會被目前的緩衝區取代

Stream' = (path, true, buffer)
FS' = FS where data(FS', path) == buffer

呼叫 close() 之後,串流會對 close() 以外的所有操作關閉;它們可能會因 IOExceptionRuntimeException 而失敗。

Stream' = (path, false, [])

close() 操作必須是冪等的,且僅嘗試在第一次呼叫中寫入資料。

  1. 如果 close() 成功,後續呼叫將為空操作。
  2. 如果 close() 失敗,後續呼叫將再次為空操作。它們可能會重新擲出前一個例外,但它們不得重試寫入。

類別 FSDataOutputStream

public class FSDataOutputStream
  extends DataOutputStream
  implements Syncable, CanSetDropBehind, StreamCapabilities {
 // ...
}

FileSystem.create()FileSystem.append()FSDataOutputStreamBuilder.build() 呼叫會傳回 FSDataOutputStream 類別的執行個體,FSDataOutputStreamjava.io.OutputStream 的子類別。

基底類別包裝一個 OutputStream 實例,它可能實作 SyncableCanSetDropBehindStreamCapabilities

此文件涵蓋此類實作的要求。

HDFS 的 FileSystem 實作 DistributedFileSystem 會傳回 HdfsDataOutputStream 的實例。此實作至少有兩個行為,未由基底 Java 實作明確宣告

  1. 寫入會同步:多個執行緒可以寫入同一個輸出串流。這是 HBase 依賴的用法模式。

  2. 當檔案關閉時,OutputStream.flush() 沒有作用。Apache Druid 過去曾對此呼叫 HADOOP-14346

由於 HDFS 實作被視為 FileSystem API 的實際規格,因此 write() 是執行緒安全的這項事實很重要。

為了相容性,其他 FS 用戶端不只應該執行緒安全,新的 HDFS 功能(例如加密和刪除編碼)也應該實作與核心 HDFS 輸出串流一致的行為。

換句話說

輸出串流實作 java.io.OutputStream 的核心語意還不夠:它們需要實作 HdfsDataOutputStream 的額外語意,尤其是讓 HBase 能正確運作。

並行的 write() 呼叫是對 Java 規格最重要的強化。

類別 java.io.OutputStream

Java OutputStream 允許應用程式將一連串位元組寫入目的地。在 Hadoop 檔案系統中,該目的地是檔案系統中路徑下的資料。

public abstract class OutputStream implements Closeable, Flushable {
  public abstract void write(int b) throws IOException;
  public void write(byte b[]) throws IOException;
  public void write(byte b[], int off, int len) throws IOException;
  public void flush() throws IOException;
  public void close() throws IOException;
}

write(Stream, data)

將一個位元組資料寫入串流。

前提條件

Stream.open else raise ClosedChannelException, PathIOException, IOException

當嘗試寫入已關閉的檔案時,HDFS 輸出串流中會引發例外狀況 java.nio.channels.ClosedChannelExceptionn。此例外狀況不包含目的地路徑;且 Exception.getMessage()null。因此,它在堆疊追蹤中價值有限。實作人員可能希望引發更詳細的例外狀況,例如 PathIOException

後置條件

緩衝區會附加資料引數的較低 8 個位元組。

Stream'.buffer = Stream.buffer + [data & 0xff]

快取資料的大小可能有一個明確的限制,或一個根據目的地檔案系統可用容量而定的隱含限制。當達到限制時,write() 應使用 IOException 失敗。

write(Stream, byte[] data, int offset, int len)

前提條件

前提條件全部定義在 OutputStream.write()

Stream.open else raise ClosedChannelException, PathIOException, IOException
data != null else raise NullPointerException
offset >= 0 else raise IndexOutOfBoundsException
len >= 0 else raise IndexOutOfBoundsException
offset < data.length else raise IndexOutOfBoundsException
offset + len < data.length else raise IndexOutOfBoundsException

操作傳回後,緩衝區可以重新使用。在 write() 操作進行中更新緩衝區的結果未定義。

後置條件

Stream'.buffer = Stream.buffer + data[offset...(offset + len)]

write(byte[] data)

這定義為等同於

write(data, 0, data.length)

flush()

要求將資料沖刷。ObjectStream.flush() 的規格宣告這應將資料寫入「預期的目的地」。

它明確排除任何關於耐用性的保證。

因此,本文檔未提供任何關於行為的規範性規格。

前提條件

無。

後置條件

無。

如果實作選擇實作串流沖刷操作,資料可能會儲存到檔案系統,讓其他人可以看到

FS' = FS where data(FS', path) == buffer

當串流關閉時,如果 flush() 不是空操作,它應降級為空操作。這是為了與應用程式和函式庫合作,它們可以這樣呼叫它。

問題flush() 應轉發到 hflush() 嗎?

否。或至少,讓它成為選項。

許多應用程式程式碼假設 flush() 成本低,而且應在寫入每一行輸出、寫入 4KB 小區塊或類似操作後呼叫。

將它轉發到分散式檔案系統的完整沖刷,或更糟的是,遠端物件儲存,非常沒有效率。將 flush() 轉換為 hflush() 的檔案系統用戶端最終必須回滾該功能:HADOOP-16548

close()

close() 操作會將所有資料儲存到檔案系統,並釋放用於寫入資料的任何資源。

預期 close() 呼叫會封鎖,直到寫入完成(與 Syncable.hflush() 一樣),可能直到寫入到耐用儲存空間為止。

close() 完成後,檔案中的資料必須可見,而且與最近寫入的資料一致。檔案的元資料必須與資料和寫入記錄本身一致(即更新任何修改時間欄位)。

呼叫 close() 之後,對串流的後續 write() 呼叫都必須失敗並傳回 IOException

任何鎖定/租用機制都必須釋放其鎖定/租用。

Stream'.open = false
FS' = FS where data(FS', path) == buffer

close() 呼叫可能會在其作業期間失敗。

  1. API 的呼叫者必須預期某些 close() 呼叫會失敗,並應適當地編寫程式碼。捕捉並吞下例外情況雖然常見,但並非總是理想的解決方案。
  2. 即使在失敗之後,close() 也必須將串流置於關閉狀態。後續對 close() 的呼叫將被忽略,而對其他方法的呼叫則會被拒絕。也就是說:呼叫者無法預期重複呼叫 close() 直到成功。
  3. close() 作業的持續時間未定義。依賴遠端系統的確認來滿足持久性保證的作業必須隱式地等待這些確認。某些物件儲存輸出串流會在 close() 作業中上傳整個資料檔案。這可能會花費大量時間。由於許多使用者應用程式假設 close() 既快速又不會失敗,因此此行為很危險。

呼叫者安全使用的建議

  • 請規劃引發例外情況,無論是透過捕捉並記錄,還是將例外情況往上傳遞。捕捉並靜默吞下例外情況可能會隱藏嚴重問題。
  • 心跳作業應在個別執行緒上進行,這樣一來,close() 的長時間延遲就不會長時間封鎖執行緒,導致心跳逾時。

實作人員

  • 請參閱 HADOOP-16785 以查看 close 中的複雜性範例。
  • 在 close 作業之前逐步寫入區塊會產生更符合客戶端預期的行為:寫入失敗會提早浮現,而 close 則比實際上傳更像是家務管理。
  • 如果區塊上傳是在個別執行緒中執行,則輸出串流 close() 呼叫必須封鎖,直到所有非同步上傳完成為止;必須報告任何引發的錯誤。如果引發多個錯誤,則串流可以選擇傳播哪一個。重要的是:當 close() 在沒有錯誤的情況下傳回時,應用程式會預期資料已成功寫入。

HDFS 和 OutputStream.close()

HDFS 沒有在 OutputStream.close() 上立即將已寫入檔案的輸出同步 (sync()) 到磁碟,除非已設定 dfs.datanode.synconclose 為 true。這已導致 某些應用程式出現問題

絕對需要保證檔案已持續存在的應用程式,在檔案關閉之前,必須呼叫 Syncable.hsync()

org.apache.hadoop.fs.Syncable

@InterfaceAudience.Public
@InterfaceStability.Stable
public interface Syncable {


  /** Flush out the data in client's user buffer. After the return of
   * this call, new readers will see the data.
   * @throws IOException if any error occurs
   */
  void hflush() throws IOException;

  /** Similar to posix fsync, flush out the data in client's user buffer
   * all the way to the disk device (but the disk may have it in its cache).
   * @throws IOException if error occurs
   */
  void hsync() throws IOException;
}

Syncable 介面的目的是提供資料已寫入檔案系統的保證,以確保可見性和耐用性。

SYNC-1:實作 Syncable 且在呼叫時不會引發 UnsupportedOperationExceptionOutputStream,明確宣告它可以符合這些保證。

SYNC-2:如果串流宣告已實作介面,但未提供耐用性,則介面的方法必須引發 UnsupportedOperationException

Syncable 介面已由 OutputStream 子類別以外的其他類別實作,例如 org.apache.hadoop.io.SequenceFile.Writer

SYNC-3 實作 Syncable 的類別,並不能保證 extends OutputStream 成立。

也就是說,對於任何類別 C(C instanceof Syncable) 並不表示 (C instanceof OutputStream)

本規範僅涵蓋實作 SyncableOutputStream 子類別所需行為。

SYNC-4: FileSystem.create(Path) 的傳回值是 FSDataOutputStream 的執行個體。

SYNC-5: FSDataOutputStream implements Syncable

SYNC-5 和 SYNC-1 表示可以使用 FileSystem.create(Path) 建立的所有輸出串流都必須支援 Syncable 的語意。這顯然不正確:如果封裝的串流不是 SyncableFSDataOutputStream 只會降級為 flush()。因此,宣告 SYNC-1 和 SYNC-2 不成立:您無法信任 Syncable

換句話說:呼叫者不能依賴介面的存在來證明支援 Syncable 的語意。相反地,他們必須使用 StreamCapabilities 介面(如果可用)動態探查。

Syncable.hflush()

將資料沖出至用戶端使用者緩衝區。呼叫此函式後,新的讀取器將會看到資料。hflush() 作業不包含任何資料耐用性的保證,只保證可見性。

因此,實作可能會將已寫入的資料快取在記憶體中,讓所有人可見,但尚未持續存在。

前提條件

hasCapability(Stream, "hflush")
Stream.open else raise IOException

後置條件

FS' = FS where data(path) == cache

呼叫傳回後,資料必須對所有呼叫 FileSystem.open(path)FileSystem.openFile(path).build() 的新呼叫者可見。

沒有任何需求或保證,使用呼叫 (FS, path) 建立的現有 DataInputStream 的用戶端會看到更新的資料,也沒有保證在目前或後續的讀取中他們不會看到。

實作注意事項:由於正確的 hsync() 實作也必須提供 hflush() 呼叫的所有語意,hflush() 的實作可能只呼叫 hsync()

public void hflush() throws IOException {
  hsync();
}

hflush() 效能

hflush() 呼叫必須封鎖,直到儲存體確認已收到資料,且資料現已對其他人可見。這可能會很慢,因為這會包含從用戶端上傳任何未完成資料的時間,以及檔案系統本身處理資料的時間。

檔案系統通常只提供 Syncable.hsync() 保證:持久性以及可見性。這表示傳回的時間可能會更長。

應用程式程式碼不得在每行結尾呼叫 hflush()hsync(),或是在每筆記錄結尾呼叫,除非他們正在寫入 WAL。請小心使用。

Syncable.hsync()

類似於 POSIX fsync(),此呼叫會將用戶端使用者緩衝區中的資料儲存到磁碟裝置(但磁碟可能會將資料儲存在其快取中)。

也就是說:基本 FS 的需求是將所有資料儲存到磁碟硬體本身,預期資料會是耐用的。

前提條件

hasCapability(Stream, "hsync")
Stream.open else raise IOException

後置條件

FS' = FS where data(path) == buffer

實作必須封鎖,直到儲存體確認已寫入。

這是為了讓呼叫者可以確信,一旦呼叫成功傳回,資料就已寫入。

介面 StreamCapabilities

@InterfaceAudience.Public
@InterfaceStability.Evolving

org.apache.hadoop.fs.StreamCapabilities 介面存在,讓呼叫者可以動態地判斷串流的行為。

  public boolean hasCapability(String capability) {
    switch (capability.toLowerCase(Locale.ENGLISH)) {
      case StreamCapabilities.HSYNC:
      case StreamCapabilities.HFLUSH:
        return supportFlush;
      default:
        return false;
    }
  }

一旦串流已關閉,hasCapability() 呼叫必須執行下列其中一項

  • 傳回開啟串流的能力。
  • 傳回 false。

也就是說:它絕不可對檔案關閉提出例外情況;

請參閱 pathcapabilities,以取得 PathCapabilities API 的具體資訊;需求如下:串流絕不可對其缺乏支援的功能傳回 true,原因如下:

  • 功能不明。
  • 功能已知且已知不受支援。

標準串流功能定義在 StreamCapabilities 中;請參閱 javadocs 以取得完整選項集。

名稱 探測對下列功能的支援
dropbehind CanSetDropBehind.setDropBehind()
hsync Syncable.hsync()
hflush Syncable.hflush()。已棄用:僅探測 HSYNC
in:readahead CanSetReadahead.setReadahead()
in:unbuffer" CanUnbuffer.unbuffer()
in:readbytebuffer ByteBufferReadable#read(ByteBuffer)
in:preadbytebuffer ByteBufferPositionedReadable#read(long, ByteBuffer)

串流實作可能會新增自己的自訂選項。這些選項必須加上 fs.SCHEMA. 前綴,其中 SCHEMA 是檔案系統的架構。

介面 CanSetDropBehind

@InterfaceAudience.Public
@InterfaceStability.Evolving
public interface CanSetDropBehind {
  /**
   * Configure whether the stream should drop the cache.
   *
   * @param dropCache     Whether to drop the cache.  null means to use the
   *                      default value.
   * @throws IOException  If there was an error changing the dropBehind
   *                      setting.
   *         UnsupportedOperationException  If this stream doesn't support
   *                                        setting the drop-behind.
   */
  void setDropBehind(Boolean dropCache)
      throws IOException, UnsupportedOperationException;
}

此介面允許呼叫者變更 HDFS 內部使用的政策。

實作必須對呼叫傳回 true

StreamCapabilities.hasCapability("dropbehind");

串流輸出的耐用性、並行性、一致性和可見性。

這些是系統行為的面向,不在這個(非常簡化的)檔案系統模型的直接涵蓋範圍內,但可以在實際應用中看到。

耐用性

  1. OutputStream.write() 可能會同步或非同步地儲存資料
  2. OutputStream.flush() 會將資料沖刷到目的地。沒有嚴格的儲存需求。
  3. Syncable.hflush() 會同步將所有未完成的資料傳送到目的地檔案系統。資料傳回呼叫者後,資料必須對其他讀取者可見,資料可能耐用。也就是說:資料不一定要儲存,只要保證對所有嘗試在路徑開啟新串流讀取資料的用戶端一致可見即可。
  4. Syncable.hsync() 必須根據 hflush 傳輸資料,並將資料儲存在底層的耐用儲存空間中。
  5. close()close() 的第一次呼叫必須沖刷緩衝區中所有剩餘資料,並將資料儲存,作為對 hsync() 的呼叫。

許多應用程式呼叫 flush() 的頻率過高,例如在寫入每一行結尾時。如果這會觸發持久性儲存中的資料更新和任何附帶的元資料,分散式儲存將會快速過載。因此:flush() 通常最多只當作提示,將資料快取到網路緩衝區,但不會提交寫入任何資料。

只有 Syncable 介面提供保證。

兩個 Syncable 作業 hsync()hflush() 僅由 hsync() 的額外保證有所不同:資料必須持續存在。如果實作 hsync(),則可以透過呼叫 hsync() 來實作 hflush()

public void hflush() throws IOException {
  hsync();
}

這作為實作是完全可以接受的:hflush() 的語意已滿足。不可接受的是將 hsync() 降級為 hflush(),因為不再符合耐用性保證。

並行性

  1. 多個處理序寫入同一個檔案的結果是未定義的。

  2. 在檔案開啟寫入之前開啟輸入串流以讀取檔案,可能會擷取由寫入至輸出串流更新的資料。由於快取和緩衝,這不是必要條件,而且如果輸入串流確實擷取更新的資料,則讀取更新資料的點是未定義的。這會在物件儲存中浮現,其中關閉並重新開啟連線的 seek() 呼叫可能會擷取更新的資料,而向前串流讀取則不會。類似地,在區塊導向檔案系統中,資料一次可能會快取一個區塊,而且只有在讀取不同的區塊時才會擷取變更。

  3. 檔案系統可能會允許在串流寫入目的地路徑時操作該路徑,例如路徑或父項目的 rename();路徑或父項目的 delete()。在這種情況下,輸出串流上未來寫入作業的結果是未定義的。有些檔案系統可能會實作鎖定以防止衝突。然而,這在分散式檔案系統中往往很少見,原因在文獻中很有名。

  4. java.io.OutputStream 的 Java API 規範不要求類別的執行個體具有執行緒安全性。然而,org.apache.hadoop.hdfs.DFSOutputStream 具有更強的執行緒安全性模型(可能無意間)。Apache HBase 依賴這個事實,如在 HADOOP-11708 中發現的。實作應該具有執行緒安全性。注意:即使是 DFSOutputStream 同步模型也允許在 hsync() 作業中等待資料節點或名稱節點寫入的確認時,呼叫輸出串流的 close()

一致性和可見性

沒有要求資料立即對其他應用程式可見,直到呼叫快取緩衝區或將其持續存在於基礎儲存媒體時為止。

如果使用 FileSystem.create(path, overwrite==true) 建立輸出串流,且路徑中存在現有檔案,也就是 exists(FS, path) 成立,則現有資料將立即不可用;路徑結尾的資料必須包含一個空位元組序列 [],且具有相符的元資料。

exists(FS, path)
(Stream', FS') = create(FS, path)
exists(FS', path)
getFileStatus(FS', path).getLen() = 0

檔案的元資料(特別是 length(FS, path))應該與 flush()sync() 之後檔案的內容相符。

(Stream', FS') = create(FS, path)
(Stream'', FS'') = write(Stream', data)
(Stream''', FS''') hsync(Stream'')
exists(FS''', path)
getFileStatus(FS''', path).getLen() = len(data)

HDFS 僅在寫入跨越區塊邊界時執行此動作;否則會使 Namenode 過載。其他儲存裝置可能會複製此行為。

因此,在寫入檔案時,length(Filesystem, Path) 可能小於 data(Filesystem, Path) 的長度。

元資料必須與 close() 作業之後檔案的內容相符。

輸出串流的內容已儲存 (hflush()/hsync()) 之後,所有新的 open(FS, Path) 作業都必須傳回已更新的資料。

在輸出串流上呼叫 close() 之後,呼叫 getFileStatus(path) 必須傳回已寫入檔案的最終元資料,包括長度和修改時間。在任何 FileSystem list 作業中傳回的檔案元資料都必須與此元資料相符。

在寫入串流時,getFileStatus(path).getModificationTime() 的值未定義。在寫入檔案時,時間戳記可能會更新,特別是在 Syncable.hsync() 呼叫之後。在檔案關閉後,時間戳記必須更新為伺服器在 close() 呼叫期間觀察到的時鐘值。很可能是檔案系統的時間和時區,而不是用戶端的時間和時區。

正式來說,如果 close() 作業觸發與伺服器的互動,而該互動在伺服器端時間 t1 開始,並在時間 t2 完成,且檔案寫入成功,則最後的修改時間應該是時間 t,其中 t1 <= t <= t2

Hadoop 輸出串流模型的問題。

Hadoop 提供的輸出串流模型有一些已知問題,特別是關於資料寫入和儲存的時間保證,以及元資料同步的時間。這些問題出在 HDFS 和「本機」檔案系統的實作層面,不符合本規範中使用的檔案系統的簡單模型。

HDFS

HDFS: hsync() 僅同步最新區塊

參考實作 DFSOutputStream 會封鎖,直到從資料節點收到確認訊息:也就是說,複寫鏈中的所有主機都已成功寫入檔案。

這表示呼叫者預期方法呼叫的回傳值包含可見性和耐用性保證,而其他實作必須維持這些保證。

不過,請注意,參考 DFSOutputStream.hsync() 呼叫實際上只會保留目前區塊。如果自上次同步以來已有一系列寫入,以致於已跨越區塊邊界,則 hsync() 呼叫只宣稱會寫入最新區塊。

來自 DFSOutputStream.hsync(EnumSet<SyncFlag> syncFlags) 的 javadoc

請注意,只有目前區塊會快取到磁碟裝置。若要保證區塊邊界之間的耐用同步,串流應使用 {@link CreateFlag#SYNC_BLOCK} 建立。

這是 HDFS 實作中一個重要的細節,任何依賴 HDFS 提供寫入前記錄或其他資料庫結構的人員都不應忽略此細節,其中應用程式的需求是「所有前置位元組都必須在 WAL 中的提交旗標快取之前已保留」

請參閱 [Stonebraker81],Michael Stonebraker,資料庫管理作業系統支援,1981 年,以了解此主題的討論。

如果您確實需要 hsync() 同步非常大寫入中的每個區塊,請定期呼叫它。

HDFS: 元資料更新的延遲可見性。

HDFS 檔案元資料通常會落後於正在寫入的檔案內容,這並非所有人都預期,也不方便任何程式嘗試擷取正在寫入檔案中的更新資料。最明顯的是在各種 list 命令和 getFileStatus 中回傳的檔案長度,這通常會過時。

由於 HDFS 僅在其輸出作業中支援檔案成長,這表示元資料中列出的檔案大小可能小於或等於可用位元組數,但絕不會更大。這是一個保證,也適用於

判斷 HDFS 中的檔案是否已更新的一種演算法是

  1. 記住檔案中的最後讀取位置 pos,如果這是初始讀取,則使用 0
  2. 使用 getFileStatus(FS, Path) 查詢元資料中記錄的檔案更新長度。
  3. 如果 Status.length &gt; pos,表示檔案已成長。
  4. 如果數字沒有改變,則
    1. 重新開啟檔案。
    2. seek(pos) 到該位置
    3. 如果 read() != -1,則有新的資料。

此演算法適用於與 metadata 和資料一致的檔案系統,以及 HDFS。重要的是要知道,對於開啟的檔案 getFileStatus(FS, path).getLen() == 0 並不表示 data(FS, path) 為空。

當 HDFS 中的輸出串流關閉時;新寫入的資料不會立即寫入磁碟,除非 HDFS 部署時將 dfs.datanode.synconclose 設為 true。否則,它會被快取並稍後寫入磁碟。

本機檔案系統,file:

LocalFileSystemfile:(或任何其他基於 ChecksumFileSystemFileSystem 實作)有不同的問題。如果從 create() 取得輸出串流,且尚未在檔案系統上呼叫 FileSystem.setWriteChecksum(false),則串流只會快取可寫入完整 checksum 資料區塊的本地資料。

也就是說,hsync/hflush 作業不保證會寫入所有待處理資料,直到檔案最終關閉。

因此,透過 file:// URL 存取的本機檔案系統不支援 Syncable,除非在該 FileSystem 執行個體上呼叫 setWriteChecksum(false) 以停用 checksum 建立。在此之後,顯然不會為任何檔案產生 checksum。Is

Checksummed 輸出串流

由於 org.apache.hadoop.fs.FSOutputSummerorg.apache.hadoop.fs.ChecksumFileSystem.ChecksumFSOutputSummer 實作 HDFS 和其他檔案系統使用的基礎 checksummed 輸出串流,因此它提供了一些輸出串流行為的核心語意。

  1. close() 呼叫未同步,可重新進入,且可能會嘗試多次關閉串流。
  2. 可以在已關閉的串流上呼叫 write(int)(但不能呼叫 write(byte[], int, int))。
  3. 可以在已關閉的串流上呼叫 flush()

行為 1 和 2 確實必須被視為要修復的錯誤,儘管要小心處理。

行為 3 必須被視為事實上的標準,供其他實作複製。

物件儲存

物件儲存串流可能會快取整個串流的輸出,直到最後的 close() 作業觸發資料的單一 PUT 和最終輸出的實體化。

與 POSIX 檔案系統和本文檔中指定的行為相比,這顯著地改變了它們的行為。

新建立物件的可見性

無法保證在建立輸出串流後,任何檔案都會在輸出串流的路徑中可見。

也就是說:當 create(FS, path, boolean) 傳回新的串流

Stream' = (path, true, [])

作業的另一個後置條件 data(FS', path) == [] 可能不成立,這種情況下

  1. exists(FS, p) 可能會傳回 false。
  2. 如果檔案是用 overwrite = True 建立的,現有的資料可能仍然可見:data(FS', path) = data(FS, path)
  3. create() 呼叫中,使用 overwrite=False 檢查現有資料,可能會在 create() 呼叫本身、寫入之前或寫入期間的 close() 呼叫,或介於兩者之間的某個時間點進行。在物件儲存體支援原子 PUT 作業的特殊情況下,檢查現有資料的存在和隨後在路徑中建立資料包含競爭條件:其他用戶端可能在存在檢查和後續寫入之間,在路徑中建立資料。

  4. 呼叫 create(FS, Path, overwrite=false) 可能會成功,傳回新的 OutputStream,即使另一個串流已開啟並寫入至目標路徑。

這允許執行以下作業順序,如果針對 HDFS 呼叫,則會在第二次 open() 呼叫中引發例外狀況

Stream1 = open(FS, path, false)
sleep(200)
Stream2 = open(FS, path, false)
Stream.write('a')
Stream1.close()
Stream2.close()

對於想知道為何用戶端不建立 0 位元組檔案的任何人,在 create() 呼叫中,這會在 close() 之後造成問題,標記檔案可能會在 open() 呼叫中傳回,而不是最終資料。

close() 之後串流輸出的可見性

物件儲存體應該做出的其中一項保證,與 POSIX 檔案系統相同:在串流 close() 呼叫傳回後,資料必須持久化並對所有呼叫者可見。遺憾的是,即使是這項保證,也不總是能滿足

  1. 路徑上的現有資料可能會在一段不確定的時間內可見。

  2. 如果儲存體有任何形式的建立不一致性或負面存在探查的緩衝,那麼即使在串流的 close() 作業傳回後,getFileStatus(FS, path)open(FS, path) 仍可能因 FileNotFoundException 而失敗。

對它們有利的是,儲存體 PUT 作業的原子性確實提供了自己的保證:新建立的物件不存在,或其所有資料都存在:建立物件的動作,雖然可能會表現出建立不一致性,但卻是原子的。應用程式可能能夠利用這個事實來獲得優勢。

可中止介面揭露了在資料可見之前中止輸出串流的能力,因此可用於檢查點和類似作業。

實作者的注意事項。

永遠實作 Syncable - 即使只是要拋出 UnsupportedOperationException

因為 FSDataOutputStream 會將 Syncable.hflush()Syncable.hsync() 靜默降級為 wrappedStream.flush(),API 的呼叫者可能會誤以為在同步到不支援 API 的串流後,資料已經被快取/同步。

實作應該實作 API 但拋出 UnsupportedOperationException

StreamCapabilities

檔案系統用戶端的實作者應該實作 StreamCapabilities 介面及其 hasCapabilities() 方法,以宣告輸出串流是否提供 Syncable 的可見性和耐用性保證。

StreamCapabilities.hasCapabilities() 的實作者不得宣告他們在不正確的串流上支援 hflushhsync 功能。

有時串流會將其資料傳遞到儲存,但遠端可能不會將其同步到磁碟。這不是用戶端可以確定的。在此:如果用戶端程式碼將 hflush/hsync 傳遞這些要求給分散式檔案系統,則它應該宣告它支援它們。

元資料更新

實作者不得在每次 hsync() 呼叫後更新檔案的元資料(長度、日期、…)。HDFS 沒有這麼做,除非寫入的資料跨越區塊邊界。

close() 是否同步並保留資料?

預設情況下,當串流關閉時,HDFS 不會立即將資料儲存到磁碟;它會非同步儲存到磁碟。

這並不表示使用者不希望這樣做。

實作的行為類似於 NFS 的寫回面向的 快取DFSClient.close() 對用戶端執行 hflush() 以將所有資料上傳到資料節點。

  1. close() 應在符合 hflush() 保證後傳回:資料對其他人可見。
  2. 對於耐用性保證,必須先呼叫 hsync()