【问题标题】:Design choices for high performance file serving高性能文件服务的设计选择
【发布时间】:2010-07-14 23:24:06
【问题描述】:

我正在 linux 下开发一个应用程序,它需要支持大约 250 个连接,并通过 TCP 套接字传输 100MB+ 大小范围内的大文件。目的是调整吞吐量而不是延迟。我想始终保持饱和的 2x1Gbit 以太网连接。这些将被频道绑定。

预计应用程序将持续忙碌,并且会尽快丢弃数据。连接大部分时间都会保持连接,因此与 HTTP 不同,它们不会经常断开。

我一直在研究各种选项,例如 epoll、sendfile api 等以获得高性能和 aio(恕我直言,这看起来太不成熟和有风险)。

我也一直在研究 boost asio api,它在下面使用了 epoll。我以前用过它,但没有用于这样的高性能应用程序。

我有超过 4 个可用的处理器内核,所以我可以利用它。

但是,我读到 boost asio 对于多线程不是很好,因为反应堆设计中有一些锁定。这对我来说可能是个问题吗?

如果我有很多可用的 CPU 内核,我是否应该创建尽可能多的线程或分叉进程并将它们设置为在每个处理器内核上运行?

锁定等怎么样。我想要一些设计建议。我怀疑我的主要瓶颈将是磁盘 I/O,但尽管如此......我想要一个好的设计,以后不需要太多的返工。

有什么建议吗?

【问题讨论】:

  • 磁盘 I/O 和潜在的客户端网络速度将成为您的瓶颈。 250 个连接是否代表 250 个唯一客户端,或者是否可以选择使用并行连接?客户端是在 LAN/trunk 连接上还是在较慢的链接上?
  • 我应该指出,对于单个连接,磁盘 I/O 最常被顺序读取。
  • 2 年过去了,事实证明 Boost ASIO 的线程扩展性真的很差。我不会将它用于任何高性能。我使用的是基于 epoll 的方法。性能很棒。

标签: linux performance multithreading boost


【解决方案1】:

我正在 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 模式,以了解客户端何时消耗了数据并准备好接收下一个块。这意味着我将在主循环中至少拥有三种类型的套接字:侦听套接字、我正在等待请求的客户端套接字、我正在等待的客户端套接字再次变为可写。

我希望前期有一个好的设计,以后不需要太多返工。

啊……好梦……

【讨论】:

  • 感谢您的指点。我想答案从来没有那么简单,所以我将从一个简单的解决方案开始,看看它是如何执行的。
【解决方案2】:

sendfile() 绝对是您从磁盘文件发送大量顺序数据的最佳选择。 epoll() 不太可能特别有用 - 它主要在您处理大量 连接时提供帮助。 250 根本不是很大,所以普通的旧 select()poll() 可能一样好。

【讨论】:

    【解决方案3】:

    恕我直言,您的主要问题将是磁盘 I/O - 文件服务通常不受 CPU 限制,因此许多内核不一定有太大帮助。如果您提供许多不同的文件,就像您暗示的那样,事情会变得更糟;在这一点上,同时从磁盘读取会给你带来很大的痛苦。

    我会尝试在内存中缓存尽可能多的数据并尝试提供这些数据以加快速度。

    【讨论】:

    • Linux 上无需在内存中缓存某些内容。 sendfile() + Linux 自己的 I/O 缓存已经做好了。
    【解决方案4】:

    做可能可行的最简单的事情,因为听起来即使这样也可能没有您可以在代码中修复的性能问题。

    每个客户端一个线程听起来不错,它使编程尽可能简单。理想情况下,根本不要编写自定义文件服务器,而是使用已经存在的文件服务器 - http、rsync 等。rsync 协议适用于许多小文件,因为它支持流水线。

    拥有 250 个线程确实没问题 - 事实上,1000 个也可以。

    根据文件是否适合 ram,以及您的 IO 有多快,您可能会遇到网络瓶颈。如果您的网络只有 1-2Gbit/sec,那么您的存储似乎可以在顺序 IO 上击败它,因此网络将成为瓶颈。

    【讨论】:

    • 谢谢,我想网络肯定是瓶颈。我不能使用现有的协议。这都是有充分理由的习惯。
    猜你喜欢
    • 2013-06-26
    • 1970-01-01
    • 2018-05-13
    • 1970-01-01
    • 1970-01-01
    • 2013-04-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多