我正在 linux 下开发一个应用程序,它需要支持大约 250 个连接,并通过 TCP 套接字传输 100MB+ 大小范围内的大文件。目的是调整吞吐量而不是延迟。我想始终保持饱和的 2x1Gbit 以太网连接。这些将被频道绑定。
磁盘 IO 通常比网络慢。 250 个客户端对于现代 CPU 来说不算什么。
文件有多大并不重要。真正的问题是数据总量是否适合 RAM - 是否可以扩展 RAM 以使数据适合它。如果数据适合 RAM,那么就不要过度优化:带有sendfile() 的哑单线程服务器就可以了。
应考虑使用 SSD 进行存储,尤其是在优先读取数据的情况下。
预计应用程序将持续忙碌,并且会尽快丢弃数据。连接大部分时间都将保持连接,因此与 HTTP 不同,它们不会经常断开。
“尽可能快”是灾难的秘诀。我至少要为这样的多线程灾难负责,因为它导致的磁盘搜索量根本无法扩展。
通常,您可能希望每个存储有几个(例如 4 个)磁盘读取线程,这将调用 read() 或 sendfile() 用于非常大的块,以便操作系统有机会优化 IO。需要很少的线程,因为人们希望乐观地认为某些数据可以从操作系统的 IO 缓存中并行提供。
不要忘记设置大的套接字发送缓冲区。在您的情况下,轮询套接字的可写能力也是有意义的:如果客户端无法像您读取/发送一样快地接收到,那么读取是没有意义的。您服务器上的网络通道可能很胖,但客户端 NIC/磁盘不是这样。
我一直在研究各种选项,例如 epoll、sendfile api 等以获得高性能和 aio(恕我直言,这看起来太不成熟和有风险)。
现在几乎所有的 FTP 服务器都使用sendfile()。 Oracle 使用 AIO,Linux 是他们的主要平台。
我也一直在研究 boost asio api,它在下面使用了 epoll。我以前用过它,但没有用于这样的高性能应用程序。
仅适用于套接字的 IIRC。 IMO 任何有助于处理套接字的实用程序都可以。
我有超过 4 个可用的处理器内核,所以我可以利用它。
TCP 由 NIC 加速,磁盘 IO 主要由控制器自己完成。理想情况下,您的应用程序会处于空闲状态,等待磁盘 IO。
但是,我读到 boost asio 对于多线程不是很好,因为反应堆设计中有一些锁定。这对我来说可能是个问题吗?
检查libevent 作为替代。您可能只需要 sendfile() 的有限数量的线程。并且数量应该是有限的,否则你会在磁盘寻道时扼杀吞吐量。
如果我有很多可用的 CPU 内核,我是否应该创建尽可能多的线程或分叉进程并将它们设置为在每个处理器内核上运行?
没有。磁盘受寻道的影响最大。 (我是否重复了足够多的时间?)如果您有许多自主读取线程,您将失去控制发送到磁盘的 IO 的可能性。
考虑最坏的情况。所有read()s 都必须转到磁盘 == 更多线程,更多磁盘寻道。
考虑最好的情况。所有read()s 都从缓存中提供== 根本没有IO。然后你正在以 RAM 的速度工作,可能根本不需要线程(RAM 比网络快)。
锁定等怎么样。我想要一些设计建议。我怀疑我的主要瓶颈将是磁盘 I/O,但尽管如此......
这是一个答案很长的问题,这里不适合(我也没有时间写)。而且这在很大程度上还取决于您要提供多少数据、您使用的存储类型以及访问存储的方式。
如果我们将 SSD 作为存储,那么任何愚蠢的设计(例如为每个客户端启动一个线程)都可以正常工作。如果您在后端有真正的旋转媒体,那么您必须对来自客户端的 IO 请求进行切片和排队,试图避免一侧的客户端饥饿,而另一侧则以一种导致尽可能少的搜索量的方式调度 IO。
我个人会从在主循环中包含 poll()(或 boost.asio 或 libevent)的简单单线程设计开始。如果数据被缓存,则没有必要启动新线程。如果必须从磁盘获取数据,单线程将确保我避免查找。用读取的数据填充套接字缓冲区并更改等待 POLLOUT 模式,以了解客户端何时消耗了数据并准备好接收下一个块。这意味着我将在主循环中至少拥有三种类型的套接字:侦听套接字、我正在等待请求的客户端套接字、我正在等待的客户端套接字再次变为可写。
我希望前期有一个好的设计,以后不需要太多返工。
啊……好梦……