2013-10-29 57 views
3

python-memcached memcache客戶端以每個線程獲取自己的連接的方式寫入。這使得python-memcached代碼變得簡單,這很好,但是如果你的應用程序有數百或數千個線程(或者你運行了很多應用程序),則會出現問題,因爲你會很快用完memcache中的可用連接。帶有Python連接池的Memcache客戶端?

通常這種問題是通過使用連接池解決的,實際上我見過的Java memcache庫實現了連接池。閱讀各種Python memcache庫的文檔後,似乎唯一一個提供連接池的文檔是pylibmc,但它對我來說有兩個問題:它不是純Python,並且它似乎沒有超時預留客戶端的池。雖然不是純粹的Python可能不是一個交易斷路器,但肯定沒有超時。目前還不清楚如何使用這些池,例如dogpile.cache

最好我想找到一個純粹的Python memcache客戶端連接池,這將與dogpile.cache一起工作,但我也接受其他建議。但我寧願避免更改應用程序邏輯(例如將所有的memcache操作推入更少的後臺線程)。

回答

3

一位同事提出了一個似乎對我們的用例足夠好的想法,所以在這裏分享。基本的想法是,你創建了你想要使用的memcache客戶端的數量,把它們放入一個隊列中,並且每當你需要一個memcache客戶端時,你就從隊列中取出一個客戶端。由於Queue.Queue get()方法具有可選的超時參數,因此您還可以處理無法及時獲取客戶端的情況。您還需要在memcache客戶端中處理threading.local的使用。

下面是它如何在代碼中工作(請注意,我沒有真正運行此確切版本,因此可能會出現一些問題,但是如果文本描述對您沒有意義,這應該會給您一個想法):

import Queue 

import memcache 

# See http://stackoverflow.com/questions/9539052/python-dynamically-changing-base-classes-at-runtime-how-to 
# Don't inherit client from threading.local so that we can reuse clients in 
# different threads 
memcache.Client = type('Client', (object,), dict(memcache.Client.__dict__)) 
# Client.__init__ references local, so need to replace that, too 
class Local(object): pass 
memcache.local = Local 

class PoolClient(object): 
    '''Pool of memcache clients that has the same API as memcache.Client''' 
    def __init__(self, pool_size, pool_timeout, *args, **kwargs): 
     self.pool_timeout = pool_timeout 
     self.queue = Queue.Queue() 
     for _i in range(pool_size): 
      self.queue.put(memcache.Client(*args, **kwargs)) 

    def __getattr__(self, name): 
     return lambda *args, **kw: self._call_client_method(name, *args, **kw) 

    def _call_client_method(self, name, *args, **kwargs): 
     try: 
      client = self.queue.get(timeout=self.pool_timeout) 
     except Queue.Empty: 
      return 

     try: 
      return getattr(client, name)(*args, **kwargs) 
     finally: 
      self.queue.put(client) 
+0

這是一個非常不錯的解決方案,非常感謝! – Goin

+0

我不是爲什麼,但我的表現在時間上是通過使用池,然後使用單個客戶端 – userRaj

0

很多感謝@ Heikki Toivenen提供的問題想法!但是,我不確定如何完全調用get()方法才能在PoolClient中使用memcache客戶端。使用任意名稱直接調用get()方法會導致AttributeError或MemcachedKeyNoneError。

通過結合@ Heikki Toivonen和pylibmc的問題的解決方案,我想出了下面的代碼來解決這個問題,並在這裏發佈以方便未來的用戶(我調試了這段代碼,它應該已經準備好運行):

import Queue, memcache 
from contextlib import contextmanager 

memcache.Client = type('Client', (object,), dict(memcache.Client.__dict__)) 
# Client.__init__ references local, so need to replace that, too 
class Local(object): pass 
memcache.local = Local 

class PoolClient(object): 
    '''Pool of memcache clients that has the same API as memcache.Client''' 
    def __init__(self, pool_size, pool_timeout, *args, **kwargs): 
     self.pool_timeout = pool_timeout 
     self.queue = Queue.Queue() 

     for _i in range(pool_size): 
      self.queue.put(memcache.Client(*args, **kwargs)) 

     print "pool_size:", pool_size, ", Queue_size:", self.queue.qsize() 

    @contextmanager 
    def reserve(self): 
     ''' Reference: http://sendapatch.se/projects/pylibmc/pooling.html#pylibmc.ClientPool''' 

     client = self.queue.get(timeout=self.pool_timeout) 

     try: 
      yield client 
     finally:    
      self.queue.put(client) 
      print "Queue_size:", self.queue.qsize() 


# Intanlise an instance of PoolClient 
mc_client_pool = PoolClient(5, 0, ['127.0.0.1:11211']) 

# Use a memcache client from the pool of memcache client in your apps 
with mc_client_pool.reserve() as mc_client: 
    #do your work here