29

我使用 boost::asio 在 C++ 中开发了一个迷你 HTTP 服务器,现在我正在使用多个客户端对其进行负载测试,但我一直无法接近 CPU 饱和。我在 Amazon EC2 实例上进行测试,一个 cpu 的使用率约为 50%,另一个 cpu 的使用率为 20%,其余两个处于空闲状态(根据 htop)。

细节:

  • 服务器每个核心启动一个线程
  • 接收、解析、处理请求并写出响应
  • 请求是从内存中读取的数据(本测试只读)
  • 我正在使用两台机器“加载”服务器,每台机器运行一个 java 应用程序,运行 25 个线程,发送请求
  • 我看到大约 230 个请求/秒的吞吐量(这是应用程序请求,由许多 HTTP 请求组成)

那么,我应该看什么来改善这个结果呢?鉴于 CPU 大多处于空闲状态,我想利用额外的容量来获得更高的吞吐量,比如 800 个请求/秒或其他什么。

我有过的想法:

  • 请求非常小,通常在几毫秒内完成,我可以修改客户端以发送/组合更大的请求(可能使用批处理)
  • 我可以修改 HTTP 服务器以使用 Select 设计模式,这适合吗?
  • 我可以做一些分析来尝试了解瓶颈是什么
4

6 回答 6

46

boost::asio 并不像你希望的那样对线程友好——在 boost/asio/detail/epoll_reactor.hpp 中的 epoll 代码周围有一个大锁,这意味着一次只有一个线程可以调用内核的 epoll 系统调用. 对于非常小的请求,这一切都不同(这意味着您只会看到大致的单线程性能)。

请注意,这是 boost::asio 如何使用 Linux 内核工具的限制,不一定是 Linux 内核本身。在使用边缘触发事件时,epoll 系统调用确实支持多线程,但是要正确处理(没有过度锁定)可能非常棘手。

顺便说一句,我在这方面做了一些工作(将完全多线程的边缘触发 epoll 事件循环与用户调度的线程/光纤相结合)并在nginetd项目下提供了一些代码。

于 2009-08-06T11:30:39.067 回答
12

当您使用 EC2 时,所有赌注都已取消。

使用真正的硬件尝试一下,然后您可能会看到发生了什么。尝试在虚拟机中进行性能测试基本上是不可能的。

我还没有弄清楚 EC2 有什么用,如果有人发现,请告诉我。

于 2009-08-06T12:59:37.173 回答
3

从您对网络利用率的评论来看,
您似乎没有太多的网络活动。

3 + 2.5 MiB/sec50Mbps球场附近(与您的 1Gbps 端口相比)。

我会说您遇到以下两个问题之一,

  1. 工作量不足(来自客户的低请求率)
    • 在服务器中阻塞(干扰响应生成)

查看cmeerw的注释和您的 CPU 利用率数据
(闲置在50% + 20% + 0% + 0%
,这似乎很可能是您的服务器实现的一个限制。
我第二个cmeerw答案(+1)。

于 2009-08-06T11:46:17.363 回答
3

对于这种简单的异步请求,每秒 230 个请求似乎非常低。因此,使用多线程可能是过早的优化——让它正常工作并在单个线程中进行调整,看看你是否仍然需要它们。只是摆脱不必要的锁定可能会使事情加快速度。

这篇文章有一些关于 2003 年左右 Web 服务器式性能的 I/O 策略的详细信息和讨论。有人有最新的吗?

于 2009-08-06T12:51:26.140 回答
2

ASIO 适用于中小型任务,但不太擅长利用底层系统的功能。原始套接字调用,甚至 Windows 上的 IOCP 都不是,但如果您有经验,您将永远比 ASIO 更好。无论哪种方式,所有这些方法都有很多开销,而 ASIO 则更多。

为了它的价值。在我的自定义 HTTP 上使用原始套接字调用可以使用 4 核 I7 每秒处理 800K 动态请求。它从 RAM 提供服务,这是您需要达到该级别性能的地方。在这种性能水平上,网络驱动程序和操作系统消耗了大约 40% 的 CPU。使用 ASIO,我每秒可以获得大约 50 到 100K 的请求,它的性能变化很大,并且主要受我的应用程序的约束。@cmeerw 的帖子主要解释了原因。

提高性能的一种方法是实现 UDP 代理。拦截 HTTP 请求,然后通过 UDP 将它们路由到后端 UDP-HTTP 服务器,您可以绕过操作系统堆栈中的大量 TCP 开销。您还可以让前端通过 UDP 自己通过管道,这不应该太难自己做。HTTP-UDP 代理的一个优点是它允许您使用任何好的前端而无需修改,并且您可以随意更换它们而不会产生任何影响。你只需要更多的服务器来实现它。对我的示例进行的此修改将操作系统 CPU 使用率降低到 10%,这使我在该单个后端上的每秒请求数增加到刚刚超过一百万。

未来似乎是编写自己的驱动程序来实现自己的网络堆栈,这样您就可以尽可能接近请求并在那里实现自己的协议。这可能不是大多数程序员想要听到的,因为它更复杂。就我而言,我将能够多使用 40% 的 CPU 并达到每秒超​​过 100 万个动态请求。UDP 代理方法可以让您接近最佳性能而无需这样做,但是您将需要更多服务器 - 尽管如果您每秒执行这么多请求,您通常需要多个网卡和多个前端来处理带宽,因此拥有里面有几个轻量级的 UDP 代理没什么大不了的。

希望其中一些对您有用。

于 2016-02-24T01:35:12.873 回答
0

How many instances of io_service do you have? Boost asio has an example that creates an io_service per CPU and use them in the manner of RoundRobin.

You can still create four threads and assign one per CPU, but each thread can poll on its own io_service.

于 2016-06-19T12:14:09.057 回答