Gunicron + gevent Mongodb数据库连接一直增加不释放

问题描述

  • 使用Flask开发的Web服务,部署在服务器上使用的是gunicorn manage:app -k gevent -w 4
  • 某日告警,说浏览器崩了,当时急急忙忙的重启,搞好了,因为所有的服务都正常运行,后面查看日志,也没有发现什么特别的地方,最终感觉因该是MongoDB连接数满了,本地测试发现确实是连接数一直增加,不会释放。

解决过程

关于Gunicron

  • 什么是Gunicron:是一个unix上被广泛使用的高性能的Python WSGI UNIX HTTP Server。和大多数的web框架兼容,并具有实现简单,轻量级,高性能等特点。

  • 为什么要使用Gunicron:用于接受http请求并转换为WSGI协议,以供实现了WSGI协议的flask使用,并且gunicorn得益于gevent等技术,大幅度提高了性能,在生产环境以替代框架自带的WSGI server。

生产环境

  • 配置

    gevent==1.3.6
    greenlet==0.4.14
    gunicorn==19.9.0
    pymongo==3.7.0

  • mongodb连接代码
def __init__(self):
        config_name = os.getenv('FLASK_CONFIG') or 'default'
        base_config = config[config_name]  # object
        self.client = MongoClient(host=base_config.MONGO_HOST, port=base_config.MONGO_PORT)
        db_name = base_config.MONGO_NAME
        self.db = self.client[db_name]

修改方案

  • 参考pymongo: MongoClient opened before fork错误排解
  • fork是启动新进程的方法,由于MongoClient不是进程安全的,所以不可以将该实例从父进程中复制到子进程当中。在这个flask应用中,flask使用gunicorn作为网关接口,在启动的时候会启动一个主进程和多个子进程,也就是master/workers,这个时候就出现了MongoClient实例在进程之间的传递。
  • 为了解决这个问题,在实例化MongoClient对象的时候要加上connect=False参数。
def __init__(self):
        config_name = os.getenv('FLASK_CONFIG') or 'default'
        base_config = config[config_name]  # object
        self.client = MongoClient(host=base_config.MONGO_HOST, port=base_config.MONGO_PORT, maxIdleTimeMS=300000, connect=False)
        db_name = base_config.MONGO_NAME
        self.db = self.client[db_name]

参考