扭曲网络服务 - SQL连接断开

2 投票
1 回答
1677 浏览
提问于 2025-04-17 06:28

我正在用Twisted开发一个网络服务,这个服务负责调用我之前在命令行中使用的几个软件包。这些软件包处理的功能之前是单独测试的,现在准备整合到我们的网络服务中。

简单来说,我有几个不同的模块,它们在原来的命令行形式中都内部创建了一个mysql连接。举个例子:

class searcher:
  def __init__(self,lat,lon,radius):
    self.conn = getConnection()[1]
    self.con=self.conn.cursor();

    self.mgo = getConnection(True)

    self.lat = lat
    self.lon = lon
    self.radius = radius
    self.profsinrange()
    self.cache = memcache.Client(["173.220.194.84:11211"])

这个getConnection函数只是一个辅助函数,分别返回mongo或mysql的游标。再次强调,这一切都是原型阶段的内容 :)

我遇到的问题是,当使用Twisted的WSGI资源作为一个持续运行的服务器时,在初始化时创建的sql连接会超时,而后续的请求似乎没有重新生成这个连接。下面是一个小型服务器应用的示例代码:

from twisted.web import server
from twisted.web.wsgi import WSGIResource
from twisted.python.threadpool import ThreadPool
from twisted.internet import reactor
from twisted.application import service, strports
import cgi

import gnengine
import nn

wsgiThreadPool = ThreadPool()
wsgiThreadPool.start()

# ensuring that it will be stopped when the reactor shuts down
reactor.addSystemEventTrigger('after', 'shutdown', wsgiThreadPool.stop)


def application(environ, start_response):
    start_response('200 OK', [('Content-type','text/plain')])
    params = cgi.parse_qs(environ['QUERY_STRING'])
    try:
      lat =  float(params['lat'][0])
      lon = float(params['lon'][0])
      radius = int(params['radius'][0])
      query_terms = params['query']
      s = gnengine.searcher(lat,lon,radius)
      query_terms = ' '.join( query_terms )
      json = s.query(query_terms)
      return [json]
    except Exception, e:
      return [str(e),str(params)]

    return ['error']

wsgiAppAsResource = WSGIResource(reactor, wsgiThreadPool, application)

# Hooks for twistd
application = service.Application('Twisted.web.wsgi Hello World Example')
server = strports.service('tcp:8080', server.Site(wsgiAppAsResource))
server.setServiceParent(application)

前几个请求工作得很好,但在mysql的wait_timeout过期后,就会出现可怕的错误2006“Mysql已经断开连接”。我原本以为每个对WSGI Twisted资源的请求都会运行应用函数,从而重新生成搜索对象并重新分配连接。如果不是这样,我该如何处理这些请求呢?这种Twisted的部署方式在这个意义上不是事务性的吗?谢谢!

编辑:应要求,这里是调用连接的原型辅助函数:

def getConnection(mong = False):
    if mong == False:
    connection = mysql.connect(host = db_host,
                   user = db_user,
                   passwd = db_pass,
                   db = db,
                   cursorclass=mysql.cursors.DictCursor)
    cur = connection.cursor();
    return (cur,connection)
    else:
    return pymongo.Connection('173.220.194.84',27017).gonation_test

1 个回答

2

我在用Twisted开发一个软件时,需要一直保持与MySQL数据库的连接。过程中遇到了一些问题,查阅了很多Twisted的文档,还发了几个问题,但始终没找到合适的解决办法。创建adbapi.connectionPool类时可以传一个布尔参数,但我试了也没用,错误依然出现。我猜这个重连的布尔值是指当SQL断开连接时,连接对象会被销毁。

adbapi.ConnectionPool("MySQLdb", cp_reconnect=True, host="", user="", passwd="", db="")

我还没测试这个,但等我测试完会再分享结果,如果其他人有测试过也请分享一下。

在我开发这个脚本时,我使用的是Twisted 8.2.0(我有一段时间没碰Twisted了),那时候框架没有明确的保持连接的方法,所以我开发了一个ping/保持连接的扩展,利用了Twisted的事件驱动特性,并结合了MySQLdb模块的ping()方法(见代码注释)。不过,当我写这个回复时,我查了一下当前的Twisted文档,还是没找到明确的保持连接的方法或参数。我猜是因为Twisted本身没有数据库连接的库或类。它使用Python提供的方法,并提供了一个间接的接口层来与这些模块交互,同时也允许直接调用所用的数据库库。这是通过使用adbapi.runWithConnection方法实现的。

这是我在Twisted 8.2.0和Python 2.6下写的模块;你可以设置ping的时间间隔。这个脚本的功能是每20分钟ping一次数据库,如果失败,就每60秒尝试重新连接。我必须提醒的是,这个脚本并不处理突然断开的连接;你可以通过在Twisted中运行查询时添加addErrback来处理,至少我是这么做的。我发现每当数据库连接断开时,只有在执行查询时才会发现,事件会触发一个错误回调,然后你再处理这个问题。基本上,如果我10分钟没运行查询,而数据库把我断开了,我的应用程序不会实时响应。应用程序会在执行后续查询时意识到连接已经断开;所以数据库可能在第一次查询后1分钟、5分钟、9分钟等时刻断开了我。



from twisted.enterprise import adbapi
from twisted.internet import reactor, defer, task

class sqlClass:
        def __init__(self, db_pointer):
                self.dbpool=db_pointer
                self.dbping = task.LoopingCall(self.dbping)
                self.dbping.start(1200) #20 minutes = 1200 seconds; i found out that if MySQL socket is idled for 20 minutes or longer, MySQL itself disconnects the session for security reasons; i do believe you can change that in the configuration of the database server itself but it may not be recommended.
                self.reconnect=False
                print "database ping initiated"

        def dbping(self):
                def ping(conn):
                        conn.ping() #what happens here is that twisted allows us to access methods from the MySQLdb module that python posesses; i chose to use the native command instead of sending null commands to the database.
                pingdb=self.dbpool.runWithConnection(ping)
                pingdb.addCallback(self.dbactive)
                pingdb.addErrback(self.dbout)
                print "pinging database"

        def dbactive(self, data):
                if data==None and self.reconnect==True:
                        self.dbping.stop()
                        self.reconnect=False
                        self.dbping.start(1200) #20 minutes = 1200 seconds
                        print "Reconnected to database!"
                elif data==None:
                        print "database is active"

        def dbout(self, deferr):
                #print deferr
                if self.reconnect==False:
                        self.dbreconnect()
                elif self.reconnect==True:
                        print "Unable to reconnect to database"
                print "unable to ping MySQL database!"

        def dbreconnect(self, *data):
                self.dbping.stop()
                self.reconnect=True
                #self.dbping = task.LoopingCall(self.dbping)
                self.dbping.start(60) #60
if __name__ == "__main__":
        db = sqlClass(adbapi.ConnectionPool("MySQLdb", cp_reconnect=True, host="", user="", passwd="", db=""))
        reactor.callLater(2, db.dbping)
        reactor.run()

告诉我你的情况如何 :)

撰写回答