发布并发和连续作业ID



我需要发布并发且尽可能连续的作业ID,最好是以一种相当无缝和轻量级的方式。我使用的是SQLAlchemy和Postgresql。

连续的

我只希望在作业成功并且ID保留在新编写的DB行中的情况下,将发出的ID视为已获取。这样,数据库中已发布的ID就不会有任何间隙。如果DB事务在作业期间失败,我希望为下一个作业释放该ID。如果要发出的第一个ID是1,并且前5个作业都失败了,我希望第6个作业尝试发出ID 1,而不是6。

并发

我可以将max(ID) + 1作为下一个ID,但这不适用于并发请求,因为所有并发作业都将使用相同的编号。

我知道不能保证连续性。如果同时启动5个作业,每个作业的ID为1-5,并且只有#5个作业存活下来,那么我只保留#5个。没关系。我有低并发性和大量的作业失败,如果不尝试邻接,我会在序列中留下巨大的漏洞。请求通常不会并发,因此出现间隔的可能性很低。结果至多是偶尔出现一个小缺口。

想法

  1. 我可以编写一个向并发作业发出ID的服务,但它需要一种方法来知道客户端作业是否失败才能释放ID。这也是一个单点故障,而且需要太多的额外工程
  2. 我想让每个作业都将max(ID) + 1放在DB的一个临时表中,这样其他作业就可以看到未提交的更改。如果事务失败,新ID也会随之脱落。从这个意义上说,所有作业实际上都会选择max(ID of completed jobs, uncommitted IDs in the temp table) + 1。如果提交了新的ID,我就不再需要它,并且可以从临时表中删除它。这是一个尴尬的模式,我不知道该怎么做
  3. 我可以执行上述操作,但提交正在进行的ID,并在成功完成作业时将其删除。因此,该表将表示";过程中";ID。如果没有办法删除那些失败事务的ID,我需要一些周期性的修剪来删除";"放弃";基于年龄的ID或一些不太理想的启发式
  4. 我可以做上述操作,并在表中包含PID。周期性进程会删除PID不再有效的行,但该解决方案不会扩展到分布式设置,我不希望轮询进程一直处于活动状态,以便系统正常工作
  5. 或者可以使用DB会话ID而不是PID?至少这是可分发的,而且我可以更快、更容易地捕获无效的DB会话?但我的应用程序可能需要管理员权限来检查DB会话ID是否有效
  6. 对我来说,理想的解决方案是#3+,如果SQLAlchemy中有一种方法可以只在事务失败时运行一些DB代码。我假设所有失败都会在Python中显示为异常,所以可能是某种全局except()块,但试图将DB事务失败与我不想全局捕捉的其他Python异常区分开来可能会很麻烦。如果我可以向SQLAlchemy注册一些清理代码,以便在事务失败时运行,这将删除已颁发的ID
  7. 类似的东西,但在DB上,比如事务失败时的触发器
  8. 我可以发布一个UUID作为作业编号,在事务提交成功后,将UUID映射到max(ID) + 1,但我不确定它是否是并发的,这会很乏味,因为ID标记在作业期间创建的一堆文件上,所以我必须四处重命名磁盘上的所有内容
  9. 以某种方式利用Postgresql序列?但他们似乎并不关心相邻性

有什么巧妙的方法吗?如果不是,我倾向于#3,因为它很简单。

我希望在事务名义上或异常结束时会调用SQLAlchemy或Postgres钩子,但如果应用程序崩溃,after_transaction_end事件不幸不会触发。

对我有效的解决方案是#6的变体,但使用了上下文管理器而不是全局finally()块。关键的见解是Python在名义操作期间和崩溃后都可靠地调用__exit__,而不必全局捕获异常。唯一缺少的部分是我强制停止Python调试器,但为此PyCharm可以配置为轻柔地终止进程,它将调用__exit__

我的解决方案如下:

  1. 创建一个对作业id具有PK/唯一约束的活动作业表
  2. 对于新作业id,选择最大值(现有、活动作业)
  3. 提交活动作业表中的新作业id。如果两个并发作业选择了相同的作业id,那么插入第二个作业将失败,因为这会违反唯一约束,从而确保并发性
  4. 使数据类成为上下文管理器,其中__exit__从活动作业表中删除其作业id
  5. with子句中使用数据类
  6. 将PyCharm配置为轻柔地终止进程,以便调试器终止仍将清理活动作业表

我发现这比异常处理更优雅,而且非常健壮。

相关内容

  • 没有找到相关文章

最新更新