Kafka:Zero-Copy零拷贝
1. 前言
前一段時間研究了大規模日志流高吞吐并行存儲,通過深入研究Kafka的底層存儲機制。我們發現Kafka的Zero-Copy零拷貝技術采用的是Java底層FileTransferTo方法,后期我們嘗試了對TransferTo性能及其并行性能進行測試。以及后面在Kafka上面實現了并行TransferTo方法,并應有到了Apache Kafka系統中。
2. 消息存儲機制
Kafka是一個分布式消息訂閱——發布系統,無論是發布還是訂閱,都須指定Topic。Topic只是一個邏輯的概念。每個Topic都包含一個或多個Partition,不同Partition可位于不同節點。同時Partition在物理上對應一個本地文件夾,每個Partition包含一個或多個Segment,每個Segment包含一個數據文件和一個與之對應的索引文件。在邏輯上,可以把一個Partition當作一個非常長的數組,可通過這個“數組”的索引(offset)去訪問其數據。
3. Kafka使用的zero-copy零拷貝技術
在Kafka中消息存儲模式中,數據存儲在底層文件系統中。當有Consumer訂閱了相應的Topic消息,數據需要從磁盤中讀取然后將數據寫回到套接字中(Socket)。此動作看似只需較少的 CPU 活動,但它的效率非常低:首先內核讀出全盤數據,然后將數據跨越內核用戶推到應用程序,然后應用程序再次跨越內核用戶將數據推回,寫出到套接字。應用程序實際上在這里擔當了一個不怎么高效的中介角色,將磁盤文件的數據轉入套接字。
數據每遍歷用戶內核一次,就要被拷貝一次,這會消耗 CPU 周期和內存帶寬。幸運的是,您可以通過一個叫 零拷貝— 很貼切 — 的技巧來消除這些拷貝。使用零拷貝的應用程序要求內核直接將數據從磁盤文件拷貝到套接字,而無需通過應用程序。零拷貝不僅大大地提高了應用程序的性能,而且還減少了內核與用戶模式間的上下文切換。
Java 類庫通過 java.nio.channels.FileChannel 中的 transferTo() 方法來在 Linux 和 UNIX 系統上支持零拷貝。可以使用 transferTo() 方法直接將字節從它被調用的通道上傳輸到另外一個可寫字節通道上,數據無需流經應用程序。本文首先展示了通過傳統拷貝語義進行的簡單文件傳輸引發的開銷,然后展示了使用 transferTo() 零拷貝技巧如何提高性能。
3.1 傳統模式下的四次拷貝與四次上下文切換
考慮一下從一個文件中讀出數據并將數據傳輸到網絡上另一程序的場景
File.read(fileDesc, buf, len); Socket.send(socket, buf, len);代碼邏輯很簡單,但實際上,拷貝的操作需要四次用戶模式和內核模式間的上下文切換,而且在操作完成前數據被復制了四次。下圖展示了數據是如何在內部從文件移動到套接字的:
這里涉及的步驟有:
使用中間內核緩沖區(而不是直接將數據傳輸到用戶緩沖區)看起來可能有點效率低下。但是之所以引入中間內核緩沖區的目的是想提高性能。在讀取方面使用中間內核緩沖區,可以允許內核緩沖區在應用程序不需要內核緩沖區內的全部數據時,充當 “預讀高速緩存(readahead cache)” 的角色。這在所需數據量小于內核緩沖區大小時極大地提高了性能。在寫入方面的中間緩沖區則可以讓寫入過程異步完成。
不幸的是,如果所需數據量遠大于內核緩沖區大小的話,這個方法本身可能成為一個性能瓶頸。數據在被最終傳入到應用程序前,在磁盤、內核緩沖區和用戶緩沖區中被拷貝了多次。
3.2 Kafka使用的zero-copy零拷貝技術
再次檢查傳統場景,我們注意到第二次和第三次拷貝根本就是多余的。應用程序只是起到緩存數據并將其傳回到套接字的作用而以,別無他用。數據可以直接從讀取緩沖區傳輸到套接字緩沖區。transferTo() 方法就能夠讓您實現這個操作。
transferTo()方法調用
public void transferTo(long position, long count, WritableByteChannel target);transferTo() 方法將數據從文件通道傳輸到了給定的可寫字節通道。在內部,它依賴底層操作系統對零拷貝的支持;在 UNIX 和各種 Linux 系統中,此調用被傳遞到 sendfile() 系統調用中,如下面代碼所示,下面代碼將數據從一個文件描述符傳輸到了另一個文件描述符:
sendFile()系統調用
#include <sys/socket.h> ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);上圖所示的 transferTo() 方法時的步驟有:
改進的地方:我們將上下文切換的次數從四次減少到了兩次,將數據復制的次數從四次減少到了三次(其中只有一次涉及到了 CPU)。但是這個代碼尚未達到我們的零拷貝要求。如果底層網絡接口卡支持收集操作 的話,那么我們就可以進一步減少內核的數據復制。在 Linux 內核 2.4 及后期版本中,套接字緩沖區描述符就做了相應調整,以滿足該需求。這種方法不僅可以減少多個上下文切換,還可以消除需要涉及 CPU 的重復的數據拷貝。對于用戶方面,用法還是一樣的,但是內部操作已經發生了改變:
4. FileTransferTo并行性能測試
接下來我們利用多線程的方法對FileTransferTo方法進行并行傳輸,希望通過并行IO的技術來提升讀取底層文件系統的性能。
測試條件
- CentOS release 5.10
- Intel? Xeon? CPU E7420 @ 2.13GHz
- 邏輯CPU個數 16
- 16GB RAM
- 測試文件大小:1.2GB
- 疑問:并行處理的性能比串行處理的性能差
- 項目鏈接https://github.com/Tjcug/kafkaParallelIO.
總結
以上是生活随笔為你收集整理的Kafka:Zero-Copy零拷贝的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Kafka:Kafka核心概念
- 下一篇: 高阶数据结构:SSTable