预定的工作任务

时间:2014-02-25 10:33:25

标签: java mysql multithreading scheduled-tasks job-scheduling

主题:

我正在尝试用Java实现基本的作业调度来处理循环持久的计划任务(用于个人学习项目)。我不想使用任何(现成的)库,如Quartz / Obsidian / Cron4J /等。

目的:

  • 作业必须持久(处理服务器关闭)
  • 作业执行时间可能需要约2-5个月。
  • 管理大量工作
  • 多线程
  • 轻快;)

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

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

一步一步:

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

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

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

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

  5. 当服务器启动时,我将每个PENDING / RUNNING作业放在ThreadPoolExecutor中。

    问题/观察:

    • 第2步:ThreadPoolExecutor会处理大量线程(~20000)吗?
    • 我应该使用NoSQL解决方案而不是MySQL吗?
    • 处理此类用例是否是最佳解决方案?

    这是草稿,背后没有代码。我愿意接受建议,评论和批评!

2 个答案:

答案 0 :(得分:2)

我在一个真实的项目中完成了类似的任务,但在.NET中。以下是我对你的问题的回忆:

  

第2步:ThreadPoolExecutor会处理大量线程(~20000)吗?

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

由于您似乎运行了大量工作(对于一台计算机而言,20k是很多),因此您肯定应该寻找自定义线程池。不需要写自己的,我打赌有现成的解决方案,写一个远远超出你的学习项目所要求的* 看到评论(如果我理解你正在做一个学校或大学项目)。

  

我应该使用NoSQL解决方案而不是MySQL吗?

取决于。您显然需要同时更新作业状态,因此,您可以从多个线程同时访问一个表。假设您做得对,数据库可以很好地扩展。以下是我所说的正确行事:

  • 设计代码,其方式是每个作业只会影响数据库中自己的行子集(包括其他表)。如果能够这样做,则不需要在数据库级别上显式锁定(以事务序列化级别的形式)。您甚至可以强制执行可能允许脏读或幻像读取的自由序列化级别 - 这将更快地执行。但是要小心,您必须小心确保没有作业会在同一行上同意。这在现实生活中很难实现,所以你应该在db锁定中寻找替代方法。

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

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

  

处理此类用例是否是最佳解决方案?

是和否。

  • ,因为您将遇到开发人员在现实世界中面临的一项艰巨任务。我和同事一起工作过3次以上我自己的经验,他们比我更不愿意做多线程任务,他们真的很讨厌这个。如果您觉得这个区域对您来说很有趣,请尽量使用它,学习和改进。

  • ,因为如果您正在开展一个真实的项目,那么您需要一些可靠的东西。如果你有这么多问题,你显然需要时间来成熟,并能够为这样的任务产生稳定的解决方案。多线程是一个难题,原因有很多:

    • 很难调试
    • 它介绍了许多失败点,你需要了解所有这些失败
    • 除非您遵守普遍接受的规则,否则其他开发人员可能会很难协助或使用您的代码。
    • 错误处理可能很棘手
    • 行为是不可预测的/不确定的。

    现有的解决方案具有高水平的成熟度和可靠性,是实际项目的首选方法。缺点是你必须学习它们并检查它们如何根据你的需求进行定制。

无论如何,如果你需要按自己的方式去做,然后将你的成就移植到一个真实的项目,或者你自己的项目,我建议你以可插拔的方式做到这一点。使用抽象,编程到接口和其他实践来将您自己的特定实现与将设置预定作业的逻辑分离。这样,如果这成为问题,您可以将api调整为现有解决方案。


最后,但并非最不重要,我没有看到任何错误处理预测。思考并研究如果工作失败该怎么办。至少添加一个&#39; FAILED&#39;在这种情况下持续存在的状态或事物。在涉及线程时,错误处理很棘手,因此请对您的研究和实践进行彻底的处理。

祝你好运

答案 1 :(得分:1)

您可以使用ThreadPoolExecutor#setMaximumPoolSize(int)声明最大池大小。由于Integer.MAX大于20000,所以从技术上来说它是可以的。

另一个问题是你的机器是否支持这么多线程运行。你将提供足够的RAM,因此每个胎面将在堆栈上分配。

在现代台式机或笔记本电脑上address ~20,000 threads不应该出现问题,但在移动设备上,这可能是一个问题。

来自doc:

核心和最大泳池数量

  

ThreadPoolExecutor会自动完成   根据边界集调整池大小(请参阅getPoolSize())   by corePoolSize(参见getCorePoolSize())和maximumPoolSize(参见   getMaximumPoolSize())。在方法中提交新任务时   execute(java.lang.Runnable),并且少于corePoolSize线程   正在运行,创建一个新线程来处理请求,即使是其他   工作线程闲置。如果有超过corePoolSize但更少   比maximumPoolSize线程运行,将创建一个新线程   只有队列满了。通过设置corePoolSize和maximumPoolSize   同样,您创建一个固定大小的线程池。通过设置   maximumPoolSize为一个基本上无界限的值,如   Integer.MAX_VALUE,允许池容纳任意值   并发任务数。最典型的核心和最大池   尺寸仅在施工时设定,但也可以更改   动态使用setCorePoolSize(int)和setMaximumPoolSize(int)。

More

关于数据库。创建不依赖于DB结构的解决方案。然后,您可以设置两个enviorements并测量它。从您熟悉的技术开始。但请继续关注其他解决方案。在开始时,关系数据库应该跟上性能。如果你适当地管理它,它应该不会成为一个问题。 NoSQL用于处理非常大的数据。但最适合你的是创建两个并运行一些性能测试。