我有一个用 C 语言编写的守护进程,它需要同时处理 20-150K 的 TCP 连接。它们是长期运行的连接,很少会断开。它们在任何给定时间传输的数据量都非常少(甚至很少超过 MTU。这是一种刺激/响应协议),但对它们的响应时间至关重要。我想知道当前的 UNIX 社区使用什么来获取大量套接字,并最大限度地减少它们的响应延迟。我已经看到围绕多路复用连接到分叉工作池、线程(每个连接)、静态大小的线程池的设计。有什么建议么?
6 回答
最简单的建议是使用libevent,它可以轻松编写符合您要求的简单非阻塞单线程服务器。
如果每个响应的处理需要一些时间,或者如果它使用一些阻塞 API(就像数据库中的几乎任何东西),那么您将需要一些线程。
一个答案是工作线程,您可以在其中生成一组线程,每个线程都在侦听某个队列以工作。如果您愿意,它可以是单独的进程,而不是线程。主要区别在于告诉工人该做什么的通信机制。
另一种方法是使用多个线程,并为每个线程分配这 150K 连接的一部分。每个都有自己的进程循环,并且主要像单线程服务器一样工作,除了侦听端口,它将由单个线程处理。这有助于在内核之间分散负载,但如果您使用阻塞资源,它将阻塞由该特定线程处理的所有连接。
如果你小心的话,libevent 允许你使用第二种方式;但还有另一种选择:libev。它不像 libevent 那样广为人知,但它特别支持多循环方案。
如果性能很关键,那么您真的很想使用多线程事件循环解决方案——即一个工作线程池来处理您的连接。不幸的是,没有抽象库可以在大多数 Unix 平台上执行此操作(请注意,libevent 和大多数这些事件循环库一样只是单线程的),因此您必须自己做这些脏活。
在 Linux 上,这意味着使用带有工作线程池的边缘触发 epoll(Windows 将具有 I/O 完成端口,这在多线程环境中也可以正常工作——我不确定其他 Unix)。
顺便说一句,我已经做了一些工作,试图在 Linux 和 Windows I/O 完成端口上抽象边缘触发的 epollhttp://nginetd.cmeerw.org(正在进行中,但可能会提供一些想法)。
如果您具有系统配置访问权限,请不要过度执行此操作并设置一些 iptables/pf/etc 以跨 n 个守护程序实例(进程)进行负载平衡连接,因为这将开箱即用。取决于如何阻塞守护进程 n 的性质应该来自系统上的核心数量或高几倍。这种方法看起来很粗糙,但它可以处理损坏的守护程序,甚至在必要时重新启动它们。迁移也会很顺利,因为您可以开始将新连接转移到另一组进程(例如,新版本或迁移到新盒子)而不是服务中断。最重要的是,您可以获得诸如源亲和性之类的几个功能,这些功能可以帮助显着缓存和争用有问题的会话。
如果您没有系统访问权限(或者无法打扰操作),您可以使用负载均衡器守护程序(有很多开源的)而不是 iptables/pf/etc 并使用 n 服务守护程序,如上所示。
这种方法也有助于分离端口的特权。如果外部服务需要在低端口(<1024)上提供服务,您只需要运行特权/或管理员/根或内核的负载均衡器。)
我过去写过几个 IP 负载均衡器,它在生产中很容易出错。您不想支持和调试它。此外,运营和管理更倾向于对您的代码进行第二次猜测,而不是外部代码。
我认为哈维尔的回答最有意义。如果您想测试理论,请查看node javascript 项目。
Node 基于 Google 的 v8 引擎,该引擎将 javascript 编译为机器代码,并且在某些任务上与 c 一样快。它也基于 libev 并且被设计为完全非阻塞的,这意味着您不必担心线程之间的上下文切换(一切都在单个事件循环上运行)。在这方面它与 erlang 非常相似。
现在,使用 node.js 编写高性能服务器真的非常容易。您还可以通过一些努力,在 c 中编写您的自定义代码并为节点创建绑定以调用它来进行实际处理(查看节点源代码以了解如何执行此操作 - 文档在此时此刻)。作为更丑陋的替代方案,您可以将自定义 c 代码构建为应用程序并使用 stdin/stdout 与其通信。
我自己测试了超过 150k 连接的节点,绝对没有问题(当然,如果所有这些连接要同时进行通信,您将需要一些严肃的硬件)。node.js 中的 TCP 连接平均只使用 2-3k 内存,因此理论上每 1GB RAM 可以处理 350-500k 连接。
注意 - Windows 目前不支持 Node.js,但它仅处于开发的早期阶段,我想它会在某个阶段被移植。
注 2 - 您必须确保从 Node 调用的代码不会阻塞
你必须从头开始吗?你可以使用类似gearman的东西。