Python 子进程默认情况下不可扩展,您可以推荐任何简单的解决方案来使其可扩展吗?

lai*_*son 3 python linux scaling subprocess kubernetes

我有一个应用程序可以执行以下操作:

subprocess.Popen(["python3", "-u", "sub-program-1.py"])
Run Code Online (Sandbox Code Playgroud)

因此Python程序可以按需启动多个长寿命进程。

如果我停止主Python程序并再次启动,它知道sub-program-1.py应该启动,因为数据库中有一条关于状态的记录告诉它。

因此,当 Docker 容器、Pod、虚拟机​​或任何你称之为的东西只有一个副本时,它就可以正常工作。


如果我将应用程序扩展到 3 个副本,subprocess则无法实现。

  1. 每个 Docker 容器都会启动sub-program-1.py,而我想在一个容器上启动

  2. 如果一个容器发生故障,应用程序应该足够智能,能够故障转移sub-program-1.py到另一个容器

  3. 一个应用程序应该足够智能,能够平衡容器之间的子进程,例如:sub-program-1.py-sub-program-9.py理想情况下应该通过每个容器放置 3 个进程来进行扩展,因此总共有 9 个子进程正在运行 - 我不需要这个很精确,最简单的解决方案就可以了来平衡它


我尝试探索 RQ(Redis 队列)和类似的解决方案,但它们主要关注任务,最好是短期任务。就我而言,它们是长期存在的进程。例如sub-program-1.py可以活几个月甚至几年。


方案是这样的:

主要 Python 应用程序 -> sub-program-1.pysub-program-2.py等。

这里有没有任何简单的解决方案,没有开销?

将每个子程序的状态写入数据库是否是一个选项(还可以根据数据库中的状态检测子进程何时无法将其故障转移到另一个容器),或者您是否会合并其他工具来解决subprocess扩展问题?


另一种选择是启动sub-program-1.py所有容器并扩展其中的操作。sub-program-1.py基本上就是调用一些第三方的API,根据用户的喜好做一些操作。因此,根据每个用户偏好扩展这些 API 调用很复杂,当同时调用 API 时,它在后台有多个线程。简而言之,sub-program-1.py与 user1 绑定,sub-program-2.py与 user2 绑定,等等。那么是否值得通过选择此选项使其变得复杂?


更新

Ifsubprocess仅在独立应用程序中使用,没有人尝试在 Github、库等上以可找到的规模实现此机制。

你会如何用Python解决这个问题?

我想到了数据库中的这些条目:

ProcessName        ProcessHostname        LastHeartBeat            Enabled
process1            host-0                2022-07-10 15:00        true
process2            null                    null                    true
process3            host-1                2022-07-10 14:50        true
Run Code Online (Sandbox Code Playgroud)

所以为了解决我上面写的三点:

  1. 每个容器都会尝试拾取尚未拾取的进程(其中 是null或 的旧日期LastHeartBeat)。当第一个容器拾取进程时,它会写入日期 LastHeartBeat,然后用于subprocess启动进程。LastHeartBeat其他容器如果不断更新则无法拾取。

  2. 如果进程失败,它不会写入LastHeartBeat,因此其他容器会按照第 1 点中所述拾取该进程。如果失败的容器无法到达数据库,它将停止操作并重新启动(如果它甚至能够做到exit)。如果它无法到达 DB,则不会执行任何操作。也就是说,不要运行同一进程两次。

  3. 为了平衡容器之间的进程,运行较少进程的容器可以选择一个新进程。该信息位于数据库表中以供做出决定。

你会用不同的方式解决吗?您有什么推荐的最佳实践吗?

谢谢

zer*_*er0 6

TL;DR - 这是一个经典的整体应用程序扩展问题,您可以通过将应用程序重新设计为微服务架构来轻松解决此问题,因为您的应用程序功能本质上是在组件之间解耦的。完成此操作后,一切实际上都归结为以原生微服务友好的方式部署应用程序,并且您的所有设计要求都将得到满足。

编辑:您当前正在尝试在微服务系统中“扩展”您的应用程序(1 个 Pod 中的多个进程/容器),这违背了使用它的全部目的。您必须坚持使用 1 个子进程 <===> 1 个 pod 才能使设计真正发挥作用。否则,您只会引入巨大的复杂性,这违背了微服务的许多设计原则。如果您有兴趣,请在下面了解更多详细信息。

让我首先总结一下您所说的所有内容,以便我们能够连贯地讨论设计。

申请要求

据我从您提供的所有信息中了解到您的申请要求,以下情况属实:

  1. 你希望你processes成为long-lived
  2. 你有一个application产生这些long-lived进程的父进程。
  3. 这些processes需要按需启动。(动态缩放 - 和横向扩展;请参阅下面的(7)了解process每1 个container
  4. 如果没有负载,您application应该只生成 1processsub-process.py.
  5. 如果container失败,您希望application能够智能地将流量切换到container也运行您的长期运行的健康流量process
  6. 应该application能够在当前运行的所有processes/之间共享负载containers
  7. Aprocess与用户请求相关,因为它调用第三方API系统来执行其功能。process因此,为了简化设计,容器内只放一个是有利的。

当前设计的局限性

当前的应用程序设计

目前,您的应用程序设置方式如下:

  1. 您有一个进程通过应用程序进程application生成多个相同的sub-process.py进程。
  2. 面向application用户,接收请求,sub-process.py根据需要生成进程,并在一个计算单元(容器、虚拟机等)内很好地扩展
  3. 然后,它们processes执行其操作,并将响应返回给用户,然后将application其返回给用户。

现在,让我们讨论一下您上面提到的当前方法,看看您所描述的挑战是什么。

扩展设计 1 - 简单扩展 Docker 容器

这意味着只需为您的应用程序创建更多容器即可。我们知道它不满足要求,因为将应用程序扩展到多个副本会启动所有副本processes并使它们active。这不是您想要的,因此不同容器中的这些副本之间没有关系(因为子进程与application每个容器中的运行相关,而不是与整个系统相关)。这显然是因为application不同容器中的 s 彼此不知道(更重要的是,每个子进程都在生成)。

所以,这不满足我们的要求(3)、(4)、(5)。

扩展设计 2 - 使用数据库作为状态存储

为了尝试满足(3)、(4)和(5),我们引入了一个database对于我们整个系统至关重要的一个,它可以在我们的系统中保存不同的状态数据processes以及如何containers将某些数据“绑定”到进程并管理它们。然而,正如您指出的那样(加上我自己的想法),这也有一定的局限性:

  1. 这样的解决方案有利于short-lived流程。
  2. 我们必须引入一个高速数据库,并且能够以非常快的速度维护状态,并且有可能race conditions
  3. 我们必须在容器之上编写大量内务代码进行编排,这些代码将使用此规则database和一些已知规则(您定义为最后 3 点)来实现我们的目标。尤其是一个编排组件,它将知道何时按需启动容器。这是非常复杂的。
  4. 我们不仅需要产生新的进程,还希望能够处理故障和自动流量切换。这将要求我们实现一个“网络”组件,该组件将与我们的编排器进行通信并检测失败的容器,并将传入流量重新路由到健康的容器并重新启动失败的容器。
  5. 我们还要求此网络服务能够在系统中当前的所有容器之间分配传入流量负载。

这无法满足我们的要求(1)和(7),最重要的是,这是重新发明轮子!

让我们来谈谈 Kubernetes 以及为什么它正是您所需要

建议的解决方案

现在让我们看看如何以最小的努力重新设计整个问题并满足我们的所有要求。

拟议的应用程序设计

我建议您可以非常简单地将 yourapplication与 your processes. 这很容易做到,因为您的应用程序正在接受用户请求并将它们转发到相同的工作人员池,这些工作人员通过进行第 3 方 API 调用来执行操作。从本质上讲,这完美地映射到微服务上。

user1 =====>      |===> worker1 => someAPIs
user2 =====>  App |===> worker2 => someAPIs
user2 =====>      |===> worker3 => someAPIs
    ...
Run Code Online (Sandbox Code Playgroud)

我们可以明智地利用这一点。请注意,不仅元素解耦,而且所有工作人员都执行一组相同的功能(这可能会根据使用输入产生不同的输出)。本质上你将替换

user1 =====>      |===> worker1 => someAPIs
user2 =====>  App |===> worker2 => someAPIs
user2 =====>      |===> worker3 => someAPIs
    ...
Run Code Online (Sandbox Code Playgroud)

通过对服务的 API 调用,该服务可以根据需要为您提供工作人员:

output = some_api(my_worker_service, user_input)
Run Code Online (Sandbox Code Playgroud)

这意味着,您的应用程序设计已被保留,您只需将流程放置在不同的系统上即可。所以,应用程序现在看起来像这样:

user1 =====>                         |===> worker1 => someAPIs
user2 =====>  App ==>worker_service  |===> worker2 => someAPIs
user2 =====>                         |===> worker3 => someAPIs
    ...
Run Code Online (Sandbox Code Playgroud)

应用程序重新设计的这一重要组成部分就位后,让我们重新审视之前设计中的问题,看看这是否对我们有帮助以及 Kubernetes 如何发挥作用。

提议的扩展解决方案 - 进入 Kubernetes!

当您描述使用数据库来维护state整个系统以及能够检索containers系统中当前状态并做出某些决策的编排逻辑时,您绝对走在正确的道路上。这正是Kubernetes 的工作原理

现在让我们看看 Kubernetes 如何解决我们的问题

  1. processes在 Kubernetes 中可以长久存在。因此,满足了要求(1),并且也减轻了我们数据库设计的限制(1)。
  2. 我们引入了一个可以为我们service管理所有工人的系统。processes所以,要求(2)满足。它还能够processes按需扩展,因此满足要求(3)。它还将保持最小process计数,1这样我们就不会产生不必要的进程,从而满足要求(4)。它将足够智能,仅将流量转发到processesat healthy。因此,满足要求(5)。它还会在processes其管辖的所有对象之间对流量进行负载平衡,因此满足要求 (6)。该服务还将减轻我们第二个设计的限制(4)和(5)。
  3. 您将被允许根据processes需要调整规模,以确保您只使用所需的资源。因此,满足要求(7)。
  4. 它使用一个名为 的中央数据库etcd,该数据库存储整个集群的状态并始终保持更新,并适应竞争条件(多个组件更新相同的信息 - 它只是让第一个到达的组件获胜而另一个组件失败)一,强制其重试)。我们已经从第二个设计中解决了问题(2)。
  5. 它带有processes开箱即用的编排逻辑,因此无需编写任何代码。这减轻了我们第二个设计的限制(3)。

因此,我们不仅能够满足我们的所有要求,还能够实现我们想要实现的解决方案,而无需为编排编写任何额外的代码!(您只需稍微重组您的程序并引入 API)。

如何实施

请注意,在 k8s 文献中,最小的计算单元被称为pod执行单个功能。从架构上讲,这与您对 的描述相同sub-process。因此,每当我谈论“Pod”时,我只是指您的sub-processes.

您将(大致)采取以下步骤来实现建议的设计。

  1. 重写应用程序的某些部分以与 解耦applicationsub-process.py在它们之间引入 API。
  2. 打包sub-process.py成容器镜像。
  3. 部署一个小型 Kubernetes 集群。
  4. Deployment使用您的图像创建一个sub-process.py,并将最小重复计数设置为 1,最大重复计数设置为您想要的任何数字,例如 10,以进行自动缩放。
  5. Deployment通过创建一个Service. 这就是我讲的“worker 服务”,你application会向这个服务“提交”请求。除了简单地向 API 端点发出请求之外,它不必担心任何事情,其他一切都由 k8s 处理。
  6. 配置您的应用程序以对此进行 API 调用Service
  7. 测试您的应用程序并观察它的扩展和缩小!

现在,其运作方式如下:

  1. 客户向您的application.
  2. application将其转发到ServiceAPI 端点。
  3. 接收ServiceAPI 请求并将其转发到Pods正在运行您的sub-process.py功能的服务器之一。如果收到多个请求,Service将平衡所有可用 Pod 之间的请求。如果 pod 发生故障,K8s 将把它从服务中“移除”,这样请求就不会失败。
  4. Pod 将执行您的功能并提供输出。
  5. 如果 中的所有 pod 都Service达到饱和,Deployment则会触发自动扩展并为其创建更多 pod,Service并且负载共享将再次恢复(横向扩展)。如果资源利用率随后降低,则会Deployment删除某些不再使用的 pod,并且您将恢复到 1 个 pod(缩小)。

如果您愿意,您也可以将您的frontend应用程序放入DeploymentService中,这将使您拥有更加友好的云原生微服务架构。用户将与您的 API 进行交互,该front-endAPI 将调用Service管理您的sub-process.py工作人员的 API,并返回结果。

我希望这对您有所帮助,并且您可以体会到微服务架构如何清晰地融入您所拥有的设计模式,以及如何非常简单地适应它并根据需要扩展您的应用程序!不仅如此,以这种方式表达您的设计还允许您通过简单地管理一组也可以与版本控制一起使用的 YAML 清单(文本文件)来重新设计/管理/测试不同的版本!