memcache.get返回错误的对象(Celery,Django)

时间:2014-04-29 07:54:23

标签: python django caching memcached celery

以下是我们目前的情况:

  1. 我们正在尝试获取缓存的django模型实例,缓存键包括模型名称和实例ID。使用Django的标准memcached后端。该程序是非常广泛使用的常用程序的一部分,不仅在芹菜中使用。
  2. 有时(随机和/或很少)cache.get(key)返回错误的对象:int或不同的模型实例,甚至出现了same-model-different-id情况。通过检查型号名称和对应的对应关系来抓住这个。 id和缓存密钥。
  3. bug只出现在我们三个celery任务的上下文中,从不在python shell或其他芹菜任务中重现。 UPD:仅在长时间运行的CPU-RAM密集型任务中出现
  4. 缓存存储正确的值(我们在错误出现时手动检查)
  5. 使用相同的参数再次调用相同的任务可能不会重现该问题,尽管概率要高得多,因此错误出现倾向于在同一时间段内“分组”
  6. 重启芹菜解决了随机时间段(分钟 - 周)的问题
  7. * NEW *这与内存溢出无关。发生这种情况时,我们总是至少有2Gb可用内存。
  8. * NEW *我们在静态代码中有cache_instance = cache.get_cache("cache_entry")。在调查期间,我发现错误发生的那一刻cache_instance.get(key)返回错误的值,尽管下一行的get_cache("cache_entry").get(key)返回正确的值。这意味着错误消失太快或由于某种原因cache_instance对象被破坏。 是不是django的缓存线程返回的缓存实例对象安全吗?
  9. * NEW *我们记录了非常奇怪的情况:作为缓存中的另一个错误对象,我们得到了模型实例w / o id set。这意味着,实例从未保存到DB,因此无法缓存。 (我希望)
  10. * NEW *这些天至少记录了一MemoryError
  11. 我知道,所有这些听起来都像是某种魔法......而且,真的,任何想法如何可能或如何调试都将非常感激。

    PS:我目前的假设是这与多处理有关:只要在静态代码中创建缓存实例,并且在工作进程分叉之前,这将导致所有工作者共享相同的套接字(听起来是否合理?)

2 个答案:

答案 0 :(得分:5)

终于解决了:

  1. Celery具有动态扩展功能 - 它能够根据负载添加/杀死工作人员
  2. 通过分支现有的
  3. 来实现
  4. 打开的套接字和文件被复制到分叉进程,因此当一个进程读取另一个进程的响应时,两个进程共享它们,这会导致竞争条件。简单地说,一个进程可能会读取针对第二个进程的响应,反之亦然。
  5. from django.core.cache import cache此对象存储预先连接的memcached套接字。当您的流程可以动态分叉时,请不要使用它。并且不要使用存储的连接,池和其他。
  6. 或者将它们存储在当前PID下,并在每次访问缓存时进行检查

答案 1 :(得分:3)

这一直困扰着我一段时间,直到我找到了这个问题和答案。我只想补充一些我学到的东西。

您可以使用本地memcached实例轻松重现此问题:

request_finished

你可以做的就是关闭缓存客户端,就像Django在cache.close()信号中所做的那样:

https://github.com/django/django/blob/master/django/core/cache/init.py#L128

如果你在分叉后放置cache.close(),一切都按预期工作。

对于芹菜,你可以connect to a signal that is fired after the worker is forked并执行def post_fork(server, worker): from django.core.cache import cache cache.close()

当preload处于活动状态并且在分配工作人员之前初始化缓存时,这也会影响gunicorn。

对于gunicorn,你可以use post_fork in your gunicorn configuration

club.com