# 9.1 Kvstore ## 需求 1. ntyco需要作为kvstore的submodule,通过git clone一次下载。 **完成**。 2. README需要包含编译步骤,测试方案与可行性,性能数据。 **完成**。 3. 全量持久化保存数据集。 **BUG FIX**。 4. 持久化的性能数据。 **完成**。 5. 特殊字符,可以解决redis的resp协议。 **完成**。 6. 实现配置文件,把日志级别,端口ip,主从模式,持久化方案。 **完成**。 7. 持久化落盘用io_uring,加载配置文件用mmap。 **完成**。 8. 主从同步的性能,开启与关闭性能做到5%?。 9. 主从同步600w条,出现的coredump。 **BUG FIX**。 10. 主从同步用ebpf实现。 **完成**。 11. 内存池测试qps与虚拟内存,物理内存。 **完成**。 12. 实现一个内存泄露检测组件。 **完成**。 ## 环境安装与编译 ```shell # xml sudo apt install libxml2 libxml2-dev # hiredis client sudo apt install -y libhiredis-dev # bpftrace sudo apt install -y bpftrace libelf libelf-dev clang git clone git@gitlab.0voice.com:lianyiheng/9.1-kvstore.git cd 9.1-kvstore/ git submodule update --init --recursive ./init.sh make ``` ``` docker run -it --rm \ -v "$(pwd)":/workdir \ -w /workdir \ --pid=host \ --privileged \ ghcr.io/eunomia-bpf/bpftime:latest \ /bin/bash ``` ## 测试 ### 测试1:性能测试 测试条件: 1. 不启用持久化。 2. 不启用主从同步。 3. pipline: 1. RSET 100w 条, p:i v:i -> +OK 2. RGET 100w 条, p:i -> +v:i 3. RDEL 100w 条。 p:i -> +OK 4. 本机发送请求。 #### 内存分配: malloc ```bash lian@ubuntu:~/share/9.1-kvstore$ ./test-redis/testcase 192.168.10.129 8888 3 Connected to 192.168.10.129:8888 BATCH (N=3000000) --> time_used=3294 ms, qps=910746 BATCH (N=3000000) --> time_used=3501 ms, qps=856898 BATCH (N=3000000) --> time_used=3457 ms, qps=867804 BATCH (N=3000000) --> time_used=3351 ms, qps=895255 BATCH (N=3000000) --> time_used=3320 ms, qps=903614 BATCH (N=3000000) --> time_used=3551 ms, qps=844832 BATCH (N=3000000) --> time_used=3354 ms, qps=894454 BATCH (N=3000000) --> time_used=3475 ms, qps=863309 BATCH (N=3000000) --> time_used=3404 ms, qps=881316 BATCH (N=3000000) --> time_used=3460 ms, qps=867052 BATCH (N=3000000) --> time_used=3392 ms, qps=884433 BATCH (N=3000000) --> time_used=3427 ms, qps=875401 BATCH (N=3000000) --> time_used=3441 ms, qps=871839 BATCH (N=3000000) --> time_used=3471 ms, qps=864304 BATCH (N=3000000) --> time_used=3354 ms, qps=894454 BATCH (N=3000000) --> time_used=3447 ms, qps=870322 BATCH (N=3000000) --> time_used=3364 ms, qps=891795 BATCH (N=3000000) --> time_used=3200 ms, qps=937500 BATCH (N=3000000) --> time_used=3159 ms, qps=949667 BATCH (N=3000000) --> time_used=3482 ms, qps=861573 BATCH (N=3000000) --> time_used=3474 ms, qps=863557 BATCH (N=3000000) --> time_used=3591 ms, qps=835421 BATCH (N=3000000) --> time_used=3466 ms, qps=865551 BATCH (N=3000000) --> time_used=3425 ms, qps=875912 BATCH (N=3000000) --> time_used=3346 ms, qps=896592 BATCH (N=3000000) --> time_used=3532 ms, qps=849377 BATCH (N=3000000) --> time_used=3471 ms, qps=864304 BATCH (N=3000000) --> time_used=3616 ms, qps=829646 BATCH (N=3000000) --> time_used=3403 ms, qps=881575 BATCH (N=3000000) --> time_used=3426 ms, qps=875656 BATCH (N=3000000) --> time_used=3493 ms, qps=858860 BATCH (N=3000000) --> time_used=3411 ms, qps=879507 BATCH (N=3000000) --> time_used=3422 ms, qps=876680 BATCH (N=3000000) --> time_used=3556 ms, qps=843644 BATCH (N=3000000) --> time_used=3285 ms, qps=913242 BATCH (N=3000000) --> time_used=3486 ms, qps=860585 BATCH (N=3000000) --> time_used=3427 ms, qps=875401 BATCH (N=3000000) --> time_used=3563 ms, qps=841987 BATCH (N=3000000) --> time_used=3304 ms, qps=907990 BATCH (N=3000000) --> time_used=3582 ms, qps=837520 BATCH (N=3000000) --> time_used=3468 ms, qps=865051 BATCH (N=3000000) --> time_used=3360 ms, qps=892857 BATCH (N=3000000) --> time_used=3426 ms, qps=875656 BATCH (N=3000000) --> time_used=3186 ms, qps=941619 BATCH (N=3000000) --> time_used=3251 ms, qps=922792 BATCH (N=3000000) --> time_used=3400 ms, qps=882352 BATCH (N=3000000) --> time_used=3446 ms, qps=870574 BATCH (N=3000000) --> time_used=3302 ms, qps=908540 BATCH (N=3000000) --> time_used=3072 ms, qps=976562 BATCH (N=3000000) --> time_used=3458 ms, qps=867553 average qps:880462 ALL TESTS PASSED. ``` #### 内存分配: 自实现内存池 ```bash lian@ubuntu:~/share/9.1-kvstore$ ./test-redis/testcase 192.168.10.129 8888 3 Connected to 192.168.10.129:8888 BATCH (N=3000000) --> time_used=3241 ms, qps=925640 BATCH (N=3000000) --> time_used=3047 ms, qps=984574 BATCH (N=3000000) --> time_used=3085 ms, qps=972447 BATCH (N=3000000) --> time_used=3119 ms, qps=961846 BATCH (N=3000000) --> time_used=3104 ms, qps=966494 BATCH (N=3000000) --> time_used=3163 ms, qps=948466 BATCH (N=3000000) --> time_used=3033 ms, qps=989119 BATCH (N=3000000) --> time_used=3170 ms, qps=946372 BATCH (N=3000000) --> time_used=3299 ms, qps=909366 BATCH (N=3000000) --> time_used=3272 ms, qps=916870 BATCH (N=3000000) --> time_used=3294 ms, qps=910746 BATCH (N=3000000) --> time_used=3182 ms, qps=942803 BATCH (N=3000000) --> time_used=3190 ms, qps=940438 BATCH (N=3000000) --> time_used=3493 ms, qps=858860 BATCH (N=3000000) --> time_used=3111 ms, qps=964320 BATCH (N=3000000) --> time_used=3220 ms, qps=931677 BATCH (N=3000000) --> time_used=3067 ms, qps=978154 BATCH (N=3000000) --> time_used=3345 ms, qps=896860 BATCH (N=3000000) --> time_used=3381 ms, qps=887311 BATCH (N=3000000) --> time_used=3416 ms, qps=878220 BATCH (N=3000000) --> time_used=3192 ms, qps=939849 BATCH (N=3000000) --> time_used=3085 ms, qps=972447 BATCH (N=3000000) --> time_used=3150 ms, qps=952380 BATCH (N=3000000) --> time_used=3296 ms, qps=910194 BATCH (N=3000000) --> time_used=3001 ms, qps=999666 BATCH (N=3000000) --> time_used=3143 ms, qps=954502 BATCH (N=3000000) --> time_used=3111 ms, qps=964320 BATCH (N=3000000) --> time_used=3123 ms, qps=960614 BATCH (N=3000000) --> time_used=3257 ms, qps=921093 BATCH (N=3000000) --> time_used=3037 ms, qps=987816 BATCH (N=3000000) --> time_used=3135 ms, qps=956937 BATCH (N=3000000) --> time_used=3124 ms, qps=960307 BATCH (N=3000000) --> time_used=3276 ms, qps=915750 BATCH (N=3000000) --> time_used=3058 ms, qps=981033 BATCH (N=3000000) --> time_used=3024 ms, qps=992063 BATCH (N=3000000) --> time_used=3224 ms, qps=930521 BATCH (N=3000000) --> time_used=3235 ms, qps=927357 BATCH (N=3000000) --> time_used=3334 ms, qps=899820 BATCH (N=3000000) --> time_used=3427 ms, qps=875401 BATCH (N=3000000) --> time_used=3218 ms, qps=932256 BATCH (N=3000000) --> time_used=3191 ms, qps=940144 BATCH (N=3000000) --> time_used=3179 ms, qps=943692 BATCH (N=3000000) --> time_used=3104 ms, qps=966494 BATCH (N=3000000) --> time_used=3202 ms, qps=936914 BATCH (N=3000000) --> time_used=3184 ms, qps=942211 BATCH (N=3000000) --> time_used=3000 ms, qps=1000000 BATCH (N=3000000) --> time_used=3280 ms, qps=914634 BATCH (N=3000000) --> time_used=3141 ms, qps=955109 BATCH (N=3000000) --> time_used=3198 ms, qps=938086 BATCH (N=3000000) --> time_used=3126 ms, qps=959692 average qps:942837 ALL TESTS PASSED. ``` #### 内存分配:jemalloc ```shell lian@ubuntu:~/share/9.1-kvstore$ ./test-redis/testcase 192.168.10.129 8888 3 Connected to 192.168.10.129:8888 BATCH (N=3000000) --> time_used=3511 ms, qps=854457 BATCH (N=3000000) --> time_used=3280 ms, qps=914634 BATCH (N=3000000) --> time_used=3603 ms, qps=832639 BATCH (N=3000000) --> time_used=3418 ms, qps=877706 BATCH (N=3000000) --> time_used=3353 ms, qps=894721 BATCH (N=3000000) --> time_used=3435 ms, qps=873362 BATCH (N=3000000) --> time_used=3250 ms, qps=923076 BATCH (N=3000000) --> time_used=3550 ms, qps=845070 BATCH (N=3000000) --> time_used=3536 ms, qps=848416 BATCH (N=3000000) --> time_used=3273 ms, qps=916590 BATCH (N=3000000) --> time_used=3224 ms, qps=930521 BATCH (N=3000000) --> time_used=3161 ms, qps=949066 BATCH (N=3000000) --> time_used=3143 ms, qps=954502 BATCH (N=3000000) --> time_used=3342 ms, qps=897666 BATCH (N=3000000) --> time_used=3410 ms, qps=879765 BATCH (N=3000000) --> time_used=3522 ms, qps=851788 BATCH (N=3000000) --> time_used=3035 ms, qps=988467 BATCH (N=3000000) --> time_used=3352 ms, qps=894988 BATCH (N=3000000) --> time_used=3226 ms, qps=929944 BATCH (N=3000000) --> time_used=3406 ms, qps=880798 BATCH (N=3000000) --> time_used=3336 ms, qps=899280 BATCH (N=3000000) --> time_used=3307 ms, qps=907166 BATCH (N=3000000) --> time_used=3171 ms, qps=946073 BATCH (N=3000000) --> time_used=3252 ms, qps=922509 BATCH (N=3000000) --> time_used=3296 ms, qps=910194 BATCH (N=3000000) --> time_used=3301 ms, qps=908815 BATCH (N=3000000) --> time_used=3403 ms, qps=881575 BATCH (N=3000000) --> time_used=3234 ms, qps=927643 BATCH (N=3000000) --> time_used=3348 ms, qps=896057 BATCH (N=3000000) --> time_used=3517 ms, qps=852999 BATCH (N=3000000) --> time_used=3354 ms, qps=894454 BATCH (N=3000000) --> time_used=3529 ms, qps=850099 BATCH (N=3000000) --> time_used=3473 ms, qps=863806 BATCH (N=3000000) --> time_used=3521 ms, qps=852030 BATCH (N=3000000) --> time_used=3370 ms, qps=890207 BATCH (N=3000000) --> time_used=3267 ms, qps=918273 BATCH (N=3000000) --> time_used=3352 ms, qps=894988 BATCH (N=3000000) --> time_used=3433 ms, qps=873871 BATCH (N=3000000) --> time_used=3374 ms, qps=889152 BATCH (N=3000000) --> time_used=3360 ms, qps=892857 BATCH (N=3000000) --> time_used=3463 ms, qps=866300 BATCH (N=3000000) --> time_used=3499 ms, qps=857387 BATCH (N=3000000) --> time_used=3294 ms, qps=910746 BATCH (N=3000000) --> time_used=3311 ms, qps=906070 BATCH (N=3000000) --> time_used=3443 ms, qps=871333 BATCH (N=3000000) --> time_used=3381 ms, qps=887311 BATCH (N=3000000) --> time_used=3422 ms, qps=876680 BATCH (N=3000000) --> time_used=3421 ms, qps=876936 BATCH (N=3000000) --> time_used=3322 ms, qps=903070 BATCH (N=3000000) --> time_used=3494 ms, qps=858614 average qps:892493 ALL TESTS PASSED. ``` ### 测试2:持久化 测试条件: 1. 启用持久化。 2. 不启用主从同步。 3. pipline: 1. RSET 100w 条, p:i v:i -> +OK 2. RGET 100w 条, p:i -> +v:i 3. RDEL 100w 条。 p:i -> +OK 5. 本机发送请求。 ```shell lian@ubuntu:~/share/9.1-kvstore$ ./test-redis/testcase 192.168.10.129 8888 4 Connected to 192.168.10.129:8888 BATCH (N=3000000) --> time_used=3500 ms, qps=857142 BATCH (N=3000000) --> time_used=3322 ms, qps=903070 BATCH (N=3000000) --> time_used=3424 ms, qps=876168 BATCH (N=3000000) --> time_used=3483 ms, qps=861326 BATCH (N=3000000) --> time_used=3421 ms, qps=876936 BATCH (N=3000000) --> time_used=3519 ms, qps=852514 BATCH (N=3000000) --> time_used=3597 ms, qps=834028 BATCH (N=3000000) --> time_used=3504 ms, qps=856164 BATCH (N=3000000) --> time_used=3281 ms, qps=914355 BATCH (N=3000000) --> time_used=3446 ms, qps=870574 average qps:870227 ALL TESTS PASSED. ``` ### 测试3:内存 #### malloc ```shell VIRT 58504 RES 4604 插入 20w 删除 10w,重复 10 次,共计插入 200w 删除 100w。 BATCH (N=9000000) --> time_used=12897 ms, qps=1395673 VIRT 489M RES 430M 插入 10w 删除 20w,重复 10 次,共计插入 100w 删除 200w。 BATCH (N=9000000) --> time_used=10033 ms, qps=1794079 VIRT 208M RES 155M ``` ![alt text](image11.png) ![alt text](image12.png) ![alt text](image13.png) #### jemalloc ```shell VIRT 69376 RES 5408 插入 20w 删除 10w,重复 30 次,共计插入 600w 删除 300w。 BATCH (N=9000000) --> time_used=9436 ms, qps=1907587 VIRT 356M RES 294M 插入 10w 删除 20w,重复 30 次,共计插入 300w 删除 600w。 BATCH (N=9000000) --> time_used=9353 ms, qps=1924516 VIRT 356M RES 119M ``` ![alt text](image11.png) ![alt text](image22.png) ![alt text](image23.png) #### mypool ```shell VIRT 58504 RES 4636 插入 20w 删除 10w,重复 30 次,共计插入 600w 删除 300w。 BATCH (N=3000000) --> time_used=3184 ms, qps=1884422 VIRT 625M RES 572M 插入 10w 删除 20w,重复 10 次,共计插入 100w 删除 200w。 BATCH (N=3000000) --> time_used=3022 ms, qps=1985440 VIRT 122M RES 71492 ``` ![alt text](image31.png) ![alt text](image32.png) ![alt text](image33.png) ### 测试4:主从同步 测试条件: 1. 不启用持久化。 2. 启用主从同步。 3. pipline: 1. RSET 100w 条, p:i v:i -> +OK 2. RGET 100w 条, p:i -> +v:i 3. RDEL 100w 条。 p:i -> +OK 5. 本机发送请求。 ```shell lian@ubuntu:~/share/9.1-kvstore$ ./test-redis/testcase 192.168.10.129 8888 4 Connected to 192.168.10.129:8888 BATCH (N=3000000) --> time_used=3702 ms, qps=810372 BATCH (N=3000000) --> time_used=3804 ms, qps=788643 BATCH (N=3000000) --> time_used=4076 ms, qps=736015 BATCH (N=3000000) --> time_used=3840 ms, qps=781250 BATCH (N=3000000) --> time_used=3824 ms, qps=784518 average qps:780159 ALL TESTS PASSED. lian@ubuntu:~/share/9.1-kvstore$ ./test-redis/testcase 192.168.10.129 8888 4 Connected to 192.168.10.129:8888 BATCH (N=3000000) --> time_used=3958 ms, qps=757958 BATCH (N=3000000) --> time_used=4043 ms, qps=742023 BATCH (N=3000000) --> time_used=3729 ms, qps=804505 BATCH (N=3000000) --> time_used=3989 ms, qps=752068 BATCH (N=3000000) --> time_used=3603 ms, qps=832639 average qps:777838 ALL TESTS PASSED. ``` ### 面试题 1. 为什么会实现kvstore,使用场景在哪里? 2. reactor, ntyco, io_uring的三种网络模型的性能差异? 3. 多线程的kvstore该如何改进? 4. 私有协议如何设计会更加安全可靠? 5. 协议改进以后,对已有的代码有哪些改变? 6. kv引擎实现了哪些? 7. 每个kv引擎的使用场景,以及性能差异? 8. 测试用例如何实现?并且保证代码覆盖率超过90% 9. 网络并发量如何?qps如何? 10. 能够跟哪些系统交互使用? ## 项目收获 #### reactor网络模型,用户态网络缓冲区的写法。 #### 特殊字符串支持的引擎层数据结构设计,支持\0作为键值存储。 1. 长度前缀 + 内容的 binary-safe 字符串表示,支持 \0 作为普通字符。 #### 实现RESP协议的服务端协议解析。 1. 解析流程: 1. 内核 拷贝到 用户态缓冲区 2. 用户态缓冲区 就地解析 bulk-string 为执行引擎可以看得懂的结构体 3. 执行引擎 拷贝 结构体的内容,插入到底层存储结构中 4. 循环解析直到 用户态缓冲区为空 2. 实现了 pipeline 支持:每次从 buffer 读到一个完整命令就交给应用层处理,应用层返回已消费字节数。如果 buffer 里有半包,连接层保留下次继续解析。 #### 快照异步创建。 1. 使用fork的Copy On Write机制,实现的异步快照创建,不会受到后续更新请求的影响 #### 实现SPSC/SPMC,专门uring线程实现异步的增量、全量落盘操作。 1. 熟悉了SPSC无锁队列的实现方案。 1. 无锁、cache friendly 2. 流程: 1. 生产者组装task,丢给SPSC。 2. 消费者从中取出然后执行,置入destroy_queue,触发eventfd。 3. 生产者释放destroy_queue。 3. 解决的问题: 1. iouring 由于 cqe 接收不及时导致的 task 丢失无法释放。 1. 通过背压解决,设置inflight上限。 2. 背压后,生产者速度 > 消费者速度,ringbuffer 满导致只能阻塞主线程或降低速度。 1. 通过修改SPSC为SPMC,构建落盘线程组实现,当task_queue满,触发扩容线程组。 2. 每个落盘线程私有一个SPSC,减少竞争。 3. 简易工作负载,生产者线程随机选取两个uring线程选取任务少的push。 3. 采用读写段采用轮询方案导致乒乓现象,性能下降。 1. MESI协议定义了缓存行的四种状态:Modified表示独占且已修改,Exclusive表示独占且未修改,Shared表示多核共享只读,Invalid表示缓存行无效。 - **关键点:** 但从S状态读没有什么开销,从I状态读则需要向其他CPU申请。 - **关键点:** 从E/M状态写也没有什么开销,从S状态写则需要广播invalidate并且等待ACK(50-100时钟周期)。 - 原子操作由于内存序的顺序性和可见性语义,也有额外开销(刷新invalidate queue、阻止指令重排) 2. 短临界区:分层退避。自旋-让出CPU时间片-较长时间休眠。允许生产者在期间无争用的写入一批数据,然后统一读。 3. 更通用的情况:事件驱动。用futex替代轮询。Fast Userspace Mutex。 1. **消费者:** 调用`syscall(SYS_futex, &futex_word, FUTEX_WAIT, old_val, &timeout, NULL, 0)` , 如果futex_word仍等于old_val,线程进入内核等待队列,真正睡眠,不占用CPU。 2. **生产者:** 调用`syscall(SYS_futex, &futex_word, FUTEX_WAKE, 1, NULL, NULL, 0)` 唤醒一个等待线程。 4. 优化:生产者速度 > 消费者速度,则写端不需要关注读指针,或者极少关注读指针(缓冲区大小的1/2)次写入才考虑一次。 #### 基于BinLog上OffSet的主从同步(已有数据+实时数据)设计。 1. 初始化阶段: 1. master 持续将收到的更新请求+seq_id 落盘到本地 binlog 中。 2. slave 向 master 发起连接并且发送本地binlog中最大的seq_id 为 slave_seq_id。 2. 执行阶段: 1. master启动一个独立同步线程与 slave 构建连接。同步线程有两个阶段: 1. slave_seq_id < local_min_seq_id:master通过fork创建内存快照发送,且发送同时刻的local_max_seq_id。 2. slave_seq_id < local_max_seq_id:通过自实现同步协议批量发送binlog的seq,并且回包new_slave_seq_id。 3. slave_seq_id == local_max_seq_id:线程休眠。 2. master收到新的请求的时候,会通过条件变量唤醒同步线程。 #### 基于ebpf的实时数据同步设计。 **基准性能**:Kvstore QPS ~90w(无持久化/同步)。 ebpf对程序的影响要考虑如下方面: 1. eBPF 探针的触发频率(上下文切换) 2. 数据拷贝方式 **用户态探针 (Uprobe) 的开销**, 1. **逐条命令探测**。QPS大概在 25w左右。**原因**:Uprobe 基于断点指令(int3)实现,用户态 → 异常 → 内核 → eBPF → 返回用户态,高频触发会导致 CPU 流水线停顿严重。 2. **批处理探测**。QPS大概在85w左右。大幅减少了上下文切换次数,平摊了中断开销。 **内核态探针 (Tracepoint/Kprobe) 的开销** 1. **Tracepoint (sys_exit_recvfrom)等** 1. 纯探测:QPS ~85w。 2. 带数据拷贝 (read_user):QPS 降至 ~70w。**原因**:**bpf_probe_read_user** 是一个非常重的 helper,开销远大于一次 memcpy。 2. **Kprobe (tcp_rcv_established)等** 1. 纯探测:QPS ~86w。 2. 带数据拷贝 (read_kernel):QPS ~83w。**原因**:比**bpf_probe_read_user**轻得多。 3. 问题:此时数据位于 TCP 协议栈底层,可能存在分片(Fragment)、乱序或非线性存储(Paged SKB),需要处理复杂的数据重组逻辑。 工作流程: 1. **内核态捕获**: 1. eBPF 程序挂载于内核网络路径( TC 或 TCP 层),拦截流量。 2. 使用 bpf_probe_read_kernel 或 bpf_skb_load_bytes 高效提取数据载荷。 3. 通过 bpf_ringbuf_submit 将数据写入环形缓冲区。 2. **用户态转发**: 1. 独立进程消费 RingBuffer。 2. 将数据暂存入本地队列,平滑突发流量,防止 RingBuffer 溢出导致的数据丢失。 3. **状态机控制**: 1. **SYNC 阶段**:探测到 __ssync,识别 Slave 连接信息(IP/Port),标记状态为“同步中”。 2. **READY 阶段**:探测到 __sready,标志全量同步完成。 3. **实时转发**:Agent 启动独立线程,消耗 Pending Queue,将增量数据通过标准 TCP 发送给 Slave。 ##### TC 与 XDP 网络栈 ``` -> [ 网卡 (NIC) ] -> [ XDP (eXpress Data Path) ] <--- xdp 处理原始帧 no skb -> [ sk_buff 分配 ] -> [ TC (Traffic Control) Ingress ] | tc 可操作 on skb -> [ Netfilter (PREROUTING) ] -> [ IP 协议栈 ] | ip_rcv -> ip_local_deliver -> [ TCP 协议栈 ] | tcp_v4_rcv -> tcp_rcv_established -> tcp_data_queue producer skb -> sk->sk_receive_queue ____________________________________________________________________ consumer sk->sk_receive_queue -> [ Socket Layer ] | tcp_recvmsg 拷贝到内存 -> [ Syscall Interface ] | sys_exit_recvfrom -> [ 用户态应用 (Kvstore) ] ``` XDP 是网卡驱动层的探点,操作系统刚刚收到数据包(DMA读入ringbuffer,CRC校验),还没有分配sk_buffer。数据形态是**裸的以太网帧** 。 TC 是在 sk_buff 分配之后,IP 协议栈处理之前的探测点。数据形态是__sk_buffer。 TCP协议栈是分界点, #### 内存泄露探测功能,实现热插拔。 方案1 基于bpf 1. 通过预定义宏__FILE__等封装一个内存分配宏定义,向真正的分配函数传递代码位置等信息。 2. 通过bpf探测内存分配的地址、大小、文件、代码位置、函数等信息,并且记录。 3. 通过bpf探测内存释放的指针信息,然后释放。 4. 打印最终剩余的内存地址。 5. 缺点:bpf 探测 malloc 等对性能的影响非常的大。 方案2 基于全局变量启用的代码内置探测工具,在网络层接收启用/关闭探测工具的请求。 1. 分配时打开一个文件,关闭时删除。 2. 分配时使用kv保存,删除时删除k。 都对性能和内存有很大的影响,不建议长时间启用。 #### 实现支持分配可变长度内存块的内存池。 1. 熟悉了glibc 的 ptmalloc的底层操作。默认阈值约 128KB,且会根据分配行为动态调整。 1. '<= 默认阈值 通过 brk/sbrk 扩展连续堆,堆里维护了 chunk 结构。 2. '> 默认阈值 的块用 mmap 独立申请,释放时 munmap。 3. 每个 chunk 头部存大小信息(通常 8~16 字节),用户拿到的是 chunk + 头部后的地址。 4. 空闲 chunk 按大小分到多个 bin(tcache、fastbin、small bin、large bin 等),fastbin 和 tcache 为了速度不合并相邻空闲块。 5. 缺点: 1. 分配路径有较多分支判断和链表遍历,不是严格 O(1)。 2. 小块故意不合并(fastbin/tcache)会导致外部碎片。 3. 长期运行内存利用率下降。 2. 内存池实现: 1. 内存池有多个桶,桶中存储固定大小的块。分配/释放均为 O(1) 2. 以 huge page 为单位向系统申请内存并切分为固定块。 3. 当页内块全部释放时整页归还系统,显著降低长期碎片。 4. 通过地址对齐 O(1) 反推出页头元信息(所属桶、空闲计数)。 5. malloc通常向上对齐,桶对应的存储大小可以根据不同系统设定,减少内部碎片。 6. 使用 bitmap + freelist 防 double free 且无额外查找开销。 7. 每线程独立内存池,相对malloc更少的锁竞争。 8. 大块分配自动退化为 malloc 处理。 相比 ptmalloc,该设计消除了外部碎片,降低了系统调用次数,并在多线程场景下显著提升分配性能与内存利用率。