“自己写。”不,真的。
保留要发送的发票的数据库表。每张发票都有一个状态(pending、sent、paid、...等值)和一个发票日期(可能是将来)。使用cron 或类似方法定期运行(例如 1/小时、1/天)一个程序,该程序查询表中的未决发票,发票日期/时间已到达/已过,但尚未发送发票。此开票流程发送发票、更新状态并完成。此发票实用程序不会集成到您的 Flask Web 应用程序中,但作为支持程序存在于它旁边。
为什么?这是简单直接的方法。它将发票代码保存在 Python 中,接近您选择的应用程序语言和数据库。它不需要深入了解外部系统或中间件。使用与编写应用程序本身相同的数据库、查询和技能,调试和监控非常简单。简单,直接,可靠,完成。什么是不爱的?
现在,我完全理解“自己编写”的建议与典型的“购买而不是构建”原则背道而驰。但是我已经尝试了所有主要的替代方案,例如cron 和Celery;我对产生收入的网络应用程序的经验表明,它们不是处理数百个长期发票事件的方法。
TL;DR--为什么不是 Cron 和 Celery?
cron 及其后来的等价物(例如launchd 或Heroku Scheduler)运行重复事件。每 10 分钟,每小时,每天一次,每隔一个星期二凌晨 3:10 UTC。它们通常不能解决“在未来 X 的时间和日期运行一次”问题,但它们非常适合定期工作。
现在最后一个陈述并不完全正确。它描述了cron 及其一些替代品。但即使是传统的 Unix 也提供了 at 作为 cron 的副车,并且一些 cron 后续(例如 launchd、systemd)将重复和未来的事件调度捆绑在一起(以及其他厨房用具和众所周知的水槽)。即便如此,还是有一些问题:
- 您依赖外部调度系统。这意味着如果出现问题,可以使用另一个界面来学习、监控和调试。这些系统级调度程序和您的 Python 应用程序之间存在显着的阻抗不匹配。即使您将“在时间 X 运行事件”外包出去,您仍然需要编写 Python 代码来发送发票并将其正确地移动到您的业务工作流程中。
- 这些系统对于少数事件来说很漂亮,但通常缺少能够直接查看、修改、监视或调试数百个未完成事件的接口。在大量预定事件中调试生产应用程序错误是令人痛心的。您正在谈论向此外部系统提交 300 多个待处理事件。您还必须考虑如何监控和调试该使用。
- 这些调度程序是为“常规”而非“高价值”或“高可用性”操作而设计的。只是一个问题,如果安排了一个活动,但你需要停机(计划内或计划外)怎么办?如果在系统备份之前事件时间过去了,那会怎样?大多数
cron-like 调度程序缺乏稳健处理“错过”事件或“尽早弥补”的规定。用技术术语来说,这可能是“无赖,伙计”。假设该事件触发了收款 - 或者在您的情况下是发票发行。您有数百张发票,并且开具这些发票可能对业务至关重要。系统级调度程序与您的运营需求之间的能力差距可能真的很痛苦,尤其是在您进行扩展时。
好的,将这些事件驱动到像 Celery 这样的外部事件调度程序中怎么样?这是一个非常更好的主意。 Celery 旨在运行大量应用程序事件。它支持各种后端引擎(例如 RabbitMQ),在实践中证明可以处理数以千计的数以千计的事件,并且它具有帮助处理大量事件的用户界面选项。到目前为止,一切都很好!但是:
- 您会发现自己在处理安装、配置和操作外部中间件(例如 RabbitMQ)的复杂性。这项工作产生了非常高的可实现规模,但启动和运营成本是真实的。即使您将大部分数据集中到 Heroku 之类的云服务上也是如此。
- 更重要的是,虽然 Celery 作为近期事件的作业调度器非常出色,但作为长时间等待的调度器并不理想。在生产中,我看到了“长距离”事件的严重问题(那些发布一个月,或者在你的情况下三个月,在未来)。虽然问题并不相同,就像
cron 等一样,但 Celery 长抛出事件与正常的应用程序更新和重启周期相交并不优雅。这取决于环境,但发生在 Heroku 等流行的云服务上。
Celery 问题并非完全无法解决或致命,但长时间延迟的事件不会享受同样的“哇!Celery 让一切工作变得更好!”为大量近期事件而获得的魔力。而且你必须成为一个有点像 Celery、RabbitMQ 等的工程师和管理员。仅仅安排几百张发票,这是一个高昂的价格和大量工作。
总而言之:虽然未来的发票计划似乎需要外包,但在实践中,将该功能保留在您的主应用程序代码中(不是直接在您的 Flask Web 应用程序中,而是作为关联的实用程序),然后将“提醒我每天运行 N 次”低级 tickler 转移到系统级作业调度程序。