带有Python连接池的Memcache客户端?

时间:2013-10-29 17:30:18

标签: python multithreading memcached connection-pooling

python-memcached内存缓存客户端的编写方式是每个线程都有自己的连接。这使得python-memcached代码变得简单,这很好,但是如果你的应用程序有数百或数千个线程(或者如果你运行大量应用程序),则会出现问题,因为你将很快用完memcache中的可用连接。

通常这种问题通过使用连接池来解决,实际上我见过的Java memcache库实现了连接池。在阅读了各种Python memcache库的文档后,似乎唯一一个提供连接池的是pylibmc,但它对我来说有两个问题:它不是纯Python,并且它似乎没有超时来保留一个来自游泳池的客户。虽然不是纯粹的Python可能不是一个交易破坏者,没有超时肯定是。还不清楚这些池如何使用例如dogpile.cache

我希望找到一个带有连接池的纯Python memcache客户端,它可以与dogpile.cache一起使用,但我也对其他建议持开放态度。不过,我宁愿避免更改应用程序逻辑(比如将所有memcache操作推送到更少的后台线程中)。

2 个答案:

答案 0 :(得分:4)

一位同事提出了一个似乎对我们的用例运行良好的想法,所以在这里分享。基本思想是创建预先使用的memcache客户端数量,将它们放入队列中,每当需要memcache客户端时,从队列中提取一个。由于Queue.Queue get()方法具有可选的超时参数,您还可以处理无法及时获取客户端的情况。您还需要在memcache客户端中处理threading.local的使用。

以下是它在代码中的工作原理(请注意,我实际上并没有运行这个确切的版本,因此可能存在一些问题,但这应该可以让您了解文本描述对您没有意义):< / p>

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)

答案 1 :(得分: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