所以我有一个多线程的程序,简而言之它下载网页,处理它们并存储结果。处理网页的规则和用途存储在数据库中。最初,数据库受到了极大的打击(处理每个网页需要1-50个数据库请求)。第1步是在memcached中缓存这些信息(如果域没有规则,它只返回一个空字符串“”),这对于每个已处理项目的数据库锤击数量来说是一个巨大的改进。但我仍在锤击memcached,这会增加网络延迟(每个处理项目的1-50次往返,即使在本地以太网上也会快速累加)。
所以我想将结果缓存在进程空间的数组中,基本上是在内存中复制memcached。数据方面它不是太糟糕,我将使用Python集基本上复制密钥:值存储(很容易)。
但是事情就是这样:通常一堆线程会占用同一个站点并需要相同的规则集,所以我想防止雷鸣般的群体问题(即10个线程都试图获得example.com的规则) ,如果不在本地缓存中,而不是在memcached中将导致数据库被击中,不是非常难,但有点)。
设置一个线程(“update_thread”)来更新内存数组,有一个工作队列,如果一个线程无法从本地缓存中获取域的规则,它会将域写入工作队列,暂停一段时间然后再次尝试,再次尝试睡眠并再次尝试,直到本地内存缓存具有空字符串“”或要使用的一组规则。线程“update_thread”读取工作队列并从memcached获取规则,如果不存在则从数据库获取规则并将它们写入memcached和本地缓存(如果没有规则在值中传播空字符串“”)。这样做的缺点是增加一个线程;更多GIL争用,轻微延迟(我们必须等待update_thread运行,因为我们受到GIL的支配)。另外还增加了另一个线程和工作队列的复杂性。只有“update_thread”才能写入内存缓存数组,因此不需要锁定/ etc。
我们使用锁来控制对内存中缓存数组的写访问。如果一个线程找不到规则集,它会尝试从memcached获取规则集,如果不在那里它会命中数据库,一旦找到规则就会锁定内存数组并写入规则(或空字符串“”对于内存缓存的值)缺点:我们可能仍然有雷鸣般的群体问题,但这可以通过编写一个特殊值来抵消,例如“获取规则所以只需等待一秒钟”一个域,这将导致其他线程等待。
其他人是否可以考虑任何其他解决方案,或评论我提出的两个解决方案?我怀疑我会使用数字2,因为锁定+“获取规则所以只需等待一秒”似乎比添加线程和工作队列更简单。或者我错过了一些非常明显和简单的解决方案?
答案 0 :(得分:1)
如果我理解正确,问题是多个线程倾向于同时从memcached中检索相同的数据。您希望协调线程,以便一个线程检索数据,而其他线程等待,并在数据到达后共享。
为要缓存的对象创建一个包装类。在开始通过网络检索值之前,在缓存中放置一个空包装器。如果另一个线程查找相同的数据,它将阻塞,直到值到达。
这是包装器对象:
class PendingValue(object):
def __init__(self):
self._event = threading.Event()
def get(self):
self._event.wait()
return self._value
def set(self, value):
self._value = value
self._event.set()
这是缓存:
class Cache(object):
def __init__(self):
self._dict = {}
self._lock = threading.Lock()
def __getitem__(self, key):
self._lock.acquire()
try:
pv = self._dict[key]
self._lock.release()
return pv.get()
except KeyError: #key not in cache
pv = PendingValue()
self._dict[key] = pv
self._lock.release()
value = retrieve_value_from_external_source()
pv.set(value)
return value
答案 1 :(得分:0)
从单独的受控制的进程空间到共享内存和某种互斥体,这是一个非常大的跳跃。
如果您的问题是通过五十次往返处理来引入延迟,为什么不使用multiget并且总是只做一次?