Django 如何处理多个 memcached 服务器?

21 投票
4 回答
7716 浏览
提问于 2025-04-16 22:31

在Django的文档中提到:

...

Memcached的一个很棒的功能是它可以在多个服务器之间共享缓存。这意味着你可以在多台机器上运行Memcached程序,而这个程序会把这些机器当作一个整体的缓存来使用,而不需要在每台机器上重复存储相同的缓存值。为了利用这个功能,你需要在LOCATION中包含所有服务器的地址,可以用分号分隔,也可以作为一个列表。

...

Django的缓存框架 - Memcached

这到底是怎么运作的呢?我在这个网站上看到一些回答提到,这个功能是通过根据键的哈希值在服务器之间进行分片来实现的。

多个Memcached服务器的问题

MemCacheStore是如何在多个服务器上工作的?

这很好,但我需要一个更具体、更详细的答案。使用Django和pylibmc或python-memcached,这个分片到底是怎么进行的?配置设置中IP地址的顺序重要吗?如果两个运行相同Django应用的不同Web服务器有两个不同的设置文件,里面的Memcached服务器IP地址顺序不同,会不会导致每台机器使用不同的分片策略,从而造成重复的键和其他效率低下的问题?

如果某台机器在列表中出现了两次呢?比如,如果我这样做,127.0.0.1实际上和172.19.26.240是同一台机器?

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': [
            '127.0.0.1:11211',
            '172.19.26.240:11211',
            '172.19.26.242:11211',
        ]
    }
}

如果其中一个Memcached服务器的容量比其他的更大呢?比如,机器一有64MB的Memcached,而机器二有128MB,分片算法会考虑这个因素,让机器二处理更多的键吗?

我还听说,如果一个Memcached服务器丢失了,那么那些键也会丢失。这在分片的情况下是显而易见的。更重要的是,如果一个Memcached服务器宕机了,而我在设置文件中保留了它的IP地址,会发生什么?Django/Memcached会简单地无法获取那些原本应该分配给那个宕机服务器的键,还是会意识到那个服务器已经宕机,并制定一个新的分片策略?如果有新的分片策略,它会智能地将原本分配给宕机服务器的键分配给剩下的服务器,还是会像那个服务器不存在一样制定一个全新的策略,导致键的重复?

我尝试阅读python-memcached的源代码,但完全搞不懂。我打算试着阅读libmemcached和pylibmc的代码,但我觉得如果有人已经知道,问这里会更简单。

4 个回答

3

想在这个问题被提出来两年后补充一下这个回答,因为这个问题在搜索中排名很高,而且我们确实遇到过django只和一个memcached服务器通信的情况。

我们有一个运行在django 1.4.3上的网站,使用python-memcached 1.51与四个memcached实例进行交互。我们发现数据库的查询次数比预期的要多得多。深入调查后,我们发现cache.get()在一些明明应该存在的键上返回了None。当我们用-vv选项启动memcached时,发现它只向一个服务器询问了问题!

经过一番折腾后,我们把后端切换到了django.core.cache.backends.memcached.PyLibMCCache(pylibmc),问题就解决了。

5

我测试了一部分内容,发现了一些有趣的事情,关于django 1.1和python-memcached 1.44。

在django中使用了两个memcache服务器。

cache.set('a', 1, 1000)

cache.get('a') # 返回了1

我查了一下,发现'a'这个数据被分配到了哪个memcache服务器上,使用了另外两个django设置,每个指向一个memcache服务器。我模拟了一个连接中断的情况,在原来的django实例和存储'a'的memcache服务器之间设置了一个防火墙。

cache.get('a') # 等了几秒钟,然后返回了None

cache.set('a', 2, 1000)

cache.get('a') # 立刻返回了2

如果某个服务器宕机,memcache客户端库会更新它的分片策略。

然后我把防火墙去掉了。

cache.get('a') # 返回了2,直到检测到服务器恢复,然后返回了1!

当memcache服务器掉线后再恢复时,你可能会读取到过时的数据!memcache并没有采取什么聪明的措施来防止这种情况。

如果你使用的缓存策略是把数据放在memcache里很长时间,并依赖缓存失效来处理更新,这可能会搞得很糟糕。一个旧的值可能会被写入到“正常”的缓存服务器上,如果你在失去连接的期间进行了失效处理,当服务器再次可用时,你会读取到不该看到的过时数据。

还有一点:我读了一些关于对象/查询缓存库的内容,我觉得johnny-cache应该不会受到这个问题的影响。它并不会明确地使条目失效;而是当表发生变化时,它会改变查询缓存的键。所以它不会意外地读取到旧值。

编辑:我觉得我关于johnny-cache能正常工作的说法不太靠谱。http://jmoiron.net/blog/is-johnny-cache-for-you/上说“每个请求都会有额外的缓存读取,以加载当前的版本”。如果这些版本存储在缓存本身中,上述情况可能会导致读取到过时的版本。

16

实际上,是memcached客户端负责进行数据分片。Django只是把配置从settings.CACHES传递给客户端。

服务器的顺序并不重要*,但是(至少对于python-memcached来说)你可以为每个服务器指定一个“权重”:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': [
                ('cache1.example.org:11211', 1),
                ('cache2.example.org:11211', 10),
            ],
}

我认为快速查看一下memcache.py(来自python-memcached)特别是memcached.Client._get_server应该能解答你剩下的问题:

def _get_server(self, key):
    if isinstance(key, tuple):
        serverhash, key = key
    else:
        serverhash = serverHashFunction(key)

    for i in range(Client._SERVER_RETRIES):
        server = self.buckets[serverhash % len(self.buckets)]
        if server.connect():
            #print "(using server %s)" % server,
            return server, key
        serverhash = serverHashFunction(str(serverhash) + str(i))
    return None, None

我预计其他的memcached客户端也是类似的实现方式。


由@Apreche澄清:在一种情况下,服务器的顺序是重要的。如果你有多个web服务器,并且希望它们都把相同的键放在相同的memcached服务器上,你需要以相同的顺序和相同的权重配置它们的服务器列表。

撰写回答