【问题标题】:Ever increasing number of opened sockets on python web serverpython web服务器上打开的套接字数量不断增加
【发布时间】:2013-12-30 15:57:46
【问题描述】:

我有一个简单的 python 网络服务器,它在 2 天/3 天后一直失败。经过调查是因为它达到了打开文件的数量限制。打开的文件描述符是套接字。 (ls -l /proc/pid/fd/xxx:/proc/pid/fd/xxx -> socket:[yyyyy])

我可以增加 ulimit,但我宁愿弄清楚发生了什么。

一些背景

  • 我有 50 台机器每小时通过一个简单的 POST id=machine_id,cpu_usage=xxx 向服务器报告它们已启动并运行
  • 服务器只是将其存储在数据库 (mongodb) 中
  • 有一个 html 页面来监控事物,使用一些 jquery/get json 来制作给定机器的 cpu 使用率图表
  • 在 GET 上提供 [(date, cpu_usage)] 的处理程序?date_start,date_end,machine_id

我是唯一使用此页面的人,正如我所说,每小时只有 50 个请求随机分发到服务器

问题可能源于:

  • jquery 的getjson 打开了一个套接字并且从不关闭它(可能是,但我不这么认为,因为我重新启动了服务器并且没有进入监控页面)
  • python 代码和我在“main”中定义处理程序的方式
  • mongodb
  • 我想不到的其他地方

主代码:

import listener_handler
from flask import Flask

if __name__ == '__main__':
  app = Flask(__name__)

  listener_handl = None
  @app.route('/listener', methods=['POST'])
  def listener():
    global listener_handl
    if listener_handl is None:
      listener_handl = listener_handler.ListenerHandler()
    return listener_handl.Post()

  ... (other handlers for the getjson and the static monitoring page)

  app.run()

处理程序代码:

from flask import request

class ListenerHandler:
  def Post(self):
    Save(request.form.get('machine_id'), request.form.get('cpu_usage'))
    return 'ok'

mongo db 代码:

import pymongo

mongo_client = pymongo.MongoClient()
mongo_db = mongo_client.stations_monitoring

def Save(machine_id, cpu_usage):
  mongo_db.db['monitoring'].save({'machine': machine_id, 'cpu': cpu_usage})

我试图保持代码轻量级,我对 python 有很好的经验,但对 python webserver 几乎没有,所以当我定义处理程序时,我真的不知道引擎盖下发生了什么,如果每次都创建一个新套接字,如果最后是关闭的,...

我首先有一个烧瓶服务器(此处为代码),然后移至 tornado(由一些 tornado 导入和一些 IOLoop.instance().start() 替换 app.run)但这导致了同样的问题

【问题讨论】:

  • 可能是愚蠢的问题,但是那些与 mongod 进程的 pymongo 连接是否存在?不是您流程的另一部分?
  • 你说得对,我前段时间解决了部分问题。套接字与mongodb无关。它位于远程计算机(客户端)和服务器之间。客户端建立了连接,但它的互联网连接很糟糕,所以它永远挂起。在 curl 上添加超时有帮助。但我不知道为什么 Flask 或 Tornado 不处理这个?

标签: python mongodb sockets flask tornado


【解决方案1】:

我在烧瓶和 pymongo 之间遇到了完全相同的问题;我通过清理每个请求来解决它。如果您没有性能原因让 MongoClient 句柄保持打开状态,您也可以关闭它。

http://api.mongodb.org/python/current/api/pymongo/mongo_client.html#pymongo.mongo_client.MongoClient.disconnect

import pymongo
class MongoConnector:
    def __init__(self):
        client = pymongo.MongoClient()
        self.db = client.stations_monitoring
    def close(self):
        self.db.disconnect()

def Save(machine_id, cpu_usage):
    mongoConnector = MongoConnector()
    mongoConnector.db['monitoring'].save({'machine': machine_id, 'cpu': cpu_usage})
    mongoConnector.close()

Flask 是单线程的,您的 WSGI 处理程序将生成所需数量的单个应用程序,因此您无需担心 Flask 级别的线程支持。

如果你真的想保持 mongo 连接并且有性能原因,MongoClient 支持 AutoReconnect 异常与重新连接,所以你不应该自己处理。

import pymongo
from pymongo.errors import AutoReconnect

class MongoConnector:
    def __init__(self):
        client = pymongo.MongoClient()
        self.db = client.stations_monitoring
    def close(self):
        self.db.disconnect()

mongoConnector = MongoConnector()
def Save(machine_id, cpu_usage):
    try:
        mongoConnector.db['monitoring'].save({'machine': machine_id, 'cpu': cpu_usage})
    except AutoReconnect:
        #should be reconnected now
        mongoConnector.db['monitoring'].save({'machine': machine_id, 'cpu': cpu_usage})

[编辑] 不知道为什么你的不工作。尝试简化你正在做的事情。如果你没有理由使用吸气剂,那就简单点吧。

testflask.py

from flask import Flask, request
import pymongo

app = Flask(__name__)

def SaveLog(machine_id, cpu_usage):
    mc = pymongo.MongoClient()
    db = mc.stations_monitoring
    db['monitoring'].save({'machine': machine_id, 'cpu': cpu_usage})
    mc.disconnect()

@app.route('/listener', methods=['POST', 'GET'])
def listener():
    SaveLog(request.form.get('machine_id'), request.form.get('cpu_usage'))
    return 'ok'

if __name__ == '__main__':
  app.run()

test_get.py 用请求锤击服务器。我的可以做到 ~50/s

import requests
from random import randint

while True:
    r = requests.get('http://localhost:5000/listener?machine_id=%s&cpu_usage=%s' %(randint(1,10000), randint(1,100)))
    print r.text

验证 fds(我的挂起大约 5-10 个打开的文件句柄)

ps aux | grep testflask.py | grep -v grep | awk '{print $2}' | xargs -I @ bash -c 'ls -l /proc/@/fd/ | wc -l'

【讨论】:

  • 非常感谢。可以肯定的是,您想在 try/except AutoReconnect 之后调用 close() ,对吗?但是,如果您这样做,它如何保持连接以提高性能?如果你不这样做,它如何解决关闭套接字的问题?我不确定医生说什么
  • 不,第二个示例显示了我将如何持久化 MongoClient 对象,mongoConnector 需要在烧瓶过程期间(永远)打开,并且您让 MongoClient 类为您重新连接.在我的情况下,我使用了简单地打开和关闭 MongoClient 句柄的第一个示例,因为我没有需要我保持连接的性能问题。
  • 我明白你在说什么,你只看到打印新连接在启动时发生一次?
  • 我不明白你的第二个例子和我的区别,因为你没有明确地打电话给close()。我的代码中也没有引发任何异常(尽管我看到了 AutoReconnect 的意义)
  • 对不起,我认为没有区别。撇开重新连接不谈,我只是不明白为什么你需要一个 getter 函数,而你可以声明它。
【解决方案2】:

我们正在使用 pymongo + flask + gunicorn,一切正常。 pymongo 会维护一个连接池,每个 MongoClient 实例都有一个内置的连接池。所以,如果你有太多正在运行的 mongoclient 实例,它可能会抱怨打开的文件太多。

how-does-connection-pooling-work-in-pymongo

【讨论】:

  • 感谢您的链接。他们建议只打开一个客户端,但这就是我首先所做的(检查 mongo_client 是否为无)并且我遇到了错误。我再次从文档中给出的 max_pool_size 等参数开始,将会看到......
  • 你能分享你如何使用pymongo的代码,打开客户端,查找或保存,...?我已经坚持了 2 周了,仍然无法弄清楚,谢谢
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-12-01
  • 1970-01-01
  • 2012-10-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多