龙卷风“error: [Errno 24] Too many open files”错误

5

我经常使用Tornado,但这是我第一次遇到这种错误。我正在开发一个非常基础的URL缩短器。URL由另一个应用程序放入数据库中,而这个应用程序只是从MongoDB存储中读取URL并重定向客户端。在编写了基本代码之后,我对其进行了简单的'Siege'测试,在大约30秒的siege运行之后(使用siege -c 64 -t 5m -r 1 http://example.com/MKy对4个应用程序线程进行测试),我开始收到500响应。查看错误日志,我看到了这个;

ERROR:root:500 GET /MKy (127.0.0.1) 2.05ms
ERROR:root:Exception in I/O handler for fd 4
Traceback (most recent call last):
  File "/opt/python2.7/lib/python2.7/site-packages/tornado-2.1-py2.7.egg/tornado/ioloop.py", line 309, in start
  File "/opt/python2.7/lib/python2.7/site-packages/tornado-2.1-py2.7.egg/tornado/netutil.py", line 314, in accept_handler
  File "/opt/python2.7/lib/python2.7/socket.py", line 200, in accept
error: [Errno 24] Too many open files
ERROR:root:Uncaught exception GET /MKy (127.0.0.1)
HTTPRequest(protocol='http', host='shortener', method='GET', uri='/MKy', version='HTTP/1.0', remote_ip='127.0.0.1', body='', headers={'Host': 'shortener', 'Accept-Encoding': 'gzip', 'X-Real-Ip': '94.23.155.32', 'X-Forwarded-For': '94.23.155.32', 'Connection': 'close', 'Accept': '*/*', 'User-Agent': 'JoeDog/1.00 [en] (X11; I; Siege 2.66)'})
Traceback (most recent call last):
  File "/opt/python2.7/lib/python2.7/site-packages/tornado-2.1-py2.7.egg/tornado/web.py", line 1040, in wrapper
  File "main.py", line 58, in get
  File "main.py", line 21, in dbmongo
  File "/opt/python2.7/lib/python2.7/site-packages/apymongo-0.0.1-py2.7-linux-x86_64.egg/apymongo/connection.py", line 349, in __init__
  File "/opt/python2.7/lib/python2.7/site-packages/apymongo-0.0.1-py2.7-linux-x86_64.egg/apymongo/connection.py", line 510, in __find_master
  File "/opt/python2.7/lib/python2.7/site-packages/apymongo-0.0.1-py2.7-linux-x86_64.egg/apymongo/connection.py", line 516, in __try_node
  File "/opt/python2.7/lib/python2.7/site-packages/apymongo-0.0.1-py2.7-linux-x86_64.egg/apymongo/database.py", line 301, in command
  File "/opt/python2.7/lib/python2.7/site-packages/apymongo-0.0.1-py2.7-linux-x86_64.egg/apymongo/collection.py", line 441, in find_one
  File "/opt/python2.7/lib/python2.7/site-packages/apymongo-0.0.1-py2.7-linux-x86_64.egg/apymongo/cursor.py", line 539, in loop
  File "/opt/python2.7/lib/python2.7/site-packages/apymongo-0.0.1-py2.7-linux-x86_64.egg/apymongo/cursor.py", line 560, in _refresh
  File "/opt/python2.7/lib/python2.7/site-packages/apymongo-0.0.1-py2.7-linux-x86_64.egg/apymongo/cursor.py", line 620, in __send_message
  File "/opt/python2.7/lib/python2.7/site-packages/apymongo-0.0.1-py2.7-linux-x86_64.egg/apymongo/connection.py", line 735, in _send_message_with_response
  File "/opt/python2.7/lib/python2.7/site-packages/apymongo-0.0.1-py2.7-linux-x86_64.egg/apymongo/connection.py", line 591, in __stream
  File "/opt/python2.7/lib/python2.7/site-packages/apymongo-0.0.1-py2.7-linux-x86_64.egg/apymongo/connection.py", line 200, in get_stream
  File "/opt/python2.7/lib/python2.7/site-packages/apymongo-0.0.1-py2.7-linux-x86_64.egg/apymongo/connection.py", line 559, in __connect
AutoReconnect: could not connect to [('127.0.0.1', 27017)]

重要(我猜);

错误:[Errno 24] 打开的文件太多

代码;(非常简单)

import tornado.ioloop
import tornado.web
import tornado.escape
import apymongo
import time
import sys

#Useful stuff (Connect to Mongo)
class setup(tornado.web.RequestHandler):
    def dbmongo(self):
        if not hasattr(self, '_dbmongo'):
            self._dbmongo = apymongo.Connection("127.0.0.1", 27017)
        return self._dbmongo
        

#Basic method to lookup URLs from Mongo and redirect accordingly
class expand(setup):
    @tornado.web.asynchronous
    def get(self, url):
        self.mongo = self.dbmongo()
        
        #Lookup the URL
        cursor = self.mongo.rmgshortlinks.links.find_one({'short':url}, self.direct)
    
    def direct(self, response):
        if response == None:
            self.send_error(404)
            self.finish()
            return
        
        link = tornado.escape.url_unescape(response['long'])
        
        #Bounce the client
        self.write("<!DOCTYPE html><html><head><meta charset=\"UTF-8\" /><meta http-equiv=\"refresh\" content=\"0;URL="+link+"\"</head><body><a href=\""+link+"\">Click Here</a></body></html>")
        self.finish();


#Define the URL routes
application = tornado.web.Application([
    (r"/([a-zA-Z0-9]+)", expand)
])

#Start the server
if __name__ == "__main__":
    listening_port = int(sys.argv[1])
    
    if listening_port > 0:
        application.listen(listening_port)
        tornado.ioloop.IOLoop.instance().start()
    else:
        sys.stderr.write("No port specified!")

我使用的 dev 服务器有 8 个核心和 64GB 的内存,运行 RedHat Enterprise Linux 5 和 Python 2.6。我以前从未遇到过 Tornado/Async Mongo 应用程序出现这种问题。
可能有用的信息;
[root@puma ~]# ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 31374
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 31374
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

“打开的文件”只设置为1024,但我认为这足够了。

难道 Tornado / Apymongo 没有正确关闭连接吗?应用程序位于 NGINX 后面,但使用 HTTP 连接,Apymongo 应该通过 TCP 连接,但可能正在使用套接字。即使如此,它也应该共享/池化连接,不是吗?

编辑

根据建议,将应用程序移动到我们的一个测试服务器上,最大打开文件限制为61440,在siege运行约30秒后出现相同错误。


顺便提一下:如果可能的话,请不要发送带有元标记刷新标签的HTML。正确重定向请求的方法是使用HTTP 301或302响应。 - Thomas K
@ThomasK 我们采用这种方式有很多原因,主要原因是为了保留/覆盖引用标头。Twitter的t.co根据客户端也采取类似的做法(尝试使用真实的浏览器标头来CURL t.co URL)。 - Smudge
你尝试过这里找到的解决方案了吗:https://dev59.com/8XE85IYBdhLWcg3w3Xn6 - Alvin K.
@AlvinK。是的,如果不触及RedHat内核限制(大约为70k,如果我没记错),我们无法将ulimit设置得更高。我宁愿找到Tornado尝试打开这么多文件的根本原因。 - Smudge
1个回答

4
非常简单,RequestHandler对象对于每个请求都会被实例化。这意味着你保存的缓存对象在RequestHandler (例如,expand)对象上。
如果你在dbmongo(...)函数中添加一个简单的“print 'CREATED!'”,你会发现它是在每个GET请求中创建的。
你需要做的是将处理程序附加到类对象或“全局”对象,虽然最好的情况是将其放在Tornado应用程序对象上。
简单:
class setup(tornado.web.RequestHandler):
    @classmethod
    def dbmongo(cls):
        if not hasattr(cls, '_dbmongo'):
            cls._dbmongo = apymongo.Connection("127.0.0.1", 27017)
        return cls._dbmongo

第二种方法是在您的文件中将其作为全局变量:

dbmongo_connection = None
def dbmongo():
    if not dbmongo_connection:
        dbmongo_connection = apymongo.Connection("127.0.0.1", 27017)
    return dbmongo_connection

两者都有同样的问题,即当有许多类需要使用数据库连接时,共享连接变得更加困难。由于数据库是一个共享实体,您可能希望为整个应用程序使用一个连接。
class MongoMixin(object):
    def mongodb(self):
        if not hasattr(self.application, 'mongodb'):
            self.application.mongodb = apymongo.Connection(self.application.settings.get("mongohost", "127.0.0.1"), 27017)
        return self.application.mongodb

class expand(tornado.web.RequestHandler, MongoMixin):
    def get(self):
       db = self.mongodb()

搞定了,我选择了第一个例子,因为在我的情况下,每个(好吧,只有一个)方法都继承了“setup”类,但我可以看出其他方法也会产生类似的效果。 - Smudge

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接