4

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

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

最好我想找到一个带有连接池的纯 Python memcache 客户端,它可以与 dogpile.cache 一起使用,但我也愿意接受其他建议。不过,我宁愿避免更改应用程序逻辑(例如将所有 memcache 操作推入更少的后台线程)。

4

2 回答 2

4

一位同事想出了一个对我们的用例来说似乎足够好的想法,所以在这里分享一下。基本思想是,您预先创建要使用的 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)
于 2014-03-19T23:49:58.160 回答
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
于 2015-01-05T12:59:52.377 回答