繁体   English   中英

预定的工作任务

[英]Scheduled Job Task

学科:

我正在尝试在Java中实现基本的作业计划,以处理重复的持久性计划任务(针对个人学习项目)。 我不想使用任何(即用型)库,例如Quartz / Obsidian / Cron4J / etc。

目的:

  • 作业必须是持久的(以处理服务器关闭)
  • 作业执行时间最多可能需要2到5百万。
  • 处理大量工作
  • 多线程
  • 轻便快捷;)

我所有的工作都在MySQL数据库中。

JOB_TABLE (id, name, nextExecution,lastExecution, status(IDLE,PENDING,RUNNING))

一步步:

  1. 从“ JOB_TABLE “nextExecution > now” AND “status = IDLE“JOB_TABLE ”中检索每个作业。 每10秒钟由一个线程执行此步骤。

  2. 对于每个检索到的作业,我在ThreadPoolExecutor放置了一个新线程,然后在“ JOB_TABLE ”中将作业状态更新为“ PENDING ”。

  3. 当作业线程正在运行时,我将作业状态更新为“ RUNNING ”。

  4. 作业完成后,我将使用当前时间更新lastExecution ,设置新的nextExecution时间,并将作业状态更改为“ IDLE ”。

服务器启动时,我将每个PENDING / RUNNING作业放入ThreadPoolExecutor

问题/观察:

  • 步骤2:ThreadPoolExecutor是否可以处理大量线程(〜20000)?
  • 我应该使用NoSQL解决方案代替MySQL吗?
  • 这是处理此类用例的最佳解决方案吗?

这是草稿,没有任何代码。 我愿意提出建议,评论和批评!

在.NET中,我已经完成了与您在实际项目中相似的任务。 关于您的问题,这是我能想到的:

步骤2:ThreadPoolExecutor是否可以处理大量线程(〜20000)?

我们发现.NET的内置线程池是最糟糕的方法,因为该项目是一个Web应用程序。 原因:该Web应用程序依赖于内置线程池(该线程池是静态的,因此在运行的进程内用于所有用途是共享的)在单独的线程中运行每个请求,同时保持有效的线程回收。 为我们的内部处理使用相同的线程池将耗尽它,并且不会为用户请求保留任何空闲线程,或者破坏其性能,这是不可接受的。

由于您似乎正在运行大量作业(一台机器需要20k,所以很多工作),所以您绝对应该寻找自定义线程池。 不过,您无需自己编写,我敢打赌,这里有现成的解决方案,并且编写的解决方案远远超出您的研究项目的要求*。 请参阅评论 (如果我正确理解您正在做的是学校或大学的项目)。

我应该使用NoSQL解决方案代替MySQL吗?

要看。 显然,您需要同时更新作业状态,因此,您将可以同时从多个线程访问一个表。 假设您做对了,数据库可以很好地扩展。 这就是我所说的正确执行的操作:

  • 设计代码的方式应使每个作业仅影响数据库中它自己的行子集(包括其他表)。 如果可以,则不需要在数据库级别上任何显式的锁定(以事务序列化级别的形式)。 您甚至可以强制执行自由序列化级别,该级别可能允许进行脏读或幻像读取-执行速度更快。 但是要当心 ,您必须仔细确保在同一行上没有作业并发。 在现实项目中很难做到这一点,因此您可能应该在数据库锁定中寻找替代方法。

  • 使用适当的事务序列化模式。 事务序列化模式在数据库级别定义锁定行为。 您可以将其设置为锁定整个表,仅锁定受影响的行或什么都不锁定。 明智地使用它,因为任何滥用都会影响整个应用程序或数据库服务器的数据一致性,完整性和稳定性。

  • 我对NoSQL数据库不熟悉,因此我只能建议您研究并发功能并将其映射到您的方案。 您最终可能会找到一个非常合适的解决方案,但必须根据需要进行检查。 根据您的描述,您将必须支持在相同类型的对象(表的模拟物)上同时进行数据操作。

这是处理此类用例的最佳解决方案吗?

是和否

  • 是的 ,因为您将遇到开发人员在现实世界中面临的困难任务之一。 我与同事的合作经验是我的3倍以上,他们比我更不愿意执行多线程任务,他们真的很讨厌。 如果您觉得这个领域很有趣,请尝试并学习,并尽可能多地提高自己。

  • ,因为如果您正在做一个真实的项目,则需要可靠的东西。 如果您有很多问题,显然您将需要时间来成熟,并且能够为该任务提供稳定的解决方案。 多线程是一个困难的话题,原因有很多:

    • 很难调试
    • 它引入了许多故障点,您需要了解所有这些点
    • 除非您遵守公认的规则,否则其他开发人员可能难以协助或使用您的代码。
    • 错误处理可能很棘手
    • 行为是不可预测的/不确定的。

    现有的成熟度和可靠性高的解决方案是实际项目的首选方法。 缺点是您将必须学习它们,并检查它们如何满足您的需求。

无论如何,如果您需要按自己的方式做,然后将成就移植到一个真实的项目或您自己的项目中,我可以建议您以可插拔的方式进行。 使用抽象, 接口编程和其他实践将您自己的特定实现与设置计划的作业的逻辑脱钩。 这样,如果这成为问题,则可以使您的api适应现有解决方案。


最后但并非最不重要的一点是 ,我没有看到任何错误处理方面的预测。 思考并研究如果工作失败了该怎么办。 至少添加“失败”状态或在这种情况下可以保留的状态。 当涉及到线程时,错误处理非常棘手,因此请仔细研究和实践。

祝好运

您可以使用ThreadPoolExecutor#setMaximumPoolSize(int)声明最大池大小。 由于Integer.MAX大于20000,因此从技术上讲可以。

另一个问题是您的计算机是否支持这么多线程来运行。 您将提供足够的RAM,以便每个踏步都将在堆栈上分配。

在现代台式机或笔记本电脑上处理约20,000个线程应该不成问题,但在移动设备上可能是个问题。

从文档:

核心和最大池大小

ThreadPoolExecutor将根据corePoolSize(请参见getCorePoolSize())和maximumPoolSize(请参见getMaximumPoolSize())设置的边界自动调整池大小(请参见getPoolSize())。 当在方法execute(java.lang.Runnable)中提交新任务,并且正在运行的线程少于corePoolSize线程时,即使其他工作线程处于空闲状态,也会创建一个新线程来处理请求。 如果运行的线程数大于corePoolSize但小于maximumPoolSize,则仅在队列已满时才创建新线程。 通过将corePoolSize和maximumPoolSize设置为相同,可以创建固定大小的线程池。 通过将maximumPoolSize设置为一个本质上不受限制的值(例如Integer.MAX_VALUE),可以允许池容纳任意数量的并发任务。 通常,核心和最大池大小仅在构造时设置,但也可以使用setCorePoolSize(int)和setMaximumPoolSize(int)动态更改。

更多

关于数据库。 创建一个不依赖于数据库结构的解决方案。 然后,您可以设置两个环境并进行测量。 从您知道的技术开始。 但是,请保持其他解决方案的开放性。 在开始时,关系数据库应该跟上性能。 而且,如果您正确地管理它,那么以后也不应该成为问题。 NoSQL用于处理真正的大数据。 但是,最适合您的是创建两者并运行一些性能测试。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM