来自 App Engine 的 Google Cloud SQL 的连接限制是啥,以及如何最好地重用数据库连接?

Posted

技术标签:

【中文标题】来自 App Engine 的 Google Cloud SQL 的连接限制是啥,以及如何最好地重用数据库连接?【英文标题】:What are the connection limits for Google Cloud SQL from App Engine, and how to best reuse DB connections?来自 App Engine 的 Google Cloud SQL 的连接限制是什么,以及如何最好地重用数据库连接? 【发布时间】:2012-05-12 13:24:22 【问题描述】:

我有一个使用 Google Cloud SQL 实例存储数据的 Google App Engine 应用。我需要我的实例能够通过 restful 调用一次为数百个客户端提供服务,每个调用都会导致一个或几个数据库查询。我已经包装了需要数据库访问的方法,并将数据库连接的句柄存储在 os.environ 中。请参阅this SO question/answer,了解我的基本做法。

但是,一旦有几百个客户端连接到我的应用并触发数据库调用,我就会开始在 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

Google App Engine 和 Google Cloud SQL 的经验丰富的用户有什么建议吗?提前致谢。

这是我在需要数据库连接的方法周围使用的装饰器的代码:

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://***.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.')

【问题讨论】:

app.yaml 中有 threadsafe: true 吗? 使用 'threadsafe: true' 不能很好地与使用 os.environ 一起工作,因为连接不能跨线程共享。有关线程安全的解决方案,请参阅我的回答 ***.com/a/10438622/1373093。 @JJC 清除了很多东西。如果您知道的话,cloud.google.com/sql/docs/diagnose-issues#limits 和 cloud.google.com/sql/pricing#v1-pricing 提到的每层 100 个待处理连接和 250 个并发连接之间有什么区别 @Kartik 抱歉,我不知道。 (但你应该是一个很好的用户,并为你在 Stack Overflow 上阅读的好问题/答案投票。:-)) 【参考方案1】:

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

长答案:

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

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

听起来连接限制对您没有影响,但我想提一下以防万一。

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

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

您应该使用EXPLAIN 和SHOW ENGINE INNODB STATUS 来查看这两个问题中的哪一个。

当然,您也有可能只是在您的实例上驱动了大量流量而没有足够的线程。在这种情况下,无论如何,您可能会最大化实例的 cpu,因此添加更多线程将无济于事。

【讨论】:

谢谢肯。我没有看到任何错误 1007,并且我相信我正确地关闭了连接。我也不认为慢查询是罪魁祸首,因为所有查询都相当简单(没有连接,很少分组)表很小(水平和垂直),所有的都有索引(或等效的唯一约束)正在查找的列等。从仪表板状态来看,最大化实例上的 CPU 也不太可能成为问题。所以,我认为这将其缩小到僵局。我正在进一步研究它。如果可以的话,可以稍后联系您寻求建议。 在您上面的 sn-p 中,我看不到连接在哪里关闭。也许您没有包括所有内容? 另外,GROUP BY 仍然可以导致文件排序,所以即使是一个也可能会伤害到你。 是的,抱歉,复制/粘贴失败。我添加了关闭连接并结束功能的缺失行。谢谢! 从谷歌支持来到这里 - 清除了很多。谢谢【参考方案2】:

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

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

【讨论】:

谢谢。当我两年前发布这个问题时,我没有在文档中看到这个,但也许它就在那里,我只是错过了它。 :-D 无论如何,它有助于解释为什么我的项目无法扩展并最终不得不放弃。 :-( 那么你解决问题了吗?除了清除旧连接之外,您还能找出其他任何东西吗?最近有点遇到同样的问题

以上是关于来自 App Engine 的 Google Cloud SQL 的连接限制是啥,以及如何最好地重用数据库连接?的主要内容,如果未能解决你的问题,请参考以下文章

来自 App Engine 的 Google Cloud SQL 的连接限制是啥,以及如何最好地重用数据库连接?

Google App Engine 上的传出 HTTP 请求位置

建立数据库连接时出错:Google App Engine Deploy

.jsp 文件不适用于 Google App Engine 留言簿教程

是否可以防止 Google App Engine 上的 DoSing?

如何从 Python 中的 App Engine 在 Google BigQuery 上创建架构?