概述
这篇文章是多年以前的文章了,但是因为最近清理收藏夹才再次翻看,所以内容可能和最新的现状有些出入,请辩证阅读。
Sockmap 是 Linux 下用于 socket 拼接的一个工具,它可以在反向代理时不经过应用层而直接在内核就将流量反向代理到其他的目标端,这对于高负载的网络代理应用程序是一个很有意思并且很有探索价值的一个应用。
为什么叫 sockmap,首先是在 eBPF 中,数据的传递的数据结构就叫 Map,eBPF 提供了很多 Map,而 sockmap 是比较新的一种,由 Cilium 的工程师 John Fastabend 创建。
传统方式的弱点
为什么要使用 sockmap 呢?这就要回到没有 sockmap 之前我们遇到了什么问题。在 Linux 系统中,如果我们要在 socket 和其他数据流结构中扭转数据,常用系统调用的有:
系统调用 | 通信双方 | 需要用户空间内存吗 | 是否零拷贝 |
---|---|---|---|
sendfile | disk file —> socket | yes | no |
splice | pipe <—> socket | yes | yes? |
vmsplice | memory region —> pipe | no | yes |
他们的问题分别是:
- 系统调用成本:这些函数本身就是系统调用,为每个转发的数据包进行多次系统调用是很昂贵的。
- 唤醒延迟:用户空间的进程必须经常被唤醒以转发数据。根据调度器的不同,这可能会导致尾部延时不佳。
- 复制成本:将数据从内核复制到用户空间,然后立即返回到内核,这不是免费的,加起来的成本是可衡量的。
splice() 和 sendfile() 的额外成本
虽然 splice() 和 sendfile() 系统调用在一定程度上优化了 TCP 数据转发的性能,避免了内核与用户空间之间的数据复制,但仍然存在一些性能瓶颈:
- 系统调用的开销:splice() 和 sendfile() 都是系统调用,每次调用它们时,都需要在用户空间和内核空间之间进行上下文切换。这些上下文切换会增加额外的开销,从而影响性能。
- 多次数据缓冲区操作:虽然 splice() 和 sendfile() 可以在内核空间完成数据传输,但它们仍然需要多次对数据缓冲区进行操作。例如,在 splice() 中,数据需要从一个套接字的接收缓冲区移动到管道缓冲区,然后再移动到另一个套接字的发送缓冲区。这些额外的数据缓冲区操作可能会影响性能。
- 额外的内存分配和释放:splice() 和 sendfile() 需要在内核空间为数据传输分配和释放内存。这些内存操作可能导致额外的开销,并且在高负载情况下可能导致内存碎片。
- 无法完全避免数据复制:在某些情况下,sendfile() 无法完全避免数据复制。例如,当需要对数据进行处理(如加密)时,sendfile() 无法直接在内核空间完成这些操作,因此可能需要将数据复制回用户空间进行处理。
所以 splice() 和 sendfile() 在数据传输过程中减少了内核与用户空间之间的数据复制,但它们仍然存在一些性能瓶颈,如系统调用的开销、多次数据缓冲区操作、额外的内存分配和释放,以及在某些情况下无法完全避免数据复制。这些性能瓶颈可能限制了这些方法在高性能场景中的表现。
一些性能比较
系统调用次数 | 用户空间唤醒 | 拷贝次数 | |
---|---|---|---|
read write loop | 2 syscalls | yes | 2 copies |
splice | 2 syscalls | yes | 0 copy (?) |
io_submit | 1 syscall | yes | 2 copies |
SOCKMAP | none | no | 0 copies |
Clouflare 的实践
Cloudflare 在其边缘网络中使用 Sockmap 实现了 TCP Splicing。在这个网络中,每个请求都经过一个负载均衡器,负载均衡器将请求转发给后端服务器。通过使用 Sockmap,Cloudflare 能够在负载均衡器上实现高效的请求转发。
虽然 Sockmap 很有前途,但也存在一些限制和挑战,例如与其他内核特性的兼容性问题,以及对复杂网络环境的支持问题,例如:
- 内核版本:Sockmap 是 Linux 内核的新特性,只有新版的内核才支持。这意味着使用这项技术需要更新内核,可能会影响到已经稳定运行的系统。
- 兼容性问题:Sockmap 和某些内核特性可能存在兼容性问题。例如,Sockmap 目前无法与 SO_ORIGINAL_DST 套接字选项共同使用,这可能会对那些依赖该选项的应用造成影响。
- 支持范围:目前,Sockmap 只支持 TCP 协议,并且不支持所有的 TCP 选项。这可能会限制 Sockmap 在某些特定的网络环境中的使用。
- 错误处理:使用 Sockmap 进行数据转发时,如果遇到错误,如何进行错误处理并恢复数据传输是一个挑战。
- 资源管理:Sockmap 会使用一些内核资源,如何管理这些资源,防止资源耗尽或内存泄漏,是需要注意的问题。
- 安全性:虽然 Sockmap 本身设计为安全的,但由于它可以控制内核中的网络数据,因此可能会成为攻击者的目标。如何保护 Sockmap 不被滥用,防止可能的安全风险,也是一个需要考虑的问题。
测试数据
我不想使用文章中的数据,本来我想自己 bench 一下的,但是一直拖拉没有去实践,所以就先欠着,我会在后面我介绍 eBPF 开发的过程中,顺便把 bench 的程序写了,然后再进行一次 bench,然后再把它填补进来。