我已经与Tornado进行了很多合作,但这是我第一次遇到这种错误。我一直在研究一个非常基本的URL缩短器。URL由另一种应用程序放入数据库中,该应用程序仅从MongoDB存储中读取URL并重定向客户端。编写基本代码后,我针对它设置了一个简单的“ Siege”测试,在运行siege(siege -c 64 -t 5m -r 1 http://example.com/MKy针对4个应用程序线程运行)约30秒钟之后,我开始获得500个响应。查看错误日志,我看到了;
siege -c 64 -t 5m -r 1 http://example.com/MKy
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!")
我正在使用的开发服务器具有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,在围攻中运行约30秒后出现相同错误。
很简单,为每个请求实例化RequestHandler对象。这意味着您要保存的缓存对象位于RequestHandler(例如,expand)对象上。
如果要向dbmongo(…)函数添加一个简单的“ print’CREATED!’”,您会看到它是在每个GET请求中创建的。
您需要做的是将处理程序附加到类对象,或者根据需要附加“全局”,尽管最好的情况是将其放在Tornado Application对象上。
简单:
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()