WHY
在进行单臂负载均衡测试和双臂负载均衡测试时,发现dpvs.conf中很多配置项意思尚不理解。
DPVS的配置文件条目比较多,参考了一些资料和代码片段来理解配置文件,希望通过对配置文件的理解能更好的了解DPVS。
下面的内容可能会有错误,会在以后的研究中进行修正。
配置文件说明
接口配置
两个接口dpdk0, dpdk1 , 这里并没有和具体网卡映射关系的配置,而是按照PCI地址的从小到大来的。
比如 98:00.0 就对应dpdk0 , 98:00.1 对应dpdk1
! netif config
netif_defs {<init> pktpool_size 1048575 # MBUF池大小<init> pktpool_cache 256 # MBUF池对每个worker的缓存大小<init> fdir_mode perfect # flowdirect 精准匹配<init> device dpdk0 {rx {queue_number 8 # 队列数量为8 descriptor_number 1024 # 队列深度 1024个数据包rss all # 全流量RSS}tx {queue_number 8descriptor_number 1024}! mtu 1500! promisc_mode! allmulticastkni_name dpdk0.kni }<init> device dpdk1 {rx {queue_number 8descriptor_number 1024rss all}tx {queue_number 8descriptor_number 1024}! mtu 1500! promisc_mode! allmulticastkni_name dpdk1.kni}
}
worker配置
worker_defs {<init> worker cpu0 { # 主worker, 控制面type mastercpu_id 0}<init> worker cpu1 { # 从worker, 转发面type slavecpu_id 1 # 绑定核心port dpdk0 { # 绑定网卡 tx/rx队列rx_queue_ids 0tx_queue_ids 0! isol_rx_cpu_ids 9! isol_rxq_ring_sz 1048576}port dpdk1 { # 绑定网卡 tx/rx队列rx_queue_ids 0tx_queue_ids 0! isol_rx_cpu_ids 9! isol_rxq_ring_sz 1048576}}<init> worker cpu2 {type slavecpu_id 2port dpdk0 {rx_queue_ids 1tx_queue_ids 1! isol_rx_cpu_ids 10! isol_rxq_ring_sz 1048576}port dpdk1 {rx_queue_ids 1tx_queue_ids 1! isol_rx_cpu_ids 10! isol_rxq_ring_sz 1048576}}...<init> worker cpu8 {type slavecpu_id 8! icmp_redirect_coreport dpdk0 {rx_queue_ids 7tx_queue_ids 7! isol_rx_cpu_ids 16! isol_rxq_ring_sz 1048576}port dpdk1 {rx_queue_ids 7tx_queue_ids 7! isol_rx_cpu_ids 16! isol_rxq_ring_sz 1048576}}}
一共设置了9cpu核心,1~8 是转发worker 数据面, 0 是master worker.控制面。
每一个worker都包含了dpdk0, dpdk1两个网卡的tx/rx队列, 说明一个 转发worker将要收发两个网卡双向的数据包。
定时器间隔
timer_defs {schedule_interval 500 #定时器间隔 单位 us
}
schedule_interval 最终赋值给 timer_sched_interval_us , 确实是us单位
//schedule_interval 最终赋值给 timer_sched_interval_us , 确实是us单位
static void lcore_job_timer_manage(void *args)
{static uint64_t tm_manager_time[DPVS_MAX_LCORE] = { 0 };uint64_t now = rte_get_timer_cycles();portid_t cid = rte_lcore_id();if (unlikely((now - tm_manager_time[cid]) * 1000000 / g_cycles_per_sec> timer_sched_interval_us)) {rte_timer_manage();tm_manager_time[cid] = now;}
}
邻居
neigh_defs {<init> unres_queue_length 128 # 未解析的队列额长度timeout 60 # 邻居表超时时间
}
这里的邻居表超时时间似乎比linux系统长, linux 30s
ipset哈希表
ip集合的哈希池大小
ipset_defs {<init> ipset_hash_pool_size 131072
}
但是观察代码发现这个值并没有使用, 而是直接使用了IPSET_HASH_POOL_SIZE_DEF固定值
#define IPSET_HASH_POOL_SIZE_DEF 262143
int
ipset_hash_init(void)
{int i;char poolname[32];// 创建表时,表的大小直接用的宏IPSET_HASH_POOL_SIZE_DEF的值for (i = 0; i < get_numa_nodes(); i++) {snprintf(poolname, sizeof(poolname), "ipset_hash_pool_%d", i);ipset_hash_cache[i] = rte_mempool_create(poolname,IPSET_HASH_POOL_SIZE_DEF,sizeof(struct hash_entry) + HASH_ELEM_SIZE_MAX,IPSET_HASH_CACHE_SIZE_DEF,0, NULL, NULL, NULL, NULL, i, 0);if (!ipset_hash_cache[i]) {return EDPVS_NOMEM;}}return EDPVS_OK;
}// ipset_hash_pool_size 并没有赋值给其他变量
static void
ipset_hash_pool_size_handler(vector_t tokens)
{char *str = set_value(tokens);int pool_size;assert(str);pool_size = atoi(str);if (pool_size < IPSET_HASH_POOL_SIZE_MIN) {RTE_LOG(WARNING, IPSET, "invalid ipset_hash_pool_size %s, using default %d\n",str, IPSET_HASH_POOL_SIZE_DEF);ipset_hash_pool_size = IPSET_HASH_POOL_SIZE_DEF;} else {is_power2(pool_size, 1, &pool_size);RTE_LOG(INFO, IPSET, "ipset_hash_pool_size = %d (round to 2^n-1)\n", pool_size);ipset_hash_pool_size = pool_size - 1;}FREE_PTR(str);
}
IPv4配置
ipv4_defs {forwarding off # 关闭转发<init> default_ttl 64 fragment { # 处理分片的资源 <init> bucket_number 4096 # 桶数量<init> bucket_entries 16 # 桶内的条目数量<init> max_entries 4096 # 最大分片数量<init> ttl 1 }
}
这里尚不确定在forwarding为off时,这些分片设置是否还会起作用
IPv6 配置
ipv6_defs {disable off # 开启ipv6forwarding off # 关闭转发route6 {<init> method hlist # 哈希链表recycle_time 10 # 路由条目回收时间}
}
控制配置
ctrl_defs {lcore_msg {<init> ring_size 4096 # 消息队列长度sync_msg_timeout_us 20000 # 同步消息超时时间 uspriority_level low # 低优先级}
}
控制消息队列,是每个核心(worker)都有的,控制消息通过这些队列发送到目的worker
参考代码
/* per-lcore msg queue */for (ii = 0; ii < DPVS_MAX_LCORE; ii++) {snprintf(ring_name, sizeof(ring_name), "msg_ring_%d", ii);msg_ring[ii] = rte_ring_create(ring_name, msg_ring_size,rte_socket_id(), RING_F_SC_DEQ);if (unlikely(NULL == msg_ring[ii])) {RTE_LOG(ERR, MSGMGR, "%s: fail to create msg ring\n", __func__);dpvs_mempool_destroy(msg_pool);for (--ii; ii >= 0; ii--)rte_ring_free(msg_ring[ii]);return EDPVS_DPDKAPIFAIL;}}
IPVS配置
连接池
conn {<init> conn_pool_size 2097152 # 连接池大小<init> conn_pool_cache 256 # 每个核心缓存的连接池大小conn_init_timeout 3 # 连接初始化超时时间 s! expire_quiescent_template! fast_xmit_close! <init> redirect off}
UDP
udp {! defence_udp_dropuoa_mode opp # opp开启UOA模式,uoa_max_trail 3 # uoa尝试次数timeout {oneway 60 # 单向超时normal 300 # 正常连接超时last 3 # 最后超时}}
uoa模式,就是将源IP地址写入IP头里的option字段中。
TCP
tcp {timeout { # tcp连接各种状态下的超时时间none 2 established 90 # 已建立连接的超时syn_sent 3 # 发出syn以后的超时syn_recv 30 # 收到syn以后的超时fin_wait 7 # FIN发送之后的超时time_wait 7 # 发送FIN,接收到FIN之后的超时close 3 close_wait 7last_ack 7listen 120 # 监听连接超时synack 30last 2}synproxy { # TCP SYN proxysynack_options { # syn ACK修改TCP头中的字段mss 1452 # max segment size , 最大段大小ttl 63 # time-to-live 最大转发跳数sack # 选择性确认}close_client_windowrs_syn_max_retry 3ack_storm_thresh 10max_ack_saved 3conn_reuse_state { # 连接重用状态closetime_wait}}
}
synproxy 用来防止SYN FLOOD攻击,在完成3次握手之前,不与RS建立连接。
如果有大量的SYN请求,而没有ACK, 后端RS不会受到影响。这些垃圾请求会在syn_recv超时以后清除。
封面图