在接收几Gbit / s流量的数据包记录器应用程序中执行磁盘IO的好/实用策略是什么?

问题描述 投票:1回答:2

请考虑以下情形:

enter image description here

流量生成器生成20 Gbit / s的网络流量,并使用两个10 Gbit链路将其发送到流量记录器。在流量记录器内部,所有数据包都应写入单个文件。

这就是我想从更高层次的角度做的事情。现在来看看包记录器的内部结构:

enter image description here

两个NIC都使用Intel DPDK(http://dpdk.org/)来处理传入的数据包。因此,所有传入流量都存储在生成在空间中的预分配的mbuf结构池的mbuf结构中。到目前为止一切正常。所有数据包都到达应用程序如果需要聚合数据,甚至可以将每个数据包记忆到更大的缓冲区。

我遇到的困难是将数据写入文件。我试图通过应用程序和文件之间的红色闪烁来表明。

到目前为止,我采用的方法都没有奏效。其中一些是:

  1. 将数据包发送到更大的(预分配的)缓冲区,并在缓冲区已满时写入文件。
  2. 与1.相同,但使用多个缓冲区进行缓冲和写入的单独线程。
  3. 类似于2.但使用Threadpool
  4. 使用Linux AIO进行异步写入
  5. 内存映射文件

在执行应用程序期间,我使用iostat来监视磁盘利用率。大多数时候磁盘利用率不是很高,或者磁盘根本没有写入。

我的想法是它应该尽可能快地将数据写入磁盘。当数据包以20 Gbit / s的速度进入时,磁盘需要写入2.5 GByte / s(理论值)。

需要注意的一件重要事情是磁盘需要足够快才能处理大量数据。我使用fio(https://github.com/axboe/fio)测量了IO性能,如果做得对,应该没问题,以达到足够的速度。尽力而为是正确的做法。

在这种情况下,最大化磁盘IO的好策略/解决方案是什么?

磁盘利用率如何提高?

任何与此主题相关的来源(文献,博客......)也会受到欢迎。

谢谢。

编辑1:这是方法#1的一些示例代码。我把代码略微删掉了,但确实没有更多的东西。我尝试在不同数量的线程上运行它,具有不同的缓冲区大小,fwrite而不是write等。

 1 static int32_t store_data(struct storage_config *config)
 2 {
 3   sturct pkt *pkts[MAX_RECV];
 4   char *buf = malloc(BUF_SIZE);
 5   uint32_t bytes_total = 0;
 6
 7   while (config->running) {
 8      uint32_t nb_recv = receive_pkts(pkts);
 9      for (uint32_t i = 0; i < nb_recv; ++i) {
10         if (bytes_total + pkts[i]->len > BUF_SIZE) {
11            write(config->fd, buf, bytes_total);
12            bytes_total = 0;
13         }
14         memcpy(buf + bytes_total, pkts[i]->data, pkts[i]->len);
15         bytes_total += pkts[i]->len;
16      }
17   }
18   return 0;
19 }

其他方法以类似的方式编写。例如,线程池变体使用多个缓冲区而不是一个缓冲区,并将该缓冲区传递给另一个线程。因此,第11行中的写入调用被提取到它自己的函数中,一个线程将在该行上执行IO任务,而store_data()函数将使用其中一个附加缓冲区。

编辑2:在参考Andriy Berestovskyy的回答时,我结合了他的建议并使用了writev。该应用程序现在看起来像这样:

enter image description here

在两个rx核心(DPDK-lcores)中的每一个上运行以下代码:

while (quit_signal == false) {
    nb_rx = rte_eth_rx_burst(conf->port, 0, pkts, RX_RING_BURST_SIZE);
    if (nb_rx == 0) { continue; }

    for (i = nb_enq; i < nb_rx; ++i) {
        len = rte_pktmbuf_pkt_len(pkts[i]);
        nb_bytes_total += len;
        iov[iov_index].iov_len = len;
        iov[iov_index].iov_base = rte_pktmbuf_mtod(pkts[i], char *);
        rte_pktmbuf_free(pkts[i]);
        ++iov_index;
        if (iov_index >= IOV_MAX) { 
            if (writev(conf->fd, iov, IOV_MAX) != nb_bytes_total) {
                printf("Couldn't write all data\n");
            }
            iov_index = 0;
            nb_bytes_total = 0;
        }
    }
}

(RX_RING_BURST_SIZE是32,因为默认值是DPDK强制执行的最大值,我不知道如何更改它。我不知道这是否会产生影响)

当两个NIC接收大约10 Gbit / s(1.25 GByte / s)的流量时,大约一半的数据丢失,当数据包大小为1024字节时。如果数据包大小为64字节,则性能更差,大约80%的数据丢失。这有点意义,因为较小的数据包意味着更多的系统调用,并且rx环被更快地填充。根据iostat的说法,没有意义的是,大多数时候驱动器不会全速写入。

c linux performance io network-programming
2个回答
1
投票

问题太广泛了,但总的来说:

  1. 避免使用文件系统,即使用原始分区/设备。
  2. 看看SPDK。它就像DPDK,但对于存储(NVMe):http://www.spdk.io/

编辑1:

在您提供的代码段中,您应该使用单个writev()调用而不是loop + memcpy + write()。此外,从片段中还不清楚实际的突发大小是多少。最好整合一些小突发并在一个系统调用中编写它们。

编辑2:

  1. 代码中有一个错误:rte_pktmbuf_free()在实际写入之前。
  2. MAX_IOV在我的平台上是1024,因此在调用write之前最好将几个突发合并到IOV_MAX。
  3. 要避免轮询核心上的系统调用,请使用另一个lcore来执行写操作。在核心之间使用单个生产者单个消费者环形缓冲区。
  4. 既然你已经熟悉了fio并且它的性能对你来说没问题,那就使用和fio一样的方法,即相同的IO引擎,类型等。
  5. 创建一个测试应用程序,它在循环中写入相同的缓冲区,即消除代码的RX部分并仅测量写入性能。

1
投票
  1. 不要memcpy() - 从pkt mbuf数据构建IOV并在将pkt mbuf写入磁盘后将其释放。
  2. 不要在DPDK轮询核心中使用read()/ write()。您可以使用aio或更好(但更难编码,但所有数据包都排队等待只用一个系统调用写入)IO_LISTIO。所有数据(IO向量或aiocb_lis)都可​​以使用priv_sizerte_pktmbuf_pool_create()参数嵌入到pktmbuf本身中,因此不需要额外的分配。

使用SPDK作为最后的手段。这是一个很棒的图书馆,但也很苛刻。

© www.soinside.com 2019 - 2024. All rights reserved.