HDFS文件

一、操作方式

Hadoop支持的文件系統由很多(見下圖),HDFS只是其中一種實現。Java抽象類org.apache.hadoop.fs.FileSystem定義了Hadoop中一個文件系統的客戶端接口,并且該抽象類有幾個具體實現。Hadoop一般使用URI(下圖)方案來選取合適的文件系統實例進行交互。

比如想要列出本地文件系統根目錄下的文件,可以輸入命令 hadoop fs -ls file:///

Hadoop支持的文件系統

特別的,HDFS文件系統的操作可以使用 FsSystem shell 、客戶端(http rest api、Java api、C api等)。

FsSystem shell

FsSystem shell 的用法基本同本地shell類似,命令可參考 FsSystem shell

JAVA

Hadoop是用Java寫的,通過Java Api(FileSystem類)可以調用大部分Hadoop文件系統的交互操作。更詳細的介紹可參考 hadoop Filesystem

HTTP

非Java開發的應用可以使用由WebHDFS協議提供的HTTP REST API,但是HTTP比原生的Java客戶端要慢,所以不到萬不得已盡量不要使用HTTP傳輸特大數據。通過HTTP來訪問HDFS有兩種方法:

  • 直接訪問 HDFS守護進程直接服務于來自客戶端的HTTP請求
  • 通過代理訪問 客戶端通常使用DistributedFileSystem API訪問HDFS。

兩種如圖

Http訪問

在第一種情況中,namenode和datanode內嵌的web服務作為WebHDFS的端節點運行(是否啟用WebHDFS可通過dfs.webhdfs.enabled設置,默認為true)。文件元數據在namenode上,文件讀寫操作首先被發往namenode,有namenode發送一個HTTP重定向至某個客戶端,指示以流的方式傳輸文件數據的目的或源datanode。

第二種方法依靠一個或多個獨立代理服務器通過HTTP訪問HDFS。所有集群的網絡通信都需要通過代理,因此客戶端從來不直接訪問namenode或datanode。使用代理后可以使用更嚴格的防火墻策略和帶寬策略。

HttpFs代理提供和WebHDFS相同的HTTP接口,這樣客戶端能夠通過webhdfs URI訪問接口。HttpFS代理啟動獨立于namenode和datanode的守護進程,使用httpfs.sh 腳本,默認在一個不同的端口上監聽(14000)。

二、文件讀取

數據流解析

下圖描述了

讀文件時客戶端與 HDFS 中的 namenode, datanode 之間的數據流動。

客戶端從HDFS讀數據

對上圖的解釋如下:

  • 客戶端首先通過在 FileSystem 上調用 open() 方法打開它想要打開的文件, 對于 HDFS 來說, 就是在 DistributedFileSystem 的實例上調用(第1步)。
  • 之后 DistributedFileSystem 就使用 remote procedure call(RPCs)去呼叫 namenode,去查明組成文件的前幾個塊的位置(第2步)。對于每一個塊,namenode 返回擁有塊復本的 datanode 的地址。幸運的是,這些 datanode 會按照與客戶端的接近度來排序(接近度是按照集群網絡中的拓撲結構來計算的,后面會說到)。如果客戶端節點自己就是一個 datanode,而且該節點的存了一個塊的復本,客戶端就直接從本地 datanode 讀取塊。
  • DistributedFileSystem 返回一個 FSDataInputStream(支持文件 seek 的輸入流)給客戶端,客戶端就能從流中讀取數據了。 FSDataInputStream 中封裝了一個管理了 datanode 與 namenode I/O 的 DFSInputStream
  • 然后客戶端就調用 read() 方法(第3步)。
  • 存儲了文件的前幾個塊的地址的 DFSInputStream,就會連接存儲了第一個塊的第一個(最近的) datanode。 然后 DFSInputStream 就通過重復調用 read() 方法,數據就從 datanode 流動到了客戶端(第4步)。
  • 當 該 datanode 中最后一個塊的讀取完成了, DFSInputStream 會關閉與 datanode 的連接,然后為下一塊尋找最佳節點(第5步)。這個過程對客戶端來說是透明的,在客戶端那邊看來,就像是只讀取了一個連續不斷的流。
  • 塊是按順序讀的,通過 DFSInputStream 在 datanode 上打開新的連接去作為客戶端讀取的流。他也將會呼叫 namenode 來取得下一批所需要的塊所在的 datanode 的位置(注意剛才說的只是從 namenode 獲取前幾個塊的)。當客戶端完成了讀取,就在 FSDataInputStream 上調用 close() 方法結束整個流程(第6步)。

在讀取過程中, 如果 FSDataInputStream 在和一個 datanode 進行交流時出現了一個錯誤,他就去試一試下一個最接近的塊,他當然也會記住剛才發生錯誤的 datanode 以至于之后不會再在這個 datanode 上進行沒必要的嘗試。 DFSInputStream 也會在 datanode 上傳輸出的數據上核查檢查數(checknums).如果損壞的塊被發現了,DFSInputStream 就試圖從另一個擁有備份的 datanode 中去讀取備份塊中的數據。

在這個設計中一個重要的方面就是客戶端直接從 datanode 上檢索數據,并通過 namenode 指導來得到每一個塊的最佳 datanode。這種設計允許 HDFS 擴展大量的并發客戶端,因為數據傳輸只是集群上的所有 datanode 展開的。期間,namenode 僅僅只需要服務于獲取塊位置的請求(塊位置信息是存放在內存中,所以效率很高)。如果不這樣設計,隨著客戶端數據量的增長,數據服務就會很快成為一個瓶頸。

集群網絡拓撲

我們知道,相對于客戶端(之后就是 mapreduce task 了),塊的位置有以下可能性:

  • 在客戶端所在節點上(0,也就是本地化的)
  • 和客戶端不在同一個節點上,但在同一個機架上(2)。
  • 和客戶端不在同一個機架上,但是在同一個數據中心里(4)。
  • 就是與客戶端不在一個數據中心(6)。

我們認為他們對于客戶端的帶寬遞減,距離遞增(括號中表示距離)。示意圖如下:

網絡拓撲

如果集群中的機器都在同一個機架上,我們無需其他配置,若集群比較復雜,由于hadoop無法自動發現網絡拓撲,所以需要額外配置網絡拓撲。

讀取舉例(Java)

基本讀取程序,將文件內容輸出到console

FileSystemCat

// cc FileSystemCat Displays files from a Hadoop filesystem on standard output by using the FileSystem directly
import java.io.InputStream;
import java.net.URI;
 
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
 
// vv FileSystemCat 
public class FileSystemCat {
 
  public static void main(String[] args) throws Exception {
    String uri = args[0];
    Configuration conf = new Configuration();
    FileSystem fs = FileSystem.get(URI.create(uri), conf);
    InputStream in = null;
    try {
      in = fs.open(new Path(uri));
      IOUtils.copyBytes(in, System.out, 4096, false);
    } finally {
      IOUtils.closeStream(in);
    }
  }
}
// ^^ FileSystemCat

隨機讀取

// cc FileSystemDoubleCat Displays files from a Hadoop filesystem on standard output twice, by using seek
import java.net.URI;
 
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
 
// vv FileSystemDoubleCat
public class FileSystemDoubleCat {
 
  public static void main(String[] args) throws Exception {
    String uri = args[0];
    Configuration conf = new Configuration();
    FileSystem fs = FileSystem.get(URI.create(uri), conf);
    FSDataInputStream in = null;
    try {
      in = fs.open(new Path(uri));
      IOUtils.copyBytes(in, System.out, 4096, false);
      in.seek(0); // go back to the start of the file
      IOUtils.copyBytes(in, System.out, 4096, false);
    } finally {
      IOUtils.closeStream(in);
    }
  }
}
// ^^ FileSystemDoubleCat

展開原碼

三、文件寫入

數據流解析

下圖描述了寫文件時客戶端與 HDFS 中的 namenode, datanode 之間的數據流動。

寫文件

對上圖的解釋如下:

  • 首先客戶端通過在 DistributedFileSystem 上調用 create()方法(第1步)來創建一個文件。

  • DistributedFileSystem 使用 RPC 呼叫 namenode ,讓他在文件系統的命名空間上創建一個沒有與任何塊關聯的新文件(第2步), namenode 會執行各種各樣的檢查以確認文件之前是不存在的,并確認客戶端是否擁有創建文件的權限。如果檢查通過。 namenode 就會為新文件生成一條記錄;否則,文件創建就會失敗,客戶端會拋出一個 IOException。 成功以后,DistributedFileSystem 會返回一個 FSDataOutputStream給客戶端以讓他開始寫數據。和讀流程中一樣,FSDataOutputStream 包裝了一個 DFSOutputStream,他掌握了與 datanode 與 namenode 的聯系。

  • 當客戶端開始寫數據(第3步),DFSOutputStream 將文件分割成很多很小的數據,然后將每個小塊放進一個個包(數據包,包中除了數據還有描述數據用的標識)中, 包們會寫進一個名為數據隊列(data quence)的內部隊列。數據隊列被 DataStreamr 消費,他負責要求 namenode 去挑選出適合存儲塊備份的 datanode 的一個列表(注意,這里是文件的一個塊,而不是整個文件)。這個列表會構成一個 pipeline(管線),這里假定備份數為3,所以在 pipeline 中就會有三個 datanode , DataStreamer 將能夠組成塊的的包先流入 pipeline 中的第一個 datanode ,第一個 datanode 會先存儲來到的包,然后繼續將所有的包轉交到 pipeline 中的第二個 datanode 中。相似的,第二個 datande 也會存儲這些包,并將他們轉交給 pipeline 中的第三個(最后一個) datanode (第4步)。

  • DFSOutputStream 也會維護一個包們的內部隊列,其中也會有所有的數據包,該隊列等待 datanode們 的寫入確認,所以叫做確認隊列(ack quence)。當一個包已經被 pipeline 中的所有 datanode 確認了寫如磁盤成功,這個包才會從 確認隊列中移除(第5步)。

  • 當客戶端完成了數據寫入,會在流上調用 close() 方法(第6步),這個行為會將所有剩下的包刷新(flush)進 datanode 中,然后等待確認信息達到后,客戶端就聯系 namenode 告訴他文件數據已經放好了(第七步)。namenode 也一直知道文件被分成了哪些塊(因為在之前是 DataStreamer 請求了塊分配),所以現在在成功之前,只需要等待塊滿足最低限度的備份。

如果在任何一個 datanode 在寫入數據的時候失敗了,接下來所做的一切對客戶端都是透明的:首先, pipeline 被關閉,在確認隊列中的剩下的包會被添加進數據隊列的起始位置上,以至于在失敗的節點下游的任 何節點都不會丟失任何的包。然后與 namenode 聯系后,當前在一個好的 datanode 會聯系 namenode, 給失敗節點上還未寫完的塊生成一個新的標識ID, 以至于如果這個失敗的 datanode 不久后恢復了,這個不完整的塊將會被刪除。失敗節點會從 pipeline 中移除,然后剩下兩個好的 datanode 會組成一個的新的 pipeline ,剩下的 這些塊的包(也就是剛才放在數據隊列隊首的包)會繼續寫進 pipeline中好的 datanode 中。最后,namenode 注意到塊備份數小于規定的備份數,他就安排在另一個節點上創建完成備份,直接從已有的塊中復制就可以。然后一直到滿足了備份數(dfs.replication)。如果有多個節點的寫入失敗了,如果滿足了最小備份數的設置(dfs.namenode.repliction.min),寫入也將會成功,然后剩下的備份會被集群異步的執行備份,直到滿足了備份數(dfs.replication)。

寫入舉例(Java)

// cc FileCopyWithProgress Copies a local file to a Hadoop filesystem, and shows progress
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
 
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.util.Progressable;
 
// vv FileCopyWithProgress
public class FileCopyWithProgress {
  public static void main(String[] args) throws Exception {
    String localSrc = args[0];
    String dst = args[1];
     
    InputStream in = new BufferedInputStream(new FileInputStream(localSrc));
     
    Configuration conf = new Configuration();
    FileSystem fs = FileSystem.get(URI.create(dst), conf);
    OutputStream out = fs.create(new Path(dst), new Progressable() {
      public void progress() {
        System.out.print(".");
      }
    });
     
    IOUtils.copyBytes(in, out, 4096, true);
  }
}
// ^^ FileCopyWithProgress

四、其他文件操作

創建目錄

創建目錄

public blooean mkdir(Path f) throws IOException

查看文件元數據

// cc ShowFileStatusTest Demonstrates file status information
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.lessThanOrEqualTo;
 
import java.io.*;
 
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.*;
import org.apache.hadoop.hdfs.MiniDFSCluster;
import org.junit.*;
 
// vv ShowFileStatusTest
public class ShowFileStatusTest {
   
  private MiniDFSCluster cluster; // use an in-process HDFS cluster for testing
  private FileSystem fs;
 
  @Before
  public void setUp() throws IOException {
    Configuration conf = new Configuration();
    if (System.getProperty("test.build.data") == null) {
      System.setProperty("test.build.data", "/tmp");
    }
    cluster = new MiniDFSCluster.Builder(conf).build();
    fs = cluster.getFileSystem();
    OutputStream out = fs.create(new Path("/dir/file"));
    out.write("content".getBytes("UTF-8"));
    out.close();
  }
   
  @After
  public void tearDown() throws IOException {
    if (fs != null) { fs.close(); }
    if (cluster != null) { cluster.shutdown(); }
  }
   
  @Test(expected = FileNotFoundException.class)
  public void throwsFileNotFoundForNonExistentFile() throws IOException {
    fs.getFileStatus(new Path("no-such-file"));
  }
   
  @Test
  public void fileStatusForFile() throws IOException {
    Path file = new Path("/dir/file");
    FileStatus stat = fs.getFileStatus(file);
    assertThat(stat.getPath().toUri().getPath(), is("/dir/file"));
    assertThat(stat.isDirectory(), is(false));
    assertThat(stat.getLen(), is(7L));
    assertThat(stat.getModificationTime(),
        is(lessThanOrEqualTo(System.currentTimeMillis())));
    assertThat(stat.getReplication(), is((short) 1));
    assertThat(stat.getBlockSize(), is(128 * 1024 * 1024L));
    assertThat(stat.getOwner(), is(System.getProperty("user.name")));
    assertThat(stat.getGroup(), is("supergroup"));
    assertThat(stat.getPermission().toString(), is("rw-r--r--"));
  }
   
  @Test
  public void fileStatusForDirectory() throws IOException {
    Path dir = new Path("/dir");
    FileStatus stat = fs.getFileStatus(dir);
    assertThat(stat.getPath().toUri().getPath(), is("/dir"));
    assertThat(stat.isDirectory(), is(true));
    assertThat(stat.getLen(), is(0L));
    assertThat(stat.getModificationTime(),
        is(lessThanOrEqualTo(System.currentTimeMillis())));
    assertThat(stat.getReplication(), is((short) 0));
    assertThat(stat.getBlockSize(), is(0L));
    assertThat(stat.getOwner(), is(System.getProperty("user.name")));
    assertThat(stat.getGroup(), is("supergroup"));
    assertThat(stat.getPermission().toString(), is("rwxr-xr-x"));
  }
   
}
// ^^ ShowFileStatusTest

查看文件狀態

// cc ListStatus Shows the file statuses for a collection of paths in a Hadoop filesystem
import java.net.URI;
 
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.FileUtil;
import org.apache.hadoop.fs.Path;
 
// vv ListStatus
public class ListStatus {
 
  public static void main(String[] args) throws Exception {
    String uri = args[0];
    Configuration conf = new Configuration();
    FileSystem fs = FileSystem.get(URI.create(uri), conf);
     
    Path[] paths = new Path[args.length];
    for (int i = 0; i < paths.length; i++) {
      paths[i] = new Path(args[i]);
    }
     
    FileStatus[] status = fs.listStatus(paths);
    Path[] listedPaths = FileUtil.stat2Paths(status);
    for (Path p : listedPaths) {
      System.out.println(p);
    }
  }
}
// ^^ ListStatus

文件模式匹配

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
 
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.PathFilter;
 
public class DateRangePathFilter implements PathFilter {
   
  private final Pattern PATTERN = Pattern.compile("^.*/(\\d\\d\\d\\d/\\d\\d/\\d\\d).*$");
   
  private final Date start, end;
 
  public DateRangePathFilter(Date start, Date end) {
    this.start = new Date(start.getTime());
    this.end = new Date(end.getTime());
  }
   
  public boolean accept(Path path) {
    Matcher matcher = PATTERN.matcher(path.toString());
    if (matcher.matches()) {
      DateFormat format = new SimpleDateFormat("yyyy/MM/dd");
      try {
        return inInterval(format.parse(matcher.group(1)));
      } catch (ParseException e) {
        return false;
      }
    }
    return false;
  }
 
  private boolean inInterval(Date date) {
    return !date.before(start) && !date.after(end);
  }
 
}

文件刪除

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
 
import java.io.IOException;
 
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.junit.Before;
import org.junit.Test;
 
public class FileSystemDeleteTest {
 
  private FileSystem fs;
   
  @Before
  public void setUp() throws Exception {
    fs = FileSystem.get(new Configuration());
    writeFile(fs, new Path("dir/file"));
  }
   
  private void writeFile(FileSystem fileSys, Path name) throws IOException {
    FSDataOutputStream stm = fileSys.create(name);
    stm.close();
  }
   
  @Test
  public void deleteFile() throws Exception {
    assertThat(fs.delete(new Path("dir/file"), false), is(true));
    assertThat(fs.exists(new Path("dir/file")), is(false));
    assertThat(fs.exists(new Path("dir")), is(true));
    assertThat(fs.delete(new Path("dir"), false), is(true));
    assertThat(fs.exists(new Path("dir")), is(false));
  }
 
  @Test
  public void deleteNonEmptyDirectoryNonRecursivelyFails() throws Exception {
    try {
      fs.delete(new Path("dir"), false);
      fail("Shouldn't delete non-empty directory");
    } catch (IOException e) {
      // expected
    }
  }
   
  @Test
  public void deleteDirectory() throws Exception {
    assertThat(fs.delete(new Path("dir"), true), is(true));
    assertThat(fs.exists(new Path("dir")), is(false));
  }
   
}

五、文件壓縮

壓縮格式

文件壓縮有兩大好處:

  • 減少存儲文件所需要的磁盤空間
  • 加速數據在網絡和磁盤上的傳輸。

Hadoop 對于壓縮格式的是自動識別。如果我們壓縮的文件有相應壓縮格式的擴展名(比如 lzo,gz,bzip2 等)。Hadoop 會根據壓縮格式的擴展名自動選擇相對應的解碼器來解壓數據,此過程完全是 Hadoop 自動處理,我們只需要確保輸入的壓縮文件有擴展名。

Hadoop中有多種壓縮格式、算法和工具,下圖列出了常用的壓縮方法。

壓縮格式

表中的“是否可切分”表示對應的壓縮算法是否支持切分,也就是說是否可以搜索數據流的任意位置并進一步往下讀取數據,可切分的壓縮格式尤其適合MapReduce。

比較

所有的壓縮算法都需要權衡空間/時間:壓縮和解壓縮速度更快,其代價通常是只能節省少量的空間。不同的壓縮工具有不同的特性:

  • gzip是一個通用的壓縮工具,在空間/時間性能權衡中,居于其他兩個壓縮方法之間。
  • bzip2的壓縮能力強于gzip,但壓縮速度更慢一些,盡管bzip2的解壓速度比壓縮速度快,但仍比其他壓縮格式慢。
  • LZO、LZ4和Snappy均優化壓縮速度,其速度比gzip快一個數量級,但壓縮效率低一點。
  • Snappy和LZ4的壓縮速度比LZO高出很多。

更詳細的比較如下

1.壓縮性能比較


性能

2.優缺點

優缺點

另外使用hadoop原生(native)類庫比其他java實現有更快的壓縮和解壓縮速度。特征比較如下:

特征

使用容器文件格式結合壓縮算法也能更好的提高效率。順序文件、Arvo文件、ORCFiles、Parqurt文件同時支持壓縮和切分。

壓縮舉例(Java)

壓縮

// cc PooledStreamCompressor A program to compress data read from standard input and write it to standard output using a pooled compressor
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.compress.*;
import org.apache.hadoop.util.ReflectionUtils;
 
// vv PooledStreamCompressor
public class PooledStreamCompressor {
 
  public static void main(String[] args) throws Exception {
    String codecClassname = args[0];
    Class<?> codecClass = Class.forName(codecClassname);
    Configuration conf = new Configuration();
    CompressionCodec codec = (CompressionCodec)
      ReflectionUtils.newInstance(codecClass, conf);
    /*[*/Compressor compressor = null;
    try {
      compressor = CodecPool.getCompressor(codec);/*]*/
      CompressionOutputStream out =
        codec.createOutputStream(System.out, /*[*/compressor/*]*/);
      IOUtils.copyBytes(System.in, out, 4096, false);
      out.finish();
    /*[*/} finally {
      CodecPool.returnCompressor(compressor);
    }/*]*/
  }
}
// ^^ PooledStreamCompressor

解壓縮

// cc FileDecompressor A program to decompress a compressed file using a codec inferred from the file's extension
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
 
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.compress.CompressionCodec;
import org.apache.hadoop.io.compress.CompressionCodecFactory;
 
// vv FileDecompressor
public class FileDecompressor {
 
  public static void main(String[] args) throws Exception {
    String uri = args[0];
    Configuration conf = new Configuration();
    FileSystem fs = FileSystem.get(URI.create(uri), conf);
     
    Path inputPath = new Path(uri);
    CompressionCodecFactory factory = new CompressionCodecFactory(conf);
    CompressionCodec codec = factory.getCodec(inputPath);
    if (codec == null) {
      System.err.println("No codec found for " + uri);
      System.exit(1);
    }
 
    String outputUri =
      CompressionCodecFactory.removeSuffix(uri, codec.getDefaultExtension());
 
    InputStream in = null;
    OutputStream out = null;
    try {
      in = codec.createInputStream(fs.open(inputPath));
      out = fs.create(new Path(outputUri));
      IOUtils.copyBytes(in, out, conf);
    } finally {
      IOUtils.closeStream(in);
      IOUtils.closeStream(out);
    }
  }
}
// ^^ FileDecompressor

六、文件序列化

序列化是指將結構化數據轉換為字節流以便在網絡上傳輸或寫到磁盤進行永久存儲。反序列化獅子將字節流轉換回結構化對象的逆過程。

序列化用于分布式數據處理的兩大領域:進程間通信和永久存儲。

對序列化的要求時是格式緊湊(高效使用存儲空間)、快速(讀寫效率高)、可擴展(可以透明地讀取老格式數據)且可以互操作(可以使用不同的語言讀寫數據)。

Hadoop使用的是自己的序列化格式Writable,它絕對緊湊、速度快,但不太容易用java以外的語言進行擴展或使用。

當然,用戶也可以使用其他序列化框架或者自定義序列化方式,如Avro框架。

Hadoop內部還使用了Apache ThriftProtocal Buffers 來實現RPC和數據交換。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,578評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,701評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,691評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,974評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,694評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,026評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,015評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,193評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,719評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,442評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,668評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,151評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,846評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,255評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,592評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,394評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容