在接收几Gbit / s流量的数据包记录器应用程序中执行磁盘IO的好/实用策略是什么?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在接收几Gbit / s流量的数据包记录器应用程序中执行磁盘IO的好/实用策略是什么?相关的知识,希望对你有一定的参考价值。
请考虑以下情形:
流量生成器生成20 Gbit / s的网络流量,并使用两个10 Gbit链路将其发送到流量记录器。在流量记录器内部,所有数据包都应写入单个文件。
这就是我想从更高层次的角度做的事情。现在来看看包记录器的内部结构:
两个NIC都使用Intel DPDK(http://dpdk.org/)来处理传入的数据包。因此,所有传入流量都存储在生成在空间中的预分配的mbuf结构池的mbuf结构中。到目前为止一切正常。所有数据包都到达应用程序如果需要聚合数据,甚至可以将每个数据包记忆到更大的缓冲区。
我遇到的困难是将数据写入文件。我试图通过应用程序和文件之间的红色闪烁来表明。
到目前为止,我采用的方法都没有奏效。其中一些是:
- 将数据包发送到更大的(预分配的)缓冲区,并在缓冲区已满时写入文件。
- 与1.相同,但使用多个缓冲区进行缓冲和写入的单独线程。
- 类似于2.但使用Threadpool
- 使用Linux AIO进行异步写入
- 内存映射文件
在执行应用程序期间,我使用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。该应用程序现在看起来像这样:
在两个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
");
}
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的说法,没有意义的是,大多数时候驱动器不会全速写入。
问题太广泛了,但总的来说:
- 避免使用文件系统,即使用原始分区/设备。
- 看看SPDK。它就像DPDK,但对于存储(NVMe):http://www.spdk.io/
编辑1:
在您提供的代码段中,您应该使用单个writev()调用而不是loop + memcpy + write()。此外,从片段中还不清楚实际的突发大小是多少。最好整合一些小突发并在一个系统调用中编写它们。
编辑2:
- 代码中有一个错误:rte_pktmbuf_free()在实际写入之前。
- MAX_IOV在我的平台上是1024,因此在调用write之前最好将几个突发合并到IOV_MAX。
- 要避免轮询核心上的系统调用,请使用另一个lcore来执行写操作。在核心之间使用单个生产者单个消费者环形缓冲区。
- 既然你已经熟悉了fio并且它的性能对你来说没问题,那就使用和fio一样的方法,即相同的IO引擎,类型等。
- 创建一个测试应用程序,它在循环中写入相同的缓冲区,即消除代码的RX部分并仅测量写入性能。
- 不要memcpy() - 从pkt mbuf数据构建IOV并在将pkt mbuf写入磁盘后将其释放。
- 不要在DPDK轮询核心中使用read()/ write()。您可以使用aio或更好(但更难编码,但所有数据包都排队等待只用一个系统调用写入)IO_LISTIO。所有数据(IO向量或aiocb_lis)都可以使用
priv_size
的rte_pktmbuf_pool_create()
参数嵌入到pktmbuf本身中,因此不需要额外的分配。
使用SPDK作为最后的手段。这是一个很棒的图书馆,但也很苛刻。
以上是关于在接收几Gbit / s流量的数据包记录器应用程序中执行磁盘IO的好/实用策略是什么?的主要内容,如果未能解决你的问题,请参考以下文章
[差分][倍增lca][tarjan] Jzoj P3325 压力