ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

【kv存储】实时主从同步实现与eBPF旁路转发方案

【kv存储】实时主从同步实现与eBPF旁路转发方案 一、背景本文重点是实时数据的主从同步以及使用eBPF做旁路转发的方案。功能实现仿照Redis实现的主从同步功能Redis的主从同步分为两个阶段第一个阶段从机启动时拉取主机的已有的数据拉取完毕后从机正式上线。第二个阶段是从机上线后主机实时将新的命令传播给从机实现实时数据的变更。总结为已有数据同步实时数据同步阶段一已有数据同步从机配置为slave并且指定主机的IP和端口从机启动时向主机发送SYNC指令主机收到SYNC指令将从机添加到从机列表当中后打开积压缓冲区触发BGSAVE全量保存数据到RDB文件中主机将保存的RDB文件发送到从机主机将积压缓冲区的数据发送到从机阶段二实时数据同步主机收到来自客户端的新指令主机解析RESP格式后后将变更类的指令不包括GET等指令重新编码转发到从机二、send网络转发实现指令解析后再变更类指令的分发分支上调用feed_slavevoidkvs_replication_feed_slaves(constchar*cmd,constchar*key,size_tklen,constchar*value,size_tvlen){if(g_repl.role!KVS_ROLE_MASTER||g_repl.slave_count0)return;charbuf[REPL_CMD_BUF];intlen0,encoded0;inthas_online0,has_syncing0;// 统计从库状态for(inti0;ig_repl.slave_count;i){if(g_slaves[i].stateSLAVE_ONLINE)has_online1;elsehas_syncing1;}// 有同步中的从库 → 写 Backlogif(has_syncing){lenresp_encode_cmd(...);encoded1;ensure_backlog();buffer_add(g_backlog,buf,len);// 满了则踢掉同步中的从库// ...}// 有在线从库 → 直接推送if(has_online){if(!encoded)lenresp_encode_cmd(...);for(inti0;ig_repl.slave_count;i){if(g_slaves[i].stateSLAVE_ONLINE){reactor_append(...);// 失败则 drop_slave(i--);}}}}待网络转发的数据填进reactor的wbuffer里等待事件循环。intreactor_append(intfd,constvoid*data,uint32_tlen){structconn*creactor_conn(fd);if(!c||!c-wbuffer||!data||len0)return-1;if(buffer_add(c-wbuffer,data,len)!0)return-1;set_event(fd,EPOLLIN|EPOLLOUT,0);return0;}可以看到这种方案面临着比较重的开销整个过程中有两次编码若干次拷贝占用主机send的吞吐。三、提出eBPF旁路转发的方案eBPF 主从同步开启时将hook点选择KV存储主机解析之后RESP格式的一个指定函数此时hook函数的参数中已经获取了cmd、key、value以及klen、vlen等数据。主机网络接收来自客户端的命令解析完毕之后拷贝到专门的hook函数声明非内联非优化以便eBPF捕获eBPF内核态程序捕获cmdkeyvalue等数据抛入ringbufBPF map中用户态程序轮询获取ringbuf中的数据并且编码send转发到从机我们分析这条路径不难发现eBPF的思路是利用了旁路捕获并转发的思路将编码、多次拷贝、send转发的开销旁路到了eBPF用户态进程中。KV存储中只需要维护一个类似这样的hook函数__attribute__((noinline))voidkvs_eBPF_propagation_hook(constchar*cmd,constchar*key,size_tklen,constchar*value,size_tvlen){(void)cmd;(void)key;(void)klen;(void)value;(void)vlen;asmvolatile();}eBPF的实现比较复杂涉及libbpf-bootstrap程序的改造vertifier下编程容易踩的坑等将来我写新的博客专门讲解。
返回列表