使用多线程在 Docker Swarm 上分布式 Java 应用程序

Distributed Java App on Docker Swarm with Multithreading

我正在 Docker Swarm[= 上迁移 Grails (2.4.4) Web 应用程序(基于 Java) 31=] 环境(3 个节点)和 MariaDB 数据库 Swarm 之外。

在其当前版本中,该应用程序托管在单个服务器上并实例化了几个线程(~ 10 个),它们都有特定的任务要做,但都在处理数据库中的数据。

既然应用程序将被复制到 Swarm 的 3 个节点上,我认为在每个节点上实例化所有线程没有意义,因为它们会对相同的数据执行相同的操作(位于 Swarm 之外的数据库机器上),并且由于并发访问和 MySql 事务,它可能无法工作。

因此,考虑到线程不能在应用程序源代码之外重新开发这一事实,因为它们依赖于应用程序的模型,我的问题是:您认为这个用例的最佳解决方案是什么?我个人考虑过两种选择,但我觉得我的方向不对:

  1. 在进程开始时同步线程:它只会是同类线程中第一个真正完成这项工作的线程,其他 2 个线程会让自己重新进入睡眠状态.我会用数据库中的锁定机制来做到这一点。
  2. 只在一个节点上实例化线程:我觉得应该可以,但是我对这个真的不确定,因为它与核心原理非常矛盾并且拥有可复制和可扩展应用程序的优势

所以,很高兴听到有关此事的任何建议!谢谢

最简单的选项(上述第 2 个)是参数化任务 运行,以便通过配置在一个实例上激活任务并在所有其他实例上禁用。虽然这种方法看起来简单合理,但你会 运行 遇到问题,当你必须扩大和缩小你的群体时。

如果激活了任务的实例被杀了会怎样?需要什么迁移?总而言之,这是一个大问题区。

另一种选择是将线程代码分解为 "worker-style" 应用程序。

您将不得不重新设计和模块化您的应用程序,以便任务可以 运行 在您原来的 Grails 应用程序之外。在这种情况下,您可以自由独立地扩展您的主应用程序和辅助应用程序。

worker 应用程序不必基于 Grails,您可以选择具有原生 Groovy 支持的任何其他框架,例如 Vert.x(推荐)、Micronaut 或 Ratpack。

您可以更改应用程序的逻辑,使每个实例启动更少的线程,然后启动 5 个实例,每个实例有 2 个线程,而不是一个具有 10 个线程的实例,并要求 Swarm 为您扩展它。

另一种选择是将多个实例连接成一个 "cluster" 并使用某种机制来选举领导者并仅在领导者节点上启动所有 treads。然后,如果 leader 宕机,您需要重新选举 leader 并在新的 leader 上重新启动任务。

这里有很多可能的设计,它们在很大程度上都取决于您实际想要实现的目标:

你说:

On its current version, the application is hosted on a single server and instantiates a few threads (~ 10) which all got a specific task to do

假设您选择选项 2,并且所有线程 运行 在三个节点中的一个节点上。

在这种情况下,这是一种主动-被动架构,一个节点工作,其他节点基本上什么都不做(好吧,我不知道也许他们做其他事情,根据您提供的信息,它超出了范围)。所以这些节点是为了冗余而维护的?

但如果是这样,工作节点将"absorb"所有负载直到它失败,然后所有负载都到达变为活动的节点,但也许它的负载太多并且它会失败,然后第三个节点将像多米诺骨牌一样失败:)

此方法的另一个问题是如何在 node1 失败时实际激活 node2,谁来决定 node2(而不是 node3 ) 现在活跃吗? 如果配置 "run-without-threads" 已经指定,你如何生成线程?

如果你能回答这些问题,不要期望单个节点的压力很大,并且同意维护节点以冗余 - 这是否可以走,很多系统都是这样构建的,所以它是可行的解决方案。

另一种解决方案是将active-active架构的解决方案完全横向扩展,这样一部分任务将由node1承担,另一部分将承担由 node2node3 处理。

这里有很多可能的选择。 你说

which all got a specific task to do

这个任务究竟是谁触发执行的?是不是某个计划的作业偶尔运行一次并提交任务以供执行?或者任务可能是由于来自客户端的某些请求(例如通过 http 调用)而产生的?

另一个问题是是否存在基本上不应重叠的任务,或者每个任务都可能破坏另一个任务的执行?

如果有机会分离任务,那么您可以在新任务到达时并基于某些 partitionId(如果您使用 kafka 之类的东西)或 rabbit mq 中的路由键或任何其他方式向某个队列发送消息关于集群,您可以构建一个架构,其中任务可以按类型组合在一起,一个特定的服务器将负责执行整组任务,另一组任务可以由另一台服务器执行。

如果服务器出现故障,则之前由故障服务器处理的任务组将被重新分配给另一台服务器(技术细节因解决方案而异)。