Django 如何处理多个 memcached 服务器?
在Django的文档中提到:
...
Memcached的一个很棒的功能是它可以在多个服务器之间共享缓存。这意味着你可以在多台机器上运行Memcached程序,而这个程序会把这些机器当作一个整体的缓存来使用,而不需要在每台机器上重复存储相同的缓存值。为了利用这个功能,你需要在LOCATION中包含所有服务器的地址,可以用分号分隔,也可以作为一个列表。
...
这到底是怎么运作的呢?我在这个网站上看到一些回答提到,这个功能是通过根据键的哈希值在服务器之间进行分片来实现的。
这很好,但我需要一个更具体、更详细的答案。使用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 个回答
想在这个问题被提出来两年后补充一下这个回答,因为这个问题在搜索中排名很高,而且我们确实遇到过django只和一个memcached服务器通信的情况。
我们有一个运行在django 1.4.3上的网站,使用python-memcached 1.51与四个memcached实例进行交互。我们发现数据库的查询次数比预期的要多得多。深入调查后,我们发现cache.get()
在一些明明应该存在的键上返回了None
。当我们用-vv选项启动memcached时,发现它只向一个服务器询问了问题!
经过一番折腾后,我们把后端切换到了django.core.cache.backends.memcached.PyLibMCCache
(pylibmc),问题就解决了。
我测试了一部分内容,发现了一些有趣的事情,关于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/上说“每个请求都会有额外的缓存读取,以加载当前的版本”。如果这些版本存储在缓存本身中,上述情况可能会导致读取到过时的版本。
实际上,是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服务器上,你需要以相同的顺序和相同的权重配置它们的服务器列表。