在我尽力研究实现消息队列服务器的最佳方法之后,我问了这个问题。为什么操作系统会限制进程和全局系统可以拥有的打开文件描述符的数量? 我当前的服务器实现使用zeromq,并为每个连接的websocket客户端打开一个订阅者套接字。显然,单个进程只能将客户端处理到fds的极限。 当我研究这个主题时,我发现了很多关于如何将系统限制提高到高达64k fds的信息,但它从未提到它如何影响系统性能以及为什么它开始时为1k或更低? 我目前的方法是尝试使用自己的循环中的协程向所有客户端发送消息,以及所有客户端及其订阅通道的映射。但我只是想听听关于文件描述符限制的可靠答案,以及它们如何影响试图在每个客户端级别使用持久连接的应用程序?
答案 0 :(得分:13)
可能是因为文件描述符值是文件描述符表的索引。因此,可能的文件描述符的数量将决定表的大小。普通用户不希望他们的一半ram被文件描述符表用尽,该表可以处理他们永远不需要的数百万个文件描述符。
答案 1 :(得分:4)
出于性能目的,需要静态分配打开的文件表,因此需要修复其大小。文件描述符只是该表的偏移量,因此所有条目都需要是连续的。您可以调整表的大小,但这需要暂停进程中的所有线程并为文件表分配新的内存块,然后将旧表中的所有条目复制到新表。这不是你想要动态做的事情,特别是当你这样做的原因是因为旧桌子已满了!
答案 2 :(得分:3)
当您有大量潜在文件描述符时,某些操作会变慢。一个例子是操作“关闭除stdin
,stdout
和stderr
之外的所有文件描述符” - 唯一可行的*方法是尝试关闭除了每个可能的文件描述符,除了这三个,如果你可能有数百万个文件描述符打开,这可能会变得很慢。
*:如果你愿意不携带,你可以查看/proc/self/fd
- 但这不是重点。
这不是一个特别好的理由,但它是 的原因。另一个原因就是保持一个错误的程序(即“泄漏”文件描述符的程序)不会消耗太多的系统资源。
答案 3 :(得分:0)
在unix系统上,进程创建fork()和fork()/ exec()惯用法需要迭代所有可能关闭每个进程文件描述符的进程文件描述符,通常只留下一些文件描述符,如stdin,stdout, stderr未触及或重定向到其他地方。
由于 用于启动进程的unix api,因此必须在创建新进程时随时执行该操作,包括执行在shell脚本中调用的每个非内置命令。
需要考虑的其他因素是,虽然某些软件可能会使用sysconf(OPEN_MAX)
来动态确定进程可能打开的文件数,但很多软件仍使用C库的默认{{1}通常是1024个描述符,因此无论管理层定义的上限是多少都不会打开多个文件。
Unix有一个基于文件描述符集的遗留异步I / O机制,它使用位偏移来表示要等待的文件以及准备好或处于异常情况的文件。它不能很好地扩展到数千个文件,因为每次围绕runloop需要设置和清除这些描述符集。更新的非标准apis出现在主要的unix变体上,包括{BSD上的FD_SETSIZE
和Linux上的kqueue()
,以解决处理大量描述符时的性能缺陷。
值得注意的是,很多时候,epoll()
仍被大量软件使用,因为它已成为异步I / O的POSIX api。现代POSIX异步IO方法现在是select()/poll()
API,但它可能与aio_*
或kqueue()
API没有竞争关系。我没有在愤怒中使用过aio,它肯定不具备本机方法提供的性能和语义,因为它们可以聚合多个事件以获得更高的性能。 * BSD上的kqueue()具有非常好的边缘触发语义用于事件通知,允许它替换select()/ poll()而不强制对应用程序进行大的结构更改。 Linux epoll()遵循* BSD kqueue()的引导并对其进行了改进,而后者又跟随Sun / Solaris的推出。
结果是,增加系统中允许的打开文件数量会增加系统中每个进程的时间和空间开销,即使他们无法根据他们使用的api使用这些描述符。对于允许的打开文件数量,还有聚合系统限制。这个older but interesting tuning summary for 100k-200k simultaneous connections using nginx on FreeBSD提供了一些洞察力来保持开放式连接和another one covering a wider range of systems,但“仅”看到10K连接作为Mt Everest。
可能是unix系统编程的最佳参考是W. Richard Stevens Advanced Programming in the Unix Environment