2012-05-03 32 views
8

我有一個使用Google Cloud SQL實例存儲數據的Google App Engine應用程序。我需要我的實例能夠通過平安的調用一次爲數百個客戶端提供服務,這些調用每個都會導致一個或幾個數據庫查詢。我已經包裝了需要數據庫訪問的方法,並將句柄存儲到os.environ中的數據庫連接中。基本上我是如何做到的,請參閱this SO問題/答案。來自App Engine的Google Cloud SQL的連接限制以及如何最佳地重用DB連接?

然而,只要幾百客戶端連接到我的應用程序和觸發器數據庫調用,我開始在谷歌應用程序引擎的錯誤日誌收到這些錯誤(和我的應用程序返回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用戶的任何提示?提前致謝。

下面是我身邊的方法使用的裝飾需要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.') 
+0

你有線程:在app.yaml中真的嗎? – proppy

+0

@proppy是的,我喜歡。謝謝。 – JJC

+0

使用'threadsafe:true'不能很好地使用os.environ,因爲連接不能在線程之間共享。有關線程安全解決方案,請參閱我的回答http://stackoverflow.com/a/10438622/1373093。 –

回答

14

簡短的回答: 您的查詢可能是太慢和MySQL服務器沒有足夠的線程來處理所有的您試圖發送的請求。

龍答:

作爲背景,雲SQL有兩個限制,這些限制與此有關:

  • 連接:這些對應於你的代碼中的「參數conn」對象。服務器上有相應的數據結構。一旦這些對象太多(當前配置爲1000),最近最少使用的對象將自動關閉。當連接在您的下方關閉時,您下次嘗試使用該連接時會收到未知連接錯誤(ApplicationError:1007)。
  • 併發請求:這些是在服務器上執行的查詢。每個正在執行的查詢綁定服務器中的一個線程,因此有100個限制。當併發請求太多時,後續請求將被拒絕並出現錯誤(ApplicationError:1033)

It聽起來不像連接限制影響你,但我想提及它以防萬一。

說到併發請求,增加限制可能會有所幫助,但它通常會使問題變得更糟。有兩種情況我們在過去看到過:

  • 死鎖:長時間運行的查詢正在鎖定數據庫的關鍵行。所有後續查詢都會阻止該鎖。應用程序超時了這些查詢,但它們仍在服務器上運行,捆綁這些線程直到觸發deadlock timeout
  • 慢查詢:每個查詢真的很慢。這通常發生在查詢需要臨時文件分類時。應用程序超時並在查詢的第一次嘗試仍在運行時針對併發請求限制進行計數,然後重試該查詢。如果你能找到你的平均查詢時間,你可以估計你的mysql實例可以支持多少QPS(例如,每個查詢5毫秒意味着每個線程200 QPS,因爲有100個線程,你可以做20000 QPS。每查詢意味着2000 QPS。)

您應該使用EXPLAINSHOW ENGINE INNODB STATUS看到其中兩個問題是怎麼回事。

當然,也有可能是你只是開着噸的流量在您的實例和那裏只是沒有足夠的線程。在這種情況下,無論如何,您可能會將實例的cpu最大化,因此添加更多線程無濟於事。

+0

謝謝Ken。我沒有看到1007錯誤,我相信我正確關閉了連接。我也不相信,查詢速度慢的罪魁禍首,因爲所有查詢都非常簡單(沒有加入,少數分組依據)的表很小(水平和垂直),則對所有索引(或等價唯一約束)被查找的列等等。從儀表板狀態,最大化實例上的CPU也可能不是問題。所以,我認爲這會縮小到僵局。我正在進一步研究。如果沒關係,可能會在晚些時候給你提供建議。 – JJC

+0

在上面的代碼片段中,我沒有看到連接關閉的位置。也許你沒有把它全部包括在內? –

+1

另外,一個GROUP BY仍然可以導致一個文件夾,所以即使一個人可能會傷害你。 –

4

我從文檔閱讀,發現有一個12連接/實例限制:

查找「每個App Engine的實例不能有一個谷歌的Cloud SQL實例超過12個併發連接。」在https://developers.google.com/appengine/docs/python/cloud-sql/

+0

謝謝。在兩年前我發佈這個問題時,我沒有在文檔中看到這一點,但也許它在那裏,我錯過了它。 :-D無論如何,這有助於解釋爲什麼我的項目不會擴展,最終不得不放棄。 :-( – JJC

+0

那你有沒有解決這個問題呢?除了清理掉舊的連接是你能找出什麼嗎?還挺跑進同樣的問題最近 – smaura777

相關問題