HDFS 短路读取:mmap 与 Unix Domain Socket 铸就的零拷贝艺术

在大数据时代,每一次数据读取都像一场隐形的马拉松。Spark 扫描百亿行 Parquet 文件、HBase 承受百万 QPS 查询、Hive 执行复杂聚合时,底层 I/O 的每一毫秒延迟、每一次 CPU 拷贝,都会放大为整个系统的性能税。

HDFS 的短路本地读取(Short-Circuit Local Read)将传统 HDFS 路径中 3 次 CPU 拷贝(DataNode 读文件①、send 进内核 TCP②、Client recv 出内核③)缩减至仅 1 次(FileChannel.read:page cache → Client JVM 堆),甚至借助 mmap 映射彻底消除 CPU 拷贝,实现真正意义上的零拷贝。

这项技术的灵魂,是操作系统两大经典机制的优雅共舞:mmap(内存映射)Unix Domain Socket(域套接字)。前者让文件直接映射进进程地址空间,后者则用文件描述符传递完成安全、高效的权限交接。HDFS 没有发明新轮子,而是把内核的智慧发挥到了极致。

本文将从操作系统原理出发,逐步深入 HDFS 短路读取的完整机制,再结合 Hadoop 3.x 主线源码进行逐层拆解,最后给出生产调优与故障排查实战。希望读完之后,你不仅能理解"为什么快",更能体会"为什么这么设计"——这才是分布式系统最迷人的地方。


一、mmap:把"搬书"变成"开窗"

在这里插入图片描述
传统 read()/write() 的本质,是反复的"数据搬家"。磁盘数据先通过 DMA 进入内核 page cache(0 次 CPU 拷贝),再被 read() 系统调用的 copy_to_user() 从 page cache 拷贝到用户态缓冲区(1 次 CPU 拷贝)。写回时又要从用户态拷贝回内核。每次搬家都伴随着用户态与内核态的切换、上下文保护、TLB 刷新,CPU 开销惊人。

mmap 的天才之处在于:它干脆取消了搬家。

当进程调用 mmap() 时,内核只在进程的页表中建立一个虚拟地址到文件偏移的映射关系,并不立即加载任何数据。直到程序真正访问某个虚拟地址,才触发缺页中断(page fault)。内核捕获中断后,按需将对应页面从磁盘加载到 page cache,并更新页表。此后,用户态指针与内核 page cache 共享同一块物理内存——程序通过指针直接读写 page cache 页面,无需任何额外的 read()/write() 系统调用和内存拷贝,这是 mmap 实现真正零拷贝的本质。

写操作直接修改 page cache 中的页面,内核后台通过 flusher 线程(writeback 机制)异步回写。整个过程,程序员只需像操作普通数组一样用指针读写,操作系统默默接管了分页、换出、脏页跟踪、预读等全部复杂工作。

用一个经典比喻:传统读取像每次借书都要管理员从书库取书、复印、再还回去;而 mmap 则是图书馆直接在你书桌上划一块区域,把整本书的位置告诉你。你想看哪页,管理员才把那一页悄然放上桌面。你所有的读写,都发生在桌面(page cache)上,无需反复复印。

在 HDFS 中,mmap 正是让客户端进程把 DataNode 上的块文件(blk_xxx)直接映射进自己虚拟地址空间的核心武器。后续所有读取,都变成了对 page cache 的直接指针访问——这正是零拷贝的本质。

CPU 拷贝次数速查表

场景 CPU 拷贝 路径
普通 read() 读本地文件 1 page cache → 用户缓冲区(copy_to_user
mmap 映射读 0 页表直指 page cache,指针直接访问
传统 HDFS 读取(同机 DataNode TCP 中转) 3 read() → DN JVM 堆
send() → 内核 TCP send buffer
recv() → Client JVM 堆
短路读取 + FileChannel.read() 1 page cache → Client JVM 堆(绕过 DN 和网络)
短路读取 + MappedByteBuffer 0 page cache 直接映射到 Client 虚拟地址空间

二、Unix Domain Socket:同一主机最优雅的通信礼仪

跨进程通信时,即使在同一台机器上,TCP 也要走完整的协议栈:三次握手、序列号、窗口管理、校验和、拥塞控制。即使是 localhost,延迟和 CPU 开销也不容忽视。而 Unix Domain SocketAF_UNIX)专为同一主机设计,完全绕过 TCP/IP 协议栈。

它有两种地址形式:

  • 基于文件系统的路径(如 /var/lib/hadoop-hdfs/dn_socket),权限由文件系统天然控制。Hadoop 3.x 中 dfs.domain.socket.path 默认为空字符串(即不启用),生产环境需显式配置。
  • Linux 特有的抽象命名空间(以 \0 开头),无需实际文件,更安全。

最关键的是,Unix Domain Socket 原生支持 SCM_RIGHTS ancillary message 机制——文件描述符传递。发送方通过 sendmsg() 把一个已打开的 fd 嵌入控制消息,内核会在接收方进程的文件描述符表中复制一个新 fd,指向同一个底层文件对象(struct file)。即使发送方关闭原 fd,接收方仍可继续使用。

这正是 HDFS 短路读取的控制通道:客户端无法直接打开 DataNode 的块文件(权限受限),于是通过 domain socket 发起协商,DataNode 验证通过后,把 fd 优雅地递给客户端。内核完成授权,双方共享同一物理页面。
在这里插入图片描述

三、传统 HDFS 读取路径:三次 CPU 拷贝的马拉松

在未启用短路读取时,一次 HDFS 块读取要经历以下旅程:
在这里插入图片描述

什么是"CPU 拷贝"?每次 read()write()send() 这类系统调用,内核需要把数据从一块内存搬运到另一块内存,这一步消耗 CPU 周期。而磁盘 → page cache 是 DMA(直接内存访问),由磁盘控制器硬件完成,不消耗 CPU。以下所有数字均排除 DMA 步骤。

可以看到,传统 HDFS 读取即便 DataNode 与 Client 在同一台机器上,也要经过 TCP 协议栈的 send/recv 中转——一次本地读被拆成了三次 CPU 拷贝,每次还伴随用户态/内核态切换。这就是为什么早期 Hadoop 在本地扫描时 CPU 利用率高企、GC 频繁。


四、短路本地读取:零拷贝的巅峰之作

当满足以下条件时,HDFS 切换到短路读取路径:

  • dfs.client.read.shortcircuit = true(默认 false
  • 客户端与 DataNode 共置同一节点
  • native 库(libhadoop.so)可用(Domain Socket 依赖 native 层)
  • dfs.domain.socket.path 已正确配置(默认空字符串意为禁用)

其中,dfs.client.use.legacy.blockreader.local(默认 false)不是开关,而是选择短路读实现方式:

  • false(默认):使用新版 BlockReaderLocal,依赖 Domain Socket 的 SCM_RIGHTS fd 传递 + 共享内存
  • true:使用旧版 BlockReaderLocalLegacy,通过 DataNode 提供的块文件路径直接打开(不需要 Domain Socket)

两种实现都是短路读取,只是底层机制不同。在这里插入图片描述
完整流程分为四步:

  1. 客户端通过 Unix domain socket 向 DataNode 发送短路请求——Protobuf 编码的 OpRequestShortCircuitAccessProto,携带块 ID(BaseHeaderProto.block)、客户端 token(BaseHeaderProto.token)、最大支持版本号(maxVersion)、可选的共享内存槽位 ID(slotId)以及回执验证标志(supportsReceiptVerification)。偏移量和长度不在此请求中,它们由后续 BlockReaderLocal.read() 调用按需指定。

  2. DataNode 验证 token 通过后,在本侧打开块文件,使用 sendmsg() + SCM_RIGHTS 将块文件 fd(data + meta 共两个)传递给客户端。

  3. 客户端 JNI 层(DomainSocket.recvFileInputStreams())接收 fd,封装为 FileInputStream,传入 ShortCircuitReplica 构造函数。ShortCircuitReplica 内部可选调用 FileChannel.map() 建立 mmap 映射。

  4. BlockReaderLocalShortCircuitReplica 提取 FileChannel,后续读取通过 FileChannel.read() 直接操作本地文件描述符——数据流完全绕过网络协议栈,只剩一次从 page cache 到用户缓冲区的拷贝。

此时,传统 HDFS 路径中 3 次 CPU 拷贝被缩短为 1 次(FileChannel.read():page cache → Client JVM 堆)。短路读的价值在于省去了 DataNode 进程和 TCP 协议栈的两次额外 CPU 拷贝。

更进一步,如果读取路径跳过了校验和验证(createNoChecksumContext() 返回 true),BlockReaderLocalreadWithoutBounceBuffer() 分支直接接收数据,避免中间 Bounce Buffer 的额外拷贝;若使用 ShortCircuitReplica.loadMmapInternal() 将块文件映射为 MappedByteBuffer,则 page cache 与用户地址空间共享同一物理页,CPU 拷贝降为 0 次。


五、源码解读:从配置决策到文件描述符传递的内核之旅

HDFS 短路读取的源码实现,分布在 hadoop-hdfs-clienthadoop-commonhadoop-hdfs 三个模块,核心类超过十个。下面按执行流程逐层拆解 Hadoop 3.x 主线的关键代码逻辑。

5.1 客户端入口:DFSInputStreamBlockReaderFactory 的决策智慧

读取起点在 org.apache.hadoop.hdfs.DFSInputStream

// DFSInputStream.java
protected BlockReader getBlockReader(LocatedBlock targetBlock,
    long offsetInBlock, long length, InetSocketAddress targetAddr,
    StorageType storageType, DatanodeInfo datanode) throws IOException {
  ExtendedBlock blk = targetBlock.getBlock();
  Token<BlockTokenIdentifier> accessToken = targetBlock.getBlockToken();
  CachingStrategy curCachingStrategy;
  boolean shortCircuitForbidden;
  synchronized (infoLock) {
    curCachingStrategy = cachingStrategy;
    shortCircuitForbidden = shortCircuitForbidden();
  }
  return new BlockReaderFactory(dfsClient.getConf()).
      setInetSocketAddress(targetAddr).
      setRemotePeerFactory(dfsClient).
      setDatanodeInfo(datanode).
      setStorageType(storageType).
      setFileName(src).
      setBlock(blk).
      setBlockToken(accessToken).
      setStartOffset(offsetInBlock).
      setVerifyChecksum(verifyChecksum).
      setClientName(dfsClient.clientName).
      setLength(length).
      setCachingStrategy(curCachingStrategy).
      setAllowShortCircuitLocalReads(!shortCircuitForbidden).
      setClientCacheContext(dfsClient.getClientContext()).
      setUserGroupInformation(dfsClient.ugi).
      setConfiguration(dfsClient.getConfiguration()).
      build();  // 进入决策链路
}

真正决定走哪条路径的是 BlockReaderFactory.build(),Hadoop 3.x 实现了 四级降级链路

// BlockReaderFactory.java
public BlockReader build() throws IOException {
  Preconditions.checkNotNull(configuration);
  Preconditions.checkState(length >= 0,
      "Length must be set to a non-negative value");

  // 第一级:外部块读取器(如 HDFS 缓存零拷贝访问器)
  BlockReader reader = tryToCreateExternalBlockReader();
  if (reader != null) {
    return reader;
  }

  final ShortCircuitConf scConf = conf.getShortCircuitConf();
  try {
    if (scConf.isShortCircuitLocalReads() && allowShortCircuitLocalReads) {
      if (clientContext.getUseLegacyBlockReaderLocal()) {
        // 第二级:旧版短路读取(BlockReaderLocalLegacy)
        reader = getLegacyBlockReaderLocal();
        if (reader != null) return reader;
      } else {
        // 第三级:新版短路读取(共享内存 + fd 传递)
        reader = getBlockReaderLocal();
        if (reader != null) return reader;
      }
    }
    if (scConf.isDomainSocketDataTraffic()) {
      // 第四级:Domain Socket 数据流(无短路,但 socket 可用)
      reader = getRemoteBlockReaderFromDomain();
      if (reader != null) return reader;
    }
  } catch (IOException e) {
    LOG.debug("Block read failed. Getting remote block reader using TCP", e);
  }
  // 最终兜底:TCP 远程读取
  return getRemoteBlockReaderFromTcp();
}

降级链(Legacy 与 New 为互斥分支):
在这里插入图片描述
这里体现了 HDFS 的设计哲学:配置驱动 + 优雅降级。即使 short-circuit 配置打开,如果 native 库不可用或 domain socket 路径缺失,也会安全回退,不会导致任务失败。

5.2 控制通道建立:DomainSocket 的连接与请求发送

短路路径的核心控制通道由 DomainSocket 负责:

// DomainSocket.java
public static DomainSocket connect(String path) throws IOException {
  if (loadingFailureReason != null) {
    throw new UnsupportedOperationException(loadingFailureReason);
  }
  int fd = connect0(path);       // 调用 native connect0()
  return new DomainSocket(path, fd);
}

连接成功后,客户端通过 DomainSocket.getOutputStream() 发送短路请求。Hadoop 3.x 使用 Protobuf 编码的 OpRequestShortCircuitAccessProto,携带块 ID、token 和 slot ID。

5.3 DataNode 侧处理:DataXceiverShortCircuitRegistry 的协作

DataNode 的短路请求入口是 DataXceiver.requestShortCircuitFds()

// DataXceiver.java
public void requestShortCircuitFds(final ExtendedBlock blk,
    final Token<BlockTokenIdentifier> token,
    SlotId slotId, int maxVersion, boolean supportsReceiptVerification)
      throws IOException {
  updateCurrentThreadName("Passing file descriptors for block " + blk);
  DataOutputStream out = getBufferedOutputStream();

  // 1. 权限校验
  checkAccess(out, true, blk, token,
      Op.REQUEST_SHORT_CIRCUIT_FDS, BlockTokenIdentifier.AccessMode.READ,
      null, null);

  BlockOpResponseProto.Builder bld = BlockOpResponseProto.newBuilder();
  FileInputStream fis[] = null;
  SlotId registeredSlotId = null;
  boolean success = false;
  try {
    try {
      if (peer.getDomainSocket() == null) {
        throw new IOException("You cannot pass file descriptors over " +
            "anything but a UNIX domain socket.");
      }

      // 2. 在共享内存中注册 slot(绑定块 ID 到 slot 位置)
      if (slotId != null) {
        boolean isCached = datanode.data.
            isCached(blk.getBlockPoolId(), blk.getBlockId());
        datanode.shortCircuitRegistry.registerSlot(
            ExtendedBlockId.fromExtendedBlock(blk), slotId, isCached);
        registeredSlotId = slotId;
      }

      // 3. 打开块文件(返回 FileInputStream 数组:data + meta)
      fis = datanode.requestShortCircuitFdsForRead(blk, token, maxVersion);
      Preconditions.checkState(fis != null);

      bld.setStatus(SUCCESS);
      bld.setShortCircuitAccessVersion(
          DataNode.CURRENT_BLOCK_FORMAT_VERSION);
    } catch (ShortCircuitFdsVersionException e) {
      bld.setStatus(ERROR_UNSUPPORTED);
      bld.setShortCircuitAccessVersion(
          DataNode.CURRENT_BLOCK_FORMAT_VERSION);
      bld.setMessage(e.getMessage());
    } catch (ShortCircuitFdsUnsupportedException e) {
      bld.setStatus(ERROR_UNSUPPORTED);
      bld.setMessage(e.getMessage());
    } catch (IOException e) {
      bld.setStatus(ERROR);
      bld.setMessage(e.getMessage());
    }

    bld.build().writeDelimitedTo(socketOut);

    // 4. 通过 SCM_RIGHTS 传递文件描述符
    if (fis != null) {
      FileDescriptor fds[] = new FileDescriptor[fis.length];
      for (int i = 0; i < fds.length; i++) {
        fds[i] = fis[i].getFD();
      }
      byte buf[] = new byte[1];
      buf[0] = (byte)(supportsReceiptVerification
          ? USE_RECEIPT_VERIFICATION.getNumber()
          : DO_NOT_USE_RECEIPT_VERIFICATION.getNumber());

      DomainSocket sock = peer.getDomainSocket();
      sock.sendFileDescriptors(fds, buf, 0, buf.length); // 内核级 fd 传递
      success = true;
    }
  } finally {
    // 失败则清理已注册的 slot
    if ((!success) && (registeredSlotId != null)) {
      datanode.shortCircuitRegistry.unregisterSlot(registeredSlotId);
    }
    // 关闭块文件流
    if (fis != null) {
      IOUtils.cleanup(null, fis);
    }
  }
}

ShortCircuitRegistry 负责共享内存段管理,DataNode.requestShortCircuitFdsForRead() 负责打开块文件,fd 的传递最终由 sock.sendFileDescriptors() 完成——三者各司其职,协作紧密。

5.4 文件描述符传递的内核之旅:SCM_RIGHTS 机制详解

真正执行 fd 传递的是 DomainSocket.c

发送侧sendFileDescriptors0 JNI):

// DomainSocket.c — 精简核心逻辑
struct cmsghdr_with_fds {
  struct cmsghdr hdr;
  int fds[MAX_PASSED_FDS];
} __attribute__((packed, aligned(8)));

Java_org_apache_hadoop_net_unix_DomainSocket_sendFileDescriptors0(
  JNIEnv *env, jclass clazz, jint fd, jobject jfds, jobject jbuf,
  jint offset, jint length) {

  struct iovec vec[1];
  struct cmsghdr_with_fds aux;
  struct msghdr socketMsg;

  int jfdsLen = (*env)->GetArrayLength(env, jfds);
  int auxLen = CMSG_LEN(jfdsLen * sizeof(int));

  memset(&aux, 0, auxLen);
  memset(&socketMsg, 0, sizeof(socketMsg));

  socketMsg.msg_iov = vec;
  socketMsg.msg_iovlen = 1;
  socketMsg.msg_control = &aux;
  socketMsg.msg_controllen = auxLen;

  aux.hdr.cmsg_len = auxLen;
  aux.hdr.cmsg_level = SOL_SOCKET;
  aux.hdr.cmsg_type = SCM_RIGHTS;

  for (i = 0; i < jfdsLen; i++) {
    jobject jfd = (*env)->GetObjectArrayElement(env, jfds, i);
    aux.fds[i] = fd_get(env, jfd);
  }

  RETRY_ON_EINTR(ret, sendmsg(fd, &socketMsg, PLATFORM_SEND_FLAGS));
}

接收侧receiveFileDescriptors0 JNI):

// DomainSocket.c— 精简核心逻辑
Java_org_apache_hadoop_net_unix_DomainSocket_receiveFileDescriptors0(
  JNIEnv *env, jclass clazz, jint fd, jarray jfds, jarray jbuf,
  jint offset, jint length) {

  struct iovec vec[1];
  struct cmsghdr_with_fds aux;
  struct msghdr socketMsg;

  int auxLen = CMSG_LEN(jfdsLen * sizeof(int));
  memset(&aux, 0, auxLen);
  memset(&socketMsg, 0, auxLen);

  socketMsg.msg_iov = vec;
  socketMsg.msg_iovlen = 1;
  socketMsg.msg_control = &aux;
  socketMsg.msg_controllen = auxLen;

  aux.hdr.cmsg_len = auxLen;
  aux.hdr.cmsg_level = SOL_SOCKET;
  aux.hdr.cmsg_type = SCM_RIGHTS;

  RETRY_ON_EINTR(bytesRead, recvmsg(fd, &socketMsg, 0));

  int jRecvFdsLen =
      (aux.hdr.cmsg_len - sizeof(struct cmsghdr)) / sizeof(int);
  for (i = 0; i < jRecvFdsLen; i++) {
    fdObj = jniCreateFileDescriptor(env, aux.fds[i]);
    (*env)->SetObjectArrayElement(env, jfds, i, fdObj);
  }
}

内核在收到 SCM_RIGHTS 后:查找发送方传递的 struct file → 为接收进程分配新 fd 槽位 → 设置新 fd 指向同一 struct file(引用计数 +1) → 返回给用户态。整个过程完全由内核完成,无需额外拷贝,安全性由 domain socket 文件权限 + DataNode 的 checkAccess() 凭证校验双重保障。

5.5 mmap 映射与 BlockReaderLocal 读取

接收到 fd 后,ShortCircuitReplica 负责建立 mmap 映射:

// ShortCircuitReplica.java
MappedByteBuffer loadMmapInternal() {
  try {
    FileChannel channel = dataStream.getChannel();
    MappedByteBuffer mmap = channel.map(MapMode.READ_ONLY, 0,
        Math.min(Integer.MAX_VALUE, channel.size()));  // Java NIO mmap
    LOG.trace("{}: created mmap of size {}", this, channel.size());
    return mmap;
  } catch (IOException e) {
    LOG.warn(this + ": mmap error", e);
    return null;
  } catch (RuntimeException e) {
    LOG.warn(this + ": mmap error", e);
    return null;
  }
}

随后 BlockReaderLocal 接管读取,核心是两条分支——零拷贝路径 vs 校验和路径:

// BlockReaderLocal.java
@Override
public synchronized int read(ByteBuffer buf) throws IOException {
  boolean canSkipChecksum = createNoChecksumContext();
  try {
    int nRead;
    try {
      if (canSkipChecksum && zeroReadaheadRequested) {
        nRead = readWithoutBounceBuffer(buf);   // 零拷贝路径
      } else {
        nRead = readWithBounceBuffer(buf, canSkipChecksum); // 校验和路径
      }
    } catch (IOException e) {
      throw e;
    }
    return nRead;
  } finally {
    if (canSkipChecksum) releaseNoChecksumContext();
  }
}

// 零拷贝读取——直接操作 FileChannel,无中间缓冲区
private synchronized int readWithoutBounceBuffer(ByteBuffer buf)
    throws IOException {
  freeDataBufIfExists();
  freeChecksumBufIfExists();
  int total = 0;
  while (buf.hasRemaining()) {
    int nRead = blockReaderIoProvider.read(dataIn, buf, dataPos);
    if (nRead <= 0) break;
    dataPos += nRead;
    total += nRead;
  }
  return (total == 0 && (dataPos == dataIn.size())) ? -1 : total;
}

Hadoop 3.x 的 BlockReaderLocal 零拷贝路径直接通过 FileChannel.read() 操作底层文件描述符,配合 MappedByteBuffer 即可获得 mmap 的零拷贝收益。Hadoop 2.x 早期实验性的 sun.misc.Unsafe 在 3.x 主线中已被彻底移除。

5.6 缓存与生命周期管理:ShortCircuitCache 的艺术

为了避免高并发下频繁连接和 mmap,客户端维护 ShortCircuitCache

// ShortCircuitCache.java
public ShortCircuitReplicaInfo fetchOrCreate(ExtendedBlockId key,
    ShortCircuitReplicaCreator creator) {
  Waitable<ShortCircuitReplicaInfo> newWaitable;
  lock.lock();
  try {
    ShortCircuitReplicaInfo info = null;
    for (int i = 0; i < FETCH_OR_CREATE_RETRY_TIMES; i++) {
      if (closed) return null;
      Waitable<ShortCircuitReplicaInfo> waitable = replicaInfoMap.get(key);
      if (waitable != null) {
        try {
          info = fetch(key, waitable);  // 已有副本,等待或直接返回
          break;
        } catch (RetriableException e) {
          LOG.debug("{}: retrying {}", this, e.getMessage());
        }
      }
    }
    if (info != null) return info;
    // 缓存未命中,需新建
    newWaitable = new Waitable<>(lock.newCondition());
    replicaInfoMap.put(key, newWaitable);
  } finally {
    lock.unlock();
  }
  return create(key, creator, newWaitable);  // 真正创建 ShortCircuitReplica
}

replicaInfoMap 使用 HashMap<> + ReentrantLock 保护。Waitable 模式允许同一个 block 的并发请求共享等待同一个创建过程,避免重复创建。

缓存受以下配置控制:

  • dfs.client.read.shortcircuit.streams.cache.size:最大缓存副本数(默认 256
  • dfs.client.read.shortcircuit.streams.cache.expiry.ms:副本过期时间(默认 5 分钟

当缓存满或过期时,自动执行 NativeIO.POSIX.munmap(mmap) + close(fd),避免 fd 泄漏和虚拟地址空间耗尽。


结语:内核智慧的分布式升华

HDFS 短路读取不是简单的功能叠加,而是对操作系统哲学的深刻致敬。它把最繁重的拷贝与调度工作,交还给内核的 page cache 和缺页中断机制;把最安全的授权,交给 Unix Domain Socket 的 SCM_RIGHTS;把极致的性能,交给 mmap 后的指针访问。

在云计算、AI 大模型、实时湖仓一体化的今天,当我们追求极致吞吐与最低延迟时,这项诞生于十余年前的技术依然闪耀。它提醒每一位工程师:真正的优化,往往不是堆砌新特性,而是让系统回归最本质、最优雅的设计——让内核做它最擅长的事,让代码保持简单而强大。

当你下次运行 Spark 任务,看到 CPU 曲线平稳、扫描速度飞跃时,请记得——背后,正是 mmap 与 Unix Domain Socket 在内核深处,悄然完成了一场零拷贝的华丽舞蹈。而 HDFS 的源码,则是这场舞蹈最精美的乐谱。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐