C0reFast记事本

C0reFast记事本

马上订阅 C0reFast记事本 RSS 更新: https://www.ichenfu.com/atom.xml

一个UFO引发的惨案

2024年4月1日 20:30

首先需要解释一下标题,原谅我当了一回标题党,此UFO不是Unidentified flying object,而是在网络中的一个Oflload卸载技术UDP fragmentation offload。事情的起因是这样的,我们最近尝试将线上的虚拟机,从基于网卡SR-IOV+直通的方案,切换到基于DPDK+vhost-user的方案,以换取热迁移的效率提升。

从之前的模拟压测和线上灰度效果来看,新的DPDK方案的性能和稳定性都处于很好的水平,在我们的场景下可以很好地满足需求。
直到灰度到某个业务的时候,发生了一些问题,导致了虚拟机的网络中断。

我们通过热拔插方式进行网络切换,首先,会把当前直通的网卡从虚拟机中热拔出来,然后,再把一个vhost-user网卡热插到虚拟机中,从而实现网卡的切换。在切换过程中,大致会有3-5s左右的网络中断,但根据和业务的沟通,在单线程的操作情况下,这样的中断是没有问题的,不会影响业务。

为了保证业务的稳定,我们在网卡切换后,会持续ping 10s对应的虚拟机,确保网络正常后才会进行下一台的操作。

然后问题就发生了,在某些虚拟机切换网卡之后,大约5分钟内,网络是正常的,但是超过5分钟之后,突然网络就不通了,这个问题也是随机的,而对于网络不通的机器,通过重启DPDK进程的方式,网络又可以恢复几分钟,然后继续不通。这些现象确实在之前的测试中没有遇到过。从日志看,有少量的DPDK进程打印了这两条日志:

VHOST_DATA: (/tmp/ens8f0-2.sock) failed to allocate memory for mbuf.VHOST_DATA: (/tmp/ens8f0-2.sock) failed to copy desc to mbuf.

从日志看,应该是给DPDK分配的内存不够了,导致DPDK从内存池里分配mbuf时无内存可用,但是DPDK使用的内存,是经过精确计算的呀?看看内存分配数量相关的代码:

#define DEFAULT_IP_MTU  (1500)#define L2_OVERHEAD  (14 + 4 + 4)#define VF_RX_OFFSET  (32)#define DEFAULT_MBUF_SIZE (DEFAULT_IP_MTU + L2_OVERHEAD + RTE_PKTMBUF_HEADROOM + VF_RX_OFFSET)#define MAX_VHOST_QUEUE_PAIRS 16/* rxq/txq descriptors numbers */#define RXQ_TXQ_DESC_1K 1024#define RXQ_TXQ_DESC_8K 8192/* relay mempool config */#define DEFAULT_NR_RX_QUEUE MAX_VHOST_QUEUE_PAIRS#define DEFAULT_NR_TX_QUEUE MAX_VHOST_QUEUE_PAIRS#define DEFAULT_NR_RX_DESCRXQ_TXQ_DESC_8K#define DEFAULT_NR_TX_DESCRXQ_TXQ_DESC_1K#define NUM_PKTMBUF_POOL(DEFAULT_NR_RX_DESC * DEFAULT_NR_RX_QUEUE + DEFAULT_NR_TX_DESC * DEFAULT_NR_TX_QUEUE + 4096)// ...mpool = rte_pktmbuf_pool_create(mp_name, n_mbufs, RTE_MEMPOOL_CACHE_MAX_SIZE, 0, DEFAULT_MBUF_SIZE, request_socket_id);

我们给每个VM分配了最多16个队列,MTU为1500,网卡侧同样支持16个发送队列+16个接收队列,其中每个接收队列设置ring buffer大小为8192,发送队列ring buffer大小1024,经过一系列的计算NUM_PKTMBUF_POOL这个值应该在所有场景都能满足需求,那为什么会出现内存不够的情况呢?我们再深入看一下DPDK相关的代码:

首先这个日志,在DPDK中有两个地方会打印,一个是在virtio_dev_tx_split函数中,另一个函数是vhost_dequeue_single_packed,这俩函数的功能是一致的,只是一个是用来处理老的virtio split ring的场景,另一个是处理packed ring的场景,而我们目前用的还是老的split ring,于是着重看下相关的代码:

__rte_always_inlinestatic uint16_tvirtio_dev_tx_split(struct virtio_net *dev, struct vhost_virtqueue *vq,struct rte_mempool *mbuf_pool, struct rte_mbuf **pkts, uint16_t count,bool legacy_ol_flags){    // ...省略    // 一次循环,最多取32个数据count = RTE_MIN...

剩余内容已隐藏

查看完整文章以阅读更多