App Engine中的Google Cloud SQL有哪些连接限制,以及如何最好地重用数据库连接?

JJC*_*JJC 9 python mysql google-app-engine wsgi google-cloud-sql

我有一个Google App Engine应用程序,它使用Google Cloud SQL实例存储数据.我需要我的实例能够通过休息调用一次为数百个客户端提供服务,每个客户端都会产生一个或几个数据库查询.我已经包装了需要DB访问的方法,并将句柄存储到os.environ中的DB连接.看看这个问题/答案基本上我是怎么做的.

但是,只要有几百个客户端连接到我的应用程序并触发数据库调用,我就会在Google App Engine错误日志中开始收到这些错误(当然,我的应用程序返回500):

could not connect: ApplicationError: 1033 Instance has too many concurrent requests: 100 Traceback (most recent call last): File "/base/python27_run
Run Code Online (Sandbox Code Playgroud)

Google App Engine和Google Cloud SQL的有经验用户提供的任何提示?提前致谢.

这是我使用需要DB连接的方法的装饰器的代码:

def with_db_cursor(do_commit = False):
    """ Decorator for managing DB connection by wrapping around web calls.
    Stores connections and open connection count in the os.environ dictionary
    between calls.  Sets a cursor variable in the wrapped function. Optionally
    does a commit.  Closes the cursor when wrapped method returns, and closes
    the DB connection if there are no outstanding cursors.

    If the wrapped method has a keyword argument 'existing_cursor', whose value
    is non-False, this wrapper is bypassed, as it is assumed another cursor is
    already in force because of an alternate call stack.

    Based mostly on post by : Shay Erlichmen
    At: https://stackoverflow.com/a/10162674/379037
    """

    def method_wrap(method):
        def wrap(*args, **kwargs):
            if kwargs.get('existing_cursor', False):
                #Bypass everything if method called with existing open cursor
                vdbg('Shortcircuiting db wrapper due to exisiting_cursor')
                return  method(None, *args, **kwargs)

            conn = os.environ.get("__data_conn")

            # Recycling connection for the current request
            # For some reason threading.local() didn't work
            # and yes os.environ is supposed to be thread safe 
            if not conn:                    
                conn = _db_connect()
                os.environ["__data_conn"] = conn
                os.environ["__data_conn_ref"] = 1
                dbg('Opening first DB connection via wrapper.')
            else:
                os.environ["__data_conn_ref"] = (os.environ["__data_conn_ref"] + 1)
                vdbg('Reusing existing DB connection. Count using is now: {0}',
                    os.environ["__data_conn_ref"])        
            try:
                cursor = conn.cursor()
                try:
                    result = method(cursor, *args, **kwargs)
                    if do_commit or os.environ.get("__data_conn_commit"):
                        os.environ["__data_conn_commit"] = False
                        dbg('Wrapper executing DB commit.')
                        conn.commit()
                    return result                        
                finally:
                    cursor.close()                    
            finally:
                os.environ["__data_conn_ref"] = (os.environ["__data_conn_ref"] -
                        1)  
                vdbg('One less user of DB connection. Count using is now: {0}',
                    os.environ["__data_conn_ref"])
                if os.environ["__data_conn_ref"] == 0:
                    dbg("No more users of this DB connection. Closing.")
                    os.environ["__data_conn"] = None
                    db_close(conn)
        return wrap
    return method_wrap

def db_close(db_conn):
    if db_conn:
        try:
            db_conn.close()
        except:
            err('Unable to close the DB connection.', )
            raise
    else:
        err('Tried to close a non-connected DB handle.')
Run Code Online (Sandbox Code Playgroud)

小智 14

简短回答:您的查询可能太慢,并且mysql服务器没有足够的线程来处理您尝试发送它的所有请求.

答案很长:

作为背景,Cloud SQL有两个与此相关的限制:

  • 连接:这些对应于代码中的"conn"对象.服务器上有相应的数据结构.一旦你有太多这些对象(当前配置为1000),最近最少使用的对象将自动关闭.当您下面的连接关闭时,下次尝试使用该连接时,您将收到未知的连接错误(ApplicationError:1007).
  • 并发请求:这些是在服务器上执行的查询.每个执行的查询都会占用服务器中的一个线程,因此限制为100.当并发请求太多时,后续请求将被拒绝,并显示您遇到的错误(ApplicationError:1033)

这听起来并不像连接限制对你有影响,但我想提一下以防万一.

当谈到并发请求时,增加限制可能有所帮助,但它通常会使问题变得更糟.我们过去见过两种情况:

  • 死锁:长时间运行的查询正在锁定数据库的关键行.所有后续查询都会阻止该锁定.应用程序在这些查询上超时,但它们继续在服务器上运行,占用这些线程,直到死锁超时触发.
  • 慢查询:每个查询都非常非常慢.当查询需要临时文件排序时,通常会发生这种情况.当查询的第一次尝试仍在运行并且计入并发请求限制时,应用程序超时并重试查询.如果你能找到你的平均查询时间,你可以得到你的mysql实例可以支持多少QPS的估计(例如,每个查询5 ms意味着每个线程200 QPS.由于有100个线程,你可以做到20,000 QPS.50 ms每个查询意味着2000 QPS.)

您应该使用EXPLAINSHOW ENGINE INNODB STATUS来查看正在进行的两个问题中的哪一个.

当然,您也可能只是在您的实例上驱动了大量的流量,并且没有足够的线程.在这种情况下,你可能无论如何都要最大化实例的cpu,所以添加更多线程无济于事.


Law*_*Mok 5

我阅读文档并注意到有 12 个连接/实例限制:

查找“每个 App Engine 实例与 Google Cloud SQL 实例的并发连接不能超过 12 个”。在https://developers.google.com/appengine/docs/python/cloud-sql/