介绍
在去年的一篇文章中,笔者曾经提到了最新一代的网络I/O框架UringNet。具体内容可以参考Rings’ Power,性能“世界第一”的Web I/O框架。这是基于最新Linux内核的异步I/O组件io_uring开发的网络框架。由于采用了最新的异步框架,因此在同等硬件配置条件下,UringNet比目前性能最好的基于select/epoll的框架的网络I/O的峰值数据还要高10%以上。目前,已经将该框架开源https://github.com/y001j/UringNet,有兴趣的读者可以进行尝试,如果有任何问题也可以联系作者进行讨论。
io_uring是一个全新设计的Linux异步I/O框架,通过合理设计并对参数进行合理调整,能够达到非常高的性能,尤其在不需要缓存的硬件和磁盘I/O中有非常好的表现,例如RockDB中就加入了对于io_uring的支持。但是将io_uring应用到高性能的网络框架中确实一个比较有挑战的领域。这是由于网络访问通常需要依赖操作系统的网络栈,以及读写缓存,而缓存机制又会涉及到内核态和有用户态的切换。此前和其他开发者交流,往往认为在高性能网络I/O领域可能并不适合采用io_uring。不过通过UringNet的实践来看,io_uring的不但适合用于网络I/O,而且还能够获得非常不错的性能。
对于io_uring的介绍,在这里就不再进行详细介绍了,下面列出了对这个技术的参考文档:
- Lord of the io_uring
- Ringing in a new asynchronous I/O API
- An Introduction to the io_uring Asynchronous I/O Framework (oracle.com)
- io_uring.pdf (kernel.dk)
- Linux高性能异步I/O接口io_uring
为什么我创建了这个项目?
UringNet是一个高性能和轻量级的网络I/O框架。使用Golang语言开发。UringNet是基于Linux内核版本5.1引入的最新新异步I/O接口io_uring开发的。这个项目最初来自于我在边缘计算和物联网研究中的实验性项目。我最初想找到一种方法来构建一个简单但高性能的网络数据传输工具,用于物联网网关。我开始的时候尝试传统的select/epoll,然后测试了io_uring在物联网数据传输的表现,发现io_uring性能更好。
是否应该在你的项目中?
尽管UringNet最初是为物联网平台设计的,但它也提供了基本的TCP和UDP网络功能。如果你的项目确实需要处理超大的数据流量,你可以尝试使用它。
在大多数情况下,你可能只需使用更成熟的网络框架,例如Go的net/http、Netty或libuv。
入门指南
UringNet参考了现有的网络框架,如gnet和Netty等,某些使用模式与gnet非常相似。请注意,UringNet基于io_uring,因此需要在运行在内核版本高于或等于5.1的Linux操作系统中。
如何使用这个框架,简单的echo程序:
package mainimport ("github.com/y001j/UringNet"socket "github.com/y001j/UringNet/sockets""os""sync"
)type testServer struct {UringNet.BuiltinEventEnginetestloop *UringNet.Ringloop//ring *uring_net.URingNetaddr stringmulticore bool
}// OnTraffic
//
// @Description: OnTraffic is a hook function that runs every read event completed
// @receiver ts
// @param data
// @return uring_net.Action
func (ts *testServer) OnTraffic(data *UringNet.UserData, ringnet UringNet.URingNet) UringNet.Action {data.WriteBuf = data.Bufferreturn UringNet.Echo
}func (ts *testServer) OnWritten(data UringNet.UserData) UringNet.Action {return UringNet.None
}func (ts *testServer) OnOpen(data *UringNet.UserData) ([]byte, UringNet.Action) {return nil, UringNet.None
}func main() {addr := os.Args[1]options := socket.SocketOptions{TCPNoDelay: socket.TCPNoDelay, ReusePort: true}ringNets, _ := UringNet.NewMany(UringNet.NetAddress{socket.Tcp4, addr}, 3200, true, runtime.NumCPU(), options, &testServer{}) //runtime.NumCPU()loop := UringNet.SetLoops(ringNets, 3000)var waitgroup sync.WaitGroupwaitgroup.Add(1)loop.RunMany()waitgroup.Wait()
}
警告:该项目仍在开发中,可能存在一些bug和性能问题。如果您发现任何错误或有任何建议,请随时提出问题。邮箱rocky@yangjian.co
提示:
- 在我们使用wrk进行性能测试时,我们观察到UringNet在进行HTTP压力测试时需要一个预热时间。通常情况下,需要约5分钟的预热(用wrk预先压测5分钟),以确保UringNet测试代码达到其性能峰值。目前还不太清楚为什么io_uring需要预热才能够达到压测峰值性,该问题任然在研究中。
- UringNet的性能表现和参数设置密切相关,主要影响的参数有,
- 线程数num,用于设定启用的io_uring实例数量,一般设置为(CPU总数±1)。
- sqpoll这个参数通常设置为true能够达到更好的性能,不过CPU占用会上升。
- size指的是SQ的大小,也就是输入队列的大小,如果这个值过小,有可能会报错。
// NewMany Create multiple uring instances
//
// @Description:
// @param addr
// @param size set SQ size
// @param sqpoll if set sqpoll to true, io_uring submit SQs automatically without enter syscall.
// @param num number of io_uring instances need to be created
// @return *[]URingNet
// @return error
UringNet.NewMany(UringNet.NetAddress{socket.Tcp4, addr}, 3200, true, runtime.NumCPU(), options, &testServer{})