主题:

我正在尝试用 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”.

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

问题/观察:

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

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

有帮助吗?

解决方案

我在一个真实的项目中完成了与您类似的任务,但是是在 .NET 中。关于你的问题,我能记得的是:

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

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

由于您似乎正在运行相当多的作业(对于一台机器来说 20k 已经很多了),那么您绝对应该寻找一个自定义线程池。不过,无需自己编写,我敢打赌已经有现成的解决方案,并且编写一个远远超出了您的研究项目的要求* 看评论 (如果我理解正确的话,你正在做一个学校或大学项目)。

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

依靠。显然,您需要同时更新作业状态,因此,您将可以从多个线程同时访问一个表。假设你做对了事情,数据库可以很好地扩展到这一点。我所说的正确做法如下:

  • 设计你的代码 在某种程度上,每个作业只会影响数据库中自己的行子集(这包括其他表)。如果您能够这样做,那么您将不需要数据库级别的任何显式锁定(以事务序列化级别的形式)。您甚至可以强制执行自由序列化级别,该级别可能允许脏读或幻读 - 这将执行得更快。但 谨防, ,您必须仔细确保没有作业会在相同的行上并发。这在现实项目中很难实现,因此您可能应该在数据库锁定中寻找替代方法。

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

  • 我对NoSQL数据库不熟悉,所以我只能建议你研究并发能力并将其映射到你的场景。您最终可能会得到一个真正合适的解决方案,但您必须根据您的需求进行检查。根据您的描述,您必须支持对同一类型的对象进行同步数据操作(表的模拟是什么)。

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

是和否。

  • 是的, ,因为您将遇到开发人员在现实世界中面临的困难任务之一。我曾与经验是我经验三倍以上的同事一起工作,他们比我更不愿意执行多线程任务,他们真的很讨厌这样做。如果你觉得这个领域对你来说很有趣,那就去尝试一下,尽可能多地学习和提高。

  • , ,因为如果您正在从事现实生活中的项目,您需要可靠的东西。如果您有这么多问题,那么您显然需要时间来成熟并能够为此类任务提供稳定的解决方案。多线程是一个困难的话题,原因有很多:

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

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

无论如何,如果您需要按照自己的方式进行操作,然后将您的成果移植到真实的项目或您自己的项目中,我可以建议您以可插入的方式进行此操作。使用抽象, 接口编程 以及其他实践,将您自己的特定实现与设置计划作业的逻辑分离。这样,如果出现问题,您可以将 api 调整为现有解决方案。


最后, 但并非最不重要的, ,我没有看到你这边有任何错误处理预测。思考并研究如果工作失败该怎么办。至少添加“失败”状态或在这种情况下持续存在的状态。对于线程来说,错误处理很棘手,因此要彻底研究和实践。

祝你好运

其他提示

您可以使用 ThreadPoolExecutor#setMaximumPoolSize(int) 声明最大池大小。作为 Integer.MAX 大于 20000 那么从技术上来说是可以的。

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

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

来自文档:

核心和最大池大小

ThreadPoolExecutor将根据CorePoolSize设置的界限自动调整池大小(请参阅GetPoolSize())(请参阅GetCorePoolSize())和MaxImimumpoolSize(请参阅GetMaximumpoolSize())。当在方法执行中提交新任务(java.lang.runnable),而与CorePoolSize线程一起运行少时,即使其他Worker线程是空间,也会创建一个新线程来处理请求。如果不仅有corepoolsize,但比运行的Maximumpoolsize线程少,只有在队列已满时才会创建一个新线程。通过设置CorePoolSize和Maximumpoolsize相同,您可以创建一个固定尺寸的线程池。通过将maximumpoolsize设置为基本无限的值,例如integer.max_value,您可以允许池容纳任意数量的并发任务。通常,仅在施工时设置核心和最大池尺寸,但也可以使用setCorePoolSize(int)和setMaximumpoolsize(int)动态更改它们。

更多的

关于数据库。创建一个不依赖于数据库结构的解决方案。然后你可以设置两个环境并测量它。从您了解的技术开始。但对其他解决方案保持开放态度。一开始关系数据库应该跟上性能。如果你管理得当,以后就不会有问题了。NoSQL 用于处理真正的大数据。但最适合您的是创建两者并运行一些性能测试。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top