Mik*_*keN 8 python django batch-file
我有一个"分析仪表板"屏幕,我的django Web应用程序用户可以看到它需要很长时间才能计算出来.它是这些屏幕中的一个,它为用户遍历数据库中的每个事务,并为其提供指标.
我希望这是一个实时操作,但对于活跃用户,计算时间可以是20-30秒(不允许分页,它给出了事务的平均值.)
想到的解决方案是通过manage.py batch命令在后端计算,然后只向用户显示缓存值.是否有Django设计模式来帮助促进这些类型的模型/显示?
Ida*_*zit 14
您正在寻找的是离线处理和缓存的组合.离线时,我的意思是计算逻辑发生在请求 - 响应周期之外.通过缓存,我的意思是您的昂贵计算结果对X时间足够有效,在此期间您无需重新计算它以进行显示.这是一种非常常见的模式.
有两种广泛使用的工作方法需要在请求 - 响应周期之外发生:
相对而言,cron设置起来更简单,而Celery更强大/更灵活.话虽如此,Celery享有精彩的文档和全面的测试套件.我几乎在每个项目中都使用它,虽然它确实涉及一些要求,但它并不是真正的设置.
Cron工作是历史悠久的方法.如果你只需要运行一些逻辑并将一些结果存储在数据库中,那么cron作业就没有依赖关系了.cron作业中唯一令人讨厌的一点是让您的代码在django项目的上下文中运行 - 也就是说,您的代码必须正确加载settings.py才能了解您的数据库和应用程序.对于没有经验的人来说,这可能导致在判断正当PYTHONPATH等等方面有所恶化.
如果您要使用cron路由,一个好方法是编写自定义管理命令.您可以轻松地从终端测试命令(并编写测试),并且您无需在管理命令的顶部执行任何特殊的喧嚣来设置适当的django环境.在生产中,你只需运行path/to/manage.py yourcommand.我不确定这种方法是否可以在没有virtualenv的帮助下工作,你应该真正使用它.
使用cronjobs时要考虑的另一个方面是:如果你的逻辑需要花费不同的时间来运行,那么cron对这个问题一无所知.一个可爱的杀死你的服务器的方法是每小时运行这样一个两小时的cronjob.您可以使用自己的锁定机制来防止这种情况,只需要注意这一点 - 当数据增长时,或者当您的RDBMS行为异常等时,短的cronjob可能不会保持这种状态.
在你的情况下,听起来cron不太适用,因为你需要每隔一段时间为每个用户计算图形,而不考虑谁实际使用系统.这是芹菜可以提供帮助的地方.
......是蜜蜂的膝盖.通常人们会被AMQP经纪人的"默认"要求吓跑.这是不是非常繁重设立RabbitMQ的,但它确实需要的Python舒适的世界一点之外步进.对于许多任务,我只使用redis作为Celery的任务存储.设置很简单:
CELERY_RESULT_BACKEND = "redis"
REDIS_HOST = "localhost"
REDIS_PORT = 6379
REDIS_DB = 0
REDIS_CONNECT_RETRY = True
Run Code Online (Sandbox Code Playgroud)
Voilá,不需要AMQP经纪人.
Celery比简单的cron工作提供了许多优势.比如cron,您可以安排定期任务,但你也可以断火响应其他刺激任务,而无需举行了请求/响应周期.
如果您不希望每隔一段时间为每个活动用户计算图表,则需要按需生成它.我假设查询最新的可用平均值是便宜的,计算新的平均值是昂贵的,并且你使用像flot这样的东西来生成客户端的实际图表.这是一个示例流程:
您可以将此与周期性任务相结合,以便每小时为具有活动会话的用户重新计算图表,以防止显示真正过时的图表.这不是剥离猫的唯一方法,但它为您提供了所需的所有控制,以确保新鲜度,同时限制计算任务的CPU负载.最重要的是,周期性任务和"按需"任务共享相同的逻辑 - 您定义任务一次并从两个地方调用它以增加DRYness.
在Django的缓存框架提供了你需要任何你想要的,只要你想缓存中的所有挂钩.大多数生产站点都依赖memcached作为缓存后端,我最近开始使用带有django-redis-cache后端的redis,但我不确定我是否相信它对于一个主要的生产站点.
下面是一些代码,展示了使用低级缓存API来完成上面列出的工作流程:
import pickle
from django.core.cache import cache
from django.shortcuts import render
from mytasks import calculate_stuff
from celery.task import task
@task
def calculate_stuff(user_id):
# ... do your work to update the averages ...
# now pull the latest series
averages = TransactionAverage.objects.filter(user=user_id, ...)
# cache the pickled result for ten minutes
cache.set("averages_%s" % user_id, pickle.dumps(averages), 60*10)
def myview(request, user_id):
ctx = {}
cached = cache.get("averages_%s" % user_id, None)
if cached:
averages = pickle.loads(cached) # use the cached queryset
else:
# fetch the latest available data for now, same as in the task
averages = TransactionAverage.objects.filter(user=user_id, ...)
# fire off the celery task to update the information in the background
calculate_stuff.delay(user_id) # doesn't happen in-process.
ctx['stale_chart'] = True # display a warning, if you like
ctx['averages'] = averages
# ... do your other work ...
render(request, 'my_template.html', ctx)
Run Code Online (Sandbox Code Playgroud)
编辑:值得注意的是,pickching一个查询集会将整个查询集加载到内存中.如果您使用平均值查询集提取大量数据,则可能不是最理想的.在任何情况下,使用真实数据进行测试都是明智之举.