【问题标题】:Django command that opens a twisted socket client leaves idle database connections打开扭曲套接字客户端的 Django 命令留下空闲的数据库连接
【发布时间】:2016-05-31 14:12:28
【问题描述】:

我正在使用 twisted 连接到套接字服务器,并且我正在使用 django 命令运行 twisted:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import json

from twisted.internet import reactor, task
from twisted.internet.protocol import ReconnectingClientFactory
from twisted.protocols.basic import LineReceiver

from django.core.management.base import BaseCommand, CommandError

from django.conf import settings


class SocketClientProtocol(LineReceiver):

    MAX_LENGTH = 64*1024*1024

    def __init__(self):
        self.setLineMode()

    def connectionMade(self):
        print "CONNECTION ESTABLISHED"

    def connectionLost(self, reason):
        print "CONNECTION LOST"
        print reason.getErrorMessage()

    def lengthLimitExceeded(self, length):
        print "EXCEED"
        print length

    def lineReceived(self, msg):
        data = json.loads(msg)
        #do sth with the data from the socket server
        handle_message(data["type"], data["data"])


class SocketClientFactory(ReconnectingClientFactory):

    def startedConnecting(self, connector):
        print 'Started to connect.'

    def buildProtocol(self, addr):
        print 'Connected'
        self.resetDelay()
        return SocketClientProtocol()

    def clientConnectionLost(self, connector, reason):
        print 'Lost connection.  Reason:', reason
        ReconnectingClientFactory.clientConnectionLost(self, connector, reason)

    def clientConnectionFailed(self, connector, reason):
        print 'Connection failed. Reason:', reason
        ReconnectingClientFactory.clientConnectionFailed(self, connector, reason)

class Command(BaseCommand):

    def handle(self, *args, **options):
        client_factory = SocketClientFactory()
        reactor.connectUNIX(settings.UNIX_OUT_SOCKET, client_factory)
        reactor.run()

我通过python manage.py connect 启动这个命令,然后让进程在Apache 后面的一个wsgi 网络服务器旁边运行。这很好用,但我遇到的问题是,在此套接字客户端中启动的数据库连接通常不会正确关闭,而只是作为打开和空闲连接堆积起来。

select * from pg_stat_activity where datname = 'lt';的输出:

 datid | datname |  pid  | usesysid | usename | application_name | client_addr | client_hostname | client_port |         backend_start         | xact_start |          query_start          |         state_change          | waiting | state |            query                                                                                                                                        
 36649 | lt      | 11026 |    16384 | dbuser  |                  | 127.0.0.1   |                 |       55167 | 2016-05-31 15:50:38.417288+02 |            | 2016-05-31 15:50:38.450209+02 | 2016-05-31 15:50:38.451045+02 | f       | idle  | SELECT "mediatorapp_audiostream"."id", "mediatorapp_audiostream"."language_tag", "mediatorapp_audiostream"."session_id", "mediatorapp_audiostream"."out_file", "mediatorapp_audiostream"."source_file" FROM "mediatorapp_audiostream" WHERE "mediatorapp_audiostream"."id" = '1104880479774183399:de'
 36649 | lt      | 11132 |    16384 | dbuser  |                  | 127.0.0.1   |                 |       55348 | 2016-05-31 15:50:48.057967+02 |            | 2016-05-31 15:50:48.089656+02 | 2016-05-31 15:50:48.090441+02 | f       | idle  | SELECT "mediatorapp_audiostream"."id", "mediatorapp_audiostream"."language_tag", "mediatorapp_audiostream"."session_id", "mediatorapp_audiostream"."out_file", "mediatorapp_audiostream"."source_file" FROM "mediatorapp_audiostream" WHERE "mediatorapp_audiostream"."id" = '1104880479774183399:de'
 36649 | lt      | 10386 |    16384 | dbuser  |                  | 127.0.0.1   |                 |       53816 | 2016-05-31 15:49:19.597695+02 |            | 2016-05-31 15:49:19.633149+02 | 2016-05-31 15:49:19.634685+02 | f       | idle  | SELECT "mediatorapp_audiostream"."id", "mediatorapp_audiostream"."language_tag", "mediatorapp_audiostream"."session_id", "mediatorapp_audiostream"."out_file", "mediatorapp_audiostream"."source_file" FROM "mediatorapp_audiostream" WHERE "mediatorapp_audiostream"."id" = '1104880479774183399:de'
 36649 | lt      | 11145 |    16384 | dbuser  |                  | 127.0.0.1   |                 |       55356 | 2016-05-31 15:50:48.353581+02 |            | 2016-05-31 15:50:48.389896+02 | 2016-05-31 15:50:48.390985+02 | f       | idle  | SELECT "mediatorapp_audiostream"."id", "mediatorapp_audiostream"."language_tag", "mediatorapp_audiostream"."session_id", "mediatorapp_audiostream"."out_file", "mediatorapp_audiostream"."source_file" FROM "mediatorapp_audiostream" WHERE "mediatorapp_audiostream"."id" = '1104880479774183399:de'
 36649 | lt      | 11150 |    16384 | dbuser  |                  | 127.0.0.1   |                 |       55373 | 2016-05-31 15:50:49.705282+02 |            | 2016-05-31 15:50:49.7269+02   | 2016-05-31 15:50:49.729029+02 | f       | idle  | SELECT "mediatorapp_audiodata"."id", "mediatorapp_audiodata"."index", "mediatorapp_audiodata"."source_file", "mediatorapp_audiodata"."duration", "mediatorapp_audiodata"."audiostream_id", "mediatorapp_audiodata"."start_timestamp", "mediatorapp_audiodata"."stop_timestamp" FROM "mediatorapp_audiodata" WHERE "mediatorapp_audiodata"."id" = 419871
 36649 | lt      | 10944 |    16384 | dbuser  |                  | 127.0.0.1   |                 |       54990 | 2016-05-31 15:50:27.940832+02 |            | 2016-05-31 15:50:27.978202+02 | 2016-05-31 15:50:27.97917+02  | f       | idle  | SELECT "mediatorapp_audiostream"."id", "mediatorapp_audiostream"."language_tag", "mediatorapp_audiostream"."session_id", "mediatorapp_audiostream"."out_file", "mediatorapp_audiostream"."source_file" FROM "mediatorapp_audiostream" WHERE "mediatorapp_audiostream"."id" = '1104880479774183399:de'
 36649 | lt      | 11059 |    16384 | dbuser  |                  | 127.0.0.1   |                 |       55243 | 2016-05-31 15:50:42.737117+02 |            | 2016-05-31 15:50:42.773878+02 | 2016-05-31 15:50:42.774562+02 | f       | idle  | SELECT "mediatorapp_audiostream"."id", "mediatorapp_audiostream"."language_tag", "mediatorapp_audiostream"."session_id", "mediatorapp_audiostream"."out_file", "mediatorapp_audiostream"."source_file" FROM "mediatorapp_audiostream" WHERE "mediatorapp_audiostream"."id" = '1104880479774183399:de'
 36649 | lt      | 11127 |    16384 | dbuser  |                  | 127.0.0.1   |                 |       55342 | 2016-05-31 15:50:47.868249+02 |            | 2016-05-31 15:50:47.905184+02 | 2016-05-31 15:50:47.90642+02  | f       | idle  | SELECT "mediatorapp_audiostream"."id", "mediatorapp_audiostream"."language_tag", "mediatorapp_audiostream"."session_id", "mediatorapp_audiostream"."out_file", "mediatorapp_audiostream"."source_file" FROM "mediatorapp_audiostream" WHERE "mediatorapp_audiostream"."id" = '1104880479774183399:de'
 36649 | lt      | 10901 |    16384 | dbuser  |                  | 127.0.0.1   |                 |       54896 | 2016-05-31 15:50:23.208558+02 |            | 2016-05-31 15:50:23.246194+02 | 2016-05-31 15:50:23.247041+02 | f       | idle  | SELECT "mediatorapp_audiostream"."id", "mediatorapp_audiostream"."language_tag", "mediatorapp_audiostream"."session_id", "mediatorapp_audiostream"."out_file", "mediatorapp_audiostream"."source_file" FROM "mediatorapp_audiostream" WHERE "mediatorapp_audiostream"."id" = '1104880479774183399:de'

在某些时候,这会导致以下错误:

OperationalError: FATAL:  remaining connection slots are reserved for non-replication superuser connections

上面列出的查询是由这个函数生成的:

def get_audio_stream(audiostream_id):
    try:
        return AudioStream.objects.get(id=audiostream_id)
    except AudioStream.DoesNotExist:
        return None

所以我只是想通过它的 id 来获取一个对象。据我所知,Django 应该在之后关闭连接,但事实并非如此。 Is 不是唯一不时挂起的查询,其他查询也是如此。但这是最常见的一个。

我正在使用最新的 django (9.6)、twisted 15.5 和 Postgresql 9.3。我已经尝试将 CONN_MAX_AGE 设置为不同的值,但没有成功。

那么这个问题的原因是什么?是因为我在这个扭曲的引擎后面运行它吗?我该如何解决?

【问题讨论】:

    标签: python django postgresql twisted


    【解决方案1】:

    您在上面发布的代码中似乎没有创建与 postgres 的连接的代码。问题出在其他地方,可能是上面代码连接的服务器代码在客户端断开连接时没有关闭数据库连接。

    编辑:如果您在非 WSGI 应用程序中使用 Django ORM 调用,则数据库连接不会自动关闭。自动连接管理仅在 WSGI 请求的上下文中完成。它通过signals.request_finished 信号实现这一点。

    您可以使用以下代码手动管理连接并在不需要时关闭它们:

    from django import db
    
    # Use this to close all configured db connections after your query code.
    # Note that Django ORM performs lazy queries, i.e. only executing SQL if
    # you access the Model object properties retrieved from DB. So you should
    # do this after you have finished ORM object tasks.
    db.connections.close_all()
    
    # or use this
    db.connection.close()
    
    # or for more control over which connection:
    db.connections['default'].close()  # closes the default DB connection
    

    如果您要手动管理连接,您还需要手动管理事务。

    【讨论】:

    • 我更新了我的问题。我只是简单地尝试从数据库中获取一个对象。 Django 正在执行较低级别的数据库交互,据我所知,应该立即关闭连接。但通常不会。
    • 该查询代码是否从 Django 视图中运行?您是否按预期使用 Django,即作为 WSGI 应用程序?即使用 gunicorn 或 runserver 运行它。
    • 我在 django 命令内部运行查询代码,在函数 handle_message() 内部。
    • 我已经更新了上面的答案。我希望它能解决你的问题。附带说明一下,如果您真的不需要除 ORM 之外的 Django 的其他功能,则最好使用 SQLAlchemy 之类的东西,然后使用 argparse 将管理命令转换为简单的 python 脚本,以便它可以处理命令行论据。
    • 我花了一段时间才让它工作。我发现主要原因不是我在这个 Django 命令中运行东西,而是我在某一时刻产生了一个单独的线程。在该线程中,连接并不总是正确关闭。但是,您手动关闭连接的建议解决了这个问题。谢谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-07-28
    • 1970-01-01
    • 2015-07-13
    • 1970-01-01
    • 2014-01-09
    • 2021-09-13
    • 1970-01-01
    相关资源
    最近更新 更多