添加端到端容错#

本节将帮助您

  • 为您的 Serve 应用程序提供额外的容错能力

  • 了解 Serve 的恢复程序

  • 模拟 Serve 应用程序中的系统错误

相关指南

本节讨论来自

指南:为您的 Serve 应用添加端到端容错#

Serve 开箱即用提供了一些 容错 功能。以下是实现端到端容错的两种选择:

副本健康检查#

默认情况下,Serve 控制器会定期对每个 Serve 部署副本进行健康检查,并在失败时重新启动它。

您可以定义自定义的应用程序级别健康检查,并调整其频率和超时。要定义自定义健康检查,请将 check_health 方法添加到您的部署类。此方法不应接受任何参数,也不应返回任何结果,如果 Ray Serve 认为副本不健康,则应引发异常。如果健康检查失败,Serve 控制器会记录异常,终止不健康的副本,然后重新启动它们。您还可以使用部署选项来自定义 Serve 运行健康检查的频率以及 Serve 将副本标记为不健康的超时时间。

@serve.deployment(health_check_period_s=10, health_check_timeout_s=30)
class MyDeployment:
    def __init__(self, db_addr: str):
        self._my_db_connection = connect_to_db(db_addr)

    def __call__(self, request):
        return self._do_something_cool()

    # Called by Serve to check the replica's health.
    def check_health(self):
        if not self._my_db_connection.is_connected():
            # The specific type of exception is not important.
            raise RuntimeError("uh-oh, DB connection is broken.")

在此示例中,如果与外部数据库的连接丢失,check_health 将引发错误。Serve 控制器会定期在部署的每个副本上调用此方法。如果该方法为某个副本引发异常,Serve 会将该副本标记为不健康并重新启动它。健康检查是针对每个副本进行配置和执行的。

注意

您不应通过部署句柄直接调用 check_health(例如,await deployment_handle.check_health.remote())。这将调用单个、任意副本的健康检查。check_health 方法设计为 Serve 控制器的接口,而不是供用户直接调用的。

注意

在可组合的部署图中,每个部署都独立负责自身的健康,而不管它绑定的其他部署。例如,在由 app = ParentDeployment.bind(ChildDeployment.bind()) 定义的应用程序中,如果 ChildDeployment 副本的健康检查失败,ParentDeployment 不会重新启动。当 ChildDeployment 副本恢复时,ParentDeployment 中的句柄会自动更新以将请求路由到健康的副本。

工作节点恢复#

默认情况下,Serve 可以从某些故障中恢复,例如不健康的 actor。当 Serve 在 Kubernetes 上运行 并使用 KubeRay 时,它还可以从一些集群级故障中恢复,例如损坏的工作节点或头节点。

当工作节点发生故障时,运行在该节点上的 actor 也会失败。Serve 会检测到 actor 失败,并尝试在剩余的健康节点上重新启动这些 actor。同时,KubeRay 会检测到节点本身发生故障,因此它会尝试在另一个正在运行的节点上重新启动工作节点 pod,并会启动一个新的健康节点来替换它。一旦节点启动,如果 pod 仍在挂起状态,它就可以在该节点上重新启动。同样,Serve 也可以在该节点上重新启动任何挂起的 actor。在恢复期间,运行在健康节点上的部署副本可以继续提供流量。

头节点恢复:Ray GCS 容错#

在本节中,您将学习如何为 Ray 的全局控制存储 (GCS) 添加容错能力,从而使您的 Serve 应用程序即使在头节点崩溃时也能提供流量。

默认情况下,Ray 头节点是单点故障:如果它崩溃,整个 Ray 集群都会崩溃,您必须重新启动它。在 Kubernetes 上运行时,RayService 控制器会检查 Ray 集群的健康状况并在发生这种情况时重新启动它,但这会引入一些停机时间。

从 Ray 2.0+ 开始,KubeRay 支持 全局控制存储 (GCS) 容错,防止 Ray 集群在头节点下线时崩溃。在头节点恢复期间,Serve 应用程序仍然可以由工作节点处理流量,但您无法更新或从 Actor 或工作节点崩溃等其他故障中恢复。一旦 GCS 恢复,集群将恢复正常运行。

您可以通过添加外部 Redis 服务器并修改您的 RayService Kubernetes 对象来启用 KubeRay 上的 GCS 容错,步骤如下:

步骤 1:添加外部 Redis 服务器#

GCS 容错需要外部 Redis 数据库。您可以选择托管自己的 Redis 数据库,也可以通过第三方供应商使用。请使用高可用性的 Redis 数据库以实现弹性。

**用于开发目的**,您也可以在与 Ray 集群相同的 Kubernetes 集群上托管一个小型 Redis 数据库。例如,您可以通过在 Kubernetes YAML 前面添加这三个 Redis 对象来添加一个 1 节点 Redis 集群:

kind: ConfigMap
apiVersion: v1
metadata:
  name: redis-config
  labels:
    app: redis
data:
  redis.conf: |-
    port 6379
    bind 0.0.0.0
    protected-mode no
    requirepass 5241590000000000
---
apiVersion: v1
kind: Service
metadata:
  name: redis
  labels:
    app: redis
spec:
  type: ClusterIP
  ports:
    - name: redis
      port: 6379
  selector:
    app: redis
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis
  labels:
    app: redis
spec:
  replicas: 1
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
        - name: redis
          image: redis:5.0.8
          command:
            - "sh"
            - "-c"
            - "redis-server /usr/local/etc/redis/redis.conf"
          ports:
            - containerPort: 6379
          volumeMounts:
            - name: config
              mountPath: /usr/local/etc/redis/redis.conf
              subPath: redis.conf
      volumes:
        - name: config
          configMap:
            name: redis-config
---

**此配置不适用于生产环境**,但对于开发和测试很有用。当您迁移到生产环境时,强烈建议您用高可用的 Redis 集群替换这个 1 节点 Redis 集群。

步骤 2:将 Redis 信息添加到 RayService#

添加 Redis 对象后,您还需要修改 RayService 配置。

首先,您需要更新 RayService 元数据的注解:

...
apiVersion: ray.io/v1alpha1
kind: RayService
metadata:
  name: rayservice-sample
spec:
...
...
apiVersion: ray.io/v1alpha1
kind: RayService
metadata:
  name: rayservice-sample
  annotations:
    ray.io/ft-enabled: "true"
    ray.io/external-storage-namespace: "my-raycluster-storage-namespace"
spec:
...

这些注解是:

  • ray.io/ft-enabled 必需:如果为 true,则启用 GCS 容错。

  • ray.io/external-storage-namespace 可选:设置 外部存储命名空间

接下来,您需要将 RAY_REDIS_ADDRESS 环境变量添加到 headGroupSpec

apiVersion: ray.io/v1alpha1
kind: RayService
metadata:
    ...
spec:
    ...
    rayClusterConfig:
        headGroupSpec:
            ...
            template:
                ...
                spec:
                    ...
                    env:
                        ...
apiVersion: ray.io/v1alpha1
kind: RayService
metadata:
    ...
spec:
    ...
    rayClusterConfig:
        headGroupSpec:
            ...
            template:
                ...
                spec:
                    ...
                    env:
                        ...
                        - name: RAY_REDIS_ADDRESS
                          value: redis:6379

RAY_REDIS_ADDRESS 的值应该是您的 Redis 数据库的 redis:// 地址。它应包含您的 Redis 数据库的主机和端口。一个 示例 Redis 地址redis://user:secret@localhost:6379/0?foo=bar&qux=baz

在上面的示例中,Redis 部署名称(redis)是 Kubernetes 集群内的宿主,Redis 端口是 6379。此示例与上一节的 示例配置 兼容。

在应用了 Redis 对象以及更新后的 RayService 后,您的 Ray 集群就可以从头节点崩溃中恢复,而无需重新启动所有工作节点!

另请参阅

请查看 KubeRay 关于 GCS 容错 的指南,以了解 Serve 如何利用外部 Redis 集群来提供头节点容错。

将副本分散到不同节点#

提高 Serve 应用程序可用性的一种方法是将部署副本分散到多个节点上,这样即使在一定数量的节点发生故障后,您仍然有足够的运行副本可用于提供流量。

默认情况下,Serve 会软分散所有部署副本,但它有一些限制:

  • 分散是软性的,尽力而为,不能保证完全均匀。

  • Serve 尝试在现有节点之间分散副本(如果可能),而不是启动新节点。例如,如果您有一个足够大的单节点集群,Serve 会将所有副本调度到该单个节点上,假设它有足够的资源。然而,该节点成为单点故障。

您可以通过 max_replicas_per_node 部署选项 来更改部署的分散行为,它会硬性限制单个节点上给定部署的副本数量。如果您将其设置为 1,则相当于严格分散部署副本。如果未设置,则没有硬性分散限制,Serve 会使用前一段中提到的默认软分散。max_replicas_per_node 选项是每个部署的,并且只影响部署内副本的分散。不同部署的副本之间没有分散。

以下代码示例展示了如何设置 max_replicas_per_node 部署选项:

import ray
from ray import serve

@serve.deployment(max_replicas_per_node=1)
class Deployment1:
  def __call__(self, request):
    return "hello"

@serve.deployment(max_replicas_per_node=2)
class Deployment2:
  def __call__(self, request):
    return "world"

此示例有两个 Serve 部署,具有不同的 max_replicas_per_nodeDeployment1 每个节点最多可以有一个副本,Deployment2 每个节点最多可以有两个副本。如果您调度 Deployment1 的两个副本和 Deployment2 的两个副本,Serve 会运行一个至少有两个节点的集群,每个节点运行 Deployment1 的一个副本。Deployment2 的两个副本可能会运行在单个节点上,或者分布在两个节点上,因为这两种情况都满足 max_replicas_per_node 约束。

Serve 的恢复程序#

本节解释 Serve 如何从系统故障中恢复。它使用以下 Serve 应用程序和配置作为工作示例。

# File name: sleepy_pid.py

from ray import serve


@serve.deployment
class SleepyPid:
    def __init__(self):
        import time

        time.sleep(10)

    def __call__(self) -> int:
        import os

        return os.getpid()


app = SleepyPid.bind()
# File name: config.yaml

kind: ConfigMap
apiVersion: v1
metadata:
  name: redis-config
  labels:
    app: redis
data:
  redis.conf: |-
    port 6379
    bind 0.0.0.0
    protected-mode no
    requirepass 5241590000000000
---
apiVersion: v1
kind: Service
metadata:
  name: redis
  labels:
    app: redis
spec:
  type: ClusterIP
  ports:
    - name: redis
      port: 6379
  selector:
    app: redis
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis
  labels:
    app: redis
spec:
  replicas: 1
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
        - name: redis
          image: redis:5.0.8
          command:
            - "sh"
            - "-c"
            - "redis-server /usr/local/etc/redis/redis.conf"
          ports:
            - containerPort: 6379
          volumeMounts:
            - name: config
              mountPath: /usr/local/etc/redis/redis.conf
              subPath: redis.conf
      volumes:
        - name: config
          configMap:
            name: redis-config
---
apiVersion: ray.io/v1alpha1
kind: RayService
metadata:
  name: rayservice-sample
  annotations:
    ray.io/ft-enabled: "true"
spec:
  serviceUnhealthySecondThreshold: 300
  deploymentUnhealthySecondThreshold: 300
  serveConfig:
    importPath: "sleepy_pid:app"
    runtimeEnv: |
      working_dir: "https://github.com/ray-project/serve_config_examples/archive/42d10bab77741b40d11304ad66d39a4ec2345247.zip"
    deployments:
      - name: SleepyPid
        numReplicas: 6
        rayActorOptions:
          numCpus: 0
  rayClusterConfig:
    rayVersion: '2.3.0'
    headGroupSpec:
      replicas: 1
      rayStartParams:
        num-cpus: '2'
        dashboard-host: '0.0.0.0'
        redis-password: "5241590000000000"
      template:
        spec:
          containers:
            - name: ray-head
              image: rayproject/ray:2.3.0
              imagePullPolicy: Always
              env:
                - name: MY_POD_IP
                  valueFrom:
                    fieldRef:
                      fieldPath: status.podIP
                - name: RAY_REDIS_ADDRESS
                  value: redis:6379
              resources:
                limits:
                  cpu: 2
                  memory: 2Gi
                requests:
                  cpu: 2
                  memory: 2Gi
              ports:
                - containerPort: 6379
                  name: redis
                - containerPort: 8265
                  name: dashboard
                - containerPort: 10001
                  name: client
                - containerPort: 8000
                  name: serve
    workerGroupSpecs:
      - replicas: 2
        minReplicas: 2
        maxReplicas: 2
        groupName: small-group
        rayStartParams:
          node-ip-address: $MY_POD_IP
        template:
          spec:
            containers:
              - name: machine-learning
                image: rayproject/ray:2.3.0
                imagePullPolicy: Always
                env:
                  - name:  RAY_DISABLE_DOCKER_CPU_WARNING
                    value: "1"
                  - name: TYPE
                    value: "worker"
                  - name: CPU_REQUEST
                    valueFrom:
                      resourceFieldRef:
                        containerName: machine-learning
                        resource: requests.cpu
                  - name: CPU_LIMITS
                    valueFrom:
                      resourceFieldRef:
                        containerName: machine-learning
                        resource: limits.cpu
                  - name: MEMORY_LIMITS
                    valueFrom:
                      resourceFieldRef:
                        containerName: machine-learning
                        resource: limits.memory
                  - name: MEMORY_REQUESTS
                    valueFrom:
                      resourceFieldRef:
                        containerName: machine-learning
                        resource: requests.memory
                  - name: MY_POD_NAME
                    valueFrom:
                      fieldRef:
                        fieldPath: metadata.name
                  - name: MY_POD_IP
                    valueFrom:
                      fieldRef:
                        fieldPath: status.podIP
                ports:
                  - containerPort: 80
                    name: client
                lifecycle:
                  preStop:
                    exec:
                      command: ["/bin/sh","-c","ray stop"]
                resources:
                  limits:
                    cpu: "1"
                    memory: "2Gi"
                  requests:
                    cpu: "500m"
                    memory: "2Gi"

遵循 KubeRay 快速入门指南 来:

  • 安装 kubectlHelm

  • 准备一个 Kubernetes 集群

  • 部署 KubeRay 操作员

然后,部署上述 Serve 应用程序

$ kubectl apply -f config.yaml

工作节点故障#

您可以在工作示例中模拟工作节点故障。首先,查看 Kubernetes 集群中正在运行的节点和 pod:

$ kubectl get nodes

NAME                                        STATUS   ROLES    AGE     VERSION
gke-serve-demo-default-pool-ed597cce-nvm2   Ready    <none>   3d22h   v1.22.12-gke.1200
gke-serve-demo-default-pool-ed597cce-m888   Ready    <none>   3d22h   v1.22.12-gke.1200
gke-serve-demo-default-pool-ed597cce-pu2q   Ready    <none>   3d22h   v1.22.12-gke.1200

$ kubectl get pods -o wide

NAME                                                      READY   STATUS    RESTARTS        AGE    IP           NODE                                        NOMINATED NODE   READINESS GATES
ervice-sample-raycluster-thwmr-worker-small-group-bdv6q   1/1     Running   0               3m3s   10.68.2.62   gke-serve-demo-default-pool-ed597cce-nvm2   <none>           <none>
ervice-sample-raycluster-thwmr-worker-small-group-pztzk   1/1     Running   0               3m3s   10.68.2.61   gke-serve-demo-default-pool-ed597cce-m888   <none>           <none>
rayservice-sample-raycluster-thwmr-head-28mdh             1/1     Running   1 (2m55s ago)   3m3s   10.68.0.45   gke-serve-demo-default-pool-ed597cce-pu2q   <none>           <none>
redis-75c8b8b65d-4qgfz                                    1/1     Running   0               3m3s   10.68.2.60   gke-serve-demo-default-pool-ed597cce-nvm2   <none>           <none>

打开一个单独的终端窗口并端口转发到其中一个工作节点:

$ kubectl port-forward ervice-sample-raycluster-thwmr-worker-small-group-bdv6q 8000
Forwarding from 127.0.0.1:8000 -> 8000
Forwarding from [::1]:8000 -> 8000

port-forward 运行时,您可以在另一个终端窗口中查询应用程序:

$ curl localhost:8000
418

输出是处理请求的部署副本的进程 ID。该应用程序启动了 6 个部署副本,因此如果您多次运行查询,应该会看到不同的进程 ID。

$ curl localhost:8000
418
$ curl localhost:8000
256
$ curl localhost:8000
385

现在您可以模拟工作节点故障。您有两个选择:杀死一个工作节点 pod 或杀死一个工作节点。让我们先从工作节点 pod 开始。请确保杀死您**没有**进行端口转发的 pod,这样当另一个 pod 重新启动时,您就可以继续查询正在运行的 pod。

$ kubectl delete pod ervice-sample-raycluster-thwmr-worker-small-group-pztzk
pod "ervice-sample-raycluster-thwmr-worker-small-group-pztzk" deleted

$ curl localhost:8000
6318

当 pod 崩溃并恢复时,正在运行的 pod 可以继续提供流量!

提示

杀死一个节点并等待其恢复通常比杀死一个 pod 并等待其恢复需要更长的时间。对于这种类型的调试,通过杀死 pod 而不是杀死节点来模拟故障更快捷。

您可以类似地杀死一个工作节点,并看到其他节点可以继续提供流量。

$ kubectl get pods -o wide

NAME                                                      READY   STATUS    RESTARTS      AGE     IP           NODE                                        NOMINATED NODE   READINESS GATES
ervice-sample-raycluster-thwmr-worker-small-group-bdv6q   1/1     Running   0             65m     10.68.2.62   gke-serve-demo-default-pool-ed597cce-nvm2   <none>           <none>
ervice-sample-raycluster-thwmr-worker-small-group-mznwq   1/1     Running   0             5m46s   10.68.1.3    gke-serve-demo-default-pool-ed597cce-m888   <none>           <none>
rayservice-sample-raycluster-thwmr-head-28mdh             1/1     Running   1 (65m ago)   65m     10.68.0.45   gke-serve-demo-default-pool-ed597cce-pu2q   <none>           <none>
redis-75c8b8b65d-4qgfz                                    1/1     Running   0             65m     10.68.2.60   gke-serve-demo-default-pool-ed597cce-nvm2   <none>           <none>

$ kubectl delete node gke-serve-demo-default-pool-ed597cce-m888
node "gke-serve-demo-default-pool-ed597cce-m888" deleted

$ curl localhost:8000
385

头节点故障#

您可以通过杀死头节点 pod 或头节点来模拟头节点故障。首先,查看集群中正在运行的 pod:

$ kubectl get pods -o wide

NAME                                                      READY   STATUS    RESTARTS      AGE     IP           NODE                                        NOMINATED NODE   READINESS GATES
ervice-sample-raycluster-thwmr-worker-small-group-6f2pk   1/1     Running   0             6m59s   10.68.2.64   gke-serve-demo-default-pool-ed597cce-nvm2   <none>           <none>
ervice-sample-raycluster-thwmr-worker-small-group-bdv6q   1/1     Running   0             79m     10.68.2.62   gke-serve-demo-default-pool-ed597cce-nvm2   <none>           <none>
rayservice-sample-raycluster-thwmr-head-28mdh             1/1     Running   1 (79m ago)   79m     10.68.0.45   gke-serve-demo-default-pool-ed597cce-pu2q   <none>           <none>
redis-75c8b8b65d-4qgfz                                    1/1     Running   0             79m     10.68.2.60   gke-serve-demo-default-pool-ed597cce-nvm2   <none>           <none>

端口转发到其中一个工作节点 pod。确保此 pod 与头节点在不同的节点上,这样您就可以杀死头节点而不至于崩溃工作节点。

$ kubectl port-forward ervice-sample-raycluster-thwmr-worker-small-group-bdv6q
Forwarding from 127.0.0.1:8000 -> 8000
Forwarding from [::1]:8000 -> 8000

在单独的终端中,您可以向 Serve 应用程序发出请求:

$ curl localhost:8000
418

您可以杀死头节点 pod 来模拟杀死 Ray 头节点:

$ kubectl delete pod rayservice-sample-raycluster-thwmr-head-28mdh
pod "rayservice-sample-raycluster-thwmr-head-28mdh" deleted

$ curl localhost:8000

如果您已在集群中配置了 GCS 容错,则您的工作节点 pod 在头节点 pod 崩溃并恢复时可以继续提供流量,而无需重新启动。如果没有 GCS 容错,KubeRay 会在头节点 pod 崩溃时重新启动所有工作节点 pod,因此您需要等待工作节点重新启动并重新初始化部署,然后才能进行端口转发并发送更多请求。

Serve 控制器故障#

您可以通过手动杀死 Serve actor 来模拟 Serve 控制器故障。

如果您正在运行 KubeRay,请 exec 进入其中一个 pod:

$ kubectl get pods

NAME                                                      READY   STATUS    RESTARTS   AGE
ervice-sample-raycluster-mx5x6-worker-small-group-hfhnw   1/1     Running   0          118m
ervice-sample-raycluster-mx5x6-worker-small-group-nwcpb   1/1     Running   0          118m
rayservice-sample-raycluster-mx5x6-head-bqjhw             1/1     Running   0          118m
redis-75c8b8b65d-4qgfz                                    1/1     Running   0          3h36m

$ kubectl exec -it rayservice-sample-raycluster-mx5x6-head-bqjhw -- bash
ray@rayservice-sample-raycluster-mx5x6-head-bqjhw:~$

您可以使用 Ray State API 来检查您的 Serve 应用:

$ ray summary actors

======== Actors Summary: 2022-10-04 21:06:33.678706 ========
Stats:
------------------------------------
total_actors: 10


Table (group by class):
------------------------------------
    CLASS_NAME              STATE_COUNTS
0   ProxyActor          ALIVE: 3
1   ServeReplica:SleepyPid  ALIVE: 6
2   ServeController         ALIVE: 1

$ ray list actors --filter "class_name=ServeController"

======== List: 2022-10-04 21:09:14.915881 ========
Stats:
------------------------------
Total: 1

Table:
------------------------------
    ACTOR_ID                          CLASS_NAME       STATE    NAME                      PID
 0  70a718c973c2ce9471d318f701000000  ServeController  ALIVE    SERVE_CONTROLLER_ACTOR  48570

然后,您可以通过 Python 解释器杀死 Serve 控制器。请注意,您需要使用 ray list actor 输出中的 NAME 来获取 Serve 控制器的句柄。

$ python

>>> import ray
>>> controller_handle = ray.get_actor("SERVE_CONTROLLER_ACTOR", namespace="serve")
>>> ray.kill(controller_handle, no_restart=True)
>>> exit()

您可以使用 Ray State API 检查控制器的状态:

$ ray list actors --filter "class_name=ServeController"

======== List: 2022-10-04 21:36:37.157754 ========
Stats:
------------------------------
Total: 2

Table:
------------------------------
    ACTOR_ID                          CLASS_NAME       STATE    NAME                      PID
 0  3281133ee86534e3b707190b01000000  ServeController  ALIVE    SERVE_CONTROLLER_ACTOR  49914
 1  70a718c973c2ce9471d318f701000000  ServeController  DEAD     SERVE_CONTROLLER_ACTOR  48570

在控制器恢复期间,您仍然应该能够查询您的部署。

# If you're running KubeRay, you
# can do this from inside the pod:

$ python

>>> import requests
>>> requests.get("https://:8000").json()
347

注意

在控制器死亡期间,副本健康检查和部署自动伸缩将不起作用。一旦控制器恢复,它们将继续工作。

部署副本故障#

您可以通过手动杀死部署副本来自我模拟副本故障。如果您正在运行 KubeRay,请确保在运行这些命令之前 exec 进入一个 Ray pod。

$ ray summary actors

======== Actors Summary: 2022-10-04 21:40:36.454488 ========
Stats:
------------------------------------
total_actors: 11


Table (group by class):
------------------------------------
    CLASS_NAME              STATE_COUNTS
0   ProxyActor          ALIVE: 3
1   ServeController         ALIVE: 1
2   ServeReplica:SleepyPid  ALIVE: 6

$ ray list actors --filter "class_name=ServeReplica:SleepyPid"

======== List: 2022-10-04 21:41:32.151864 ========
Stats:
------------------------------
Total: 6

Table:
------------------------------
    ACTOR_ID                          CLASS_NAME              STATE    NAME                               PID
 0  39e08b172e66a5d22b2b4cf401000000  ServeReplica:SleepyPid  ALIVE    SERVE_REPLICA::SleepyPid#RlRptP    203
 1  55d59bcb791a1f9353cd34e301000000  ServeReplica:SleepyPid  ALIVE    SERVE_REPLICA::SleepyPid#BnoOtj    348
 2  8c34e675edf7b6695461d13501000000  ServeReplica:SleepyPid  ALIVE    SERVE_REPLICA::SleepyPid#SakmRM    283
 3  a95405318047c5528b7483e701000000  ServeReplica:SleepyPid  ALIVE    SERVE_REPLICA::SleepyPid#rUigUh    347
 4  c531188fede3ebfc868b73a001000000  ServeReplica:SleepyPid  ALIVE    SERVE_REPLICA::SleepyPid#gbpoFe    383
 5  de8dfa16839443f940fe725f01000000  ServeReplica:SleepyPid  ALIVE    SERVE_REPLICA::SleepyPid#PHvdJW    176

您可以使用 ray list actor 输出中的 NAME 来获取其中一个副本的句柄:

$ python

>>> import ray
>>> replica_handle = ray.get_actor("SERVE_REPLICA::SleepyPid#RlRptP", namespace="serve")
>>> ray.kill(replica_handle, no_restart=True)
>>> exit()

在副本重新启动期间,其他副本可以继续处理请求。最终,副本会重新启动并继续提供请求。

$ python

>>> import requests
>>> requests.get("https://:8000").json()
383

代理故障#

您可以通过手动杀死 ProxyActor actors 来模拟代理故障。如果您正在运行 KubeRay,请确保在运行这些命令之前 exec 进入一个 Ray pod。

$ ray summary actors

======== Actors Summary: 2022-10-04 21:51:55.903800 ========
Stats:
------------------------------------
total_actors: 12


Table (group by class):
------------------------------------
    CLASS_NAME              STATE_COUNTS
0   ProxyActor          ALIVE: 3
1   ServeController         ALIVE: 1
2   ServeReplica:SleepyPid  ALIVE: 6

$ ray list actors --filter "class_name=ProxyActor"

======== List: 2022-10-04 21:52:39.853758 ========
Stats:
------------------------------
Total: 3

Table:
------------------------------
    ACTOR_ID                          CLASS_NAME      STATE    NAME                                                                                                 PID
 0  283fc11beebb6149deb608eb01000000  ProxyActor  ALIVE    SERVE_CONTROLLER_ACTOR:SERVE_PROXY_ACTOR-91f9a685e662313a0075efcb7fd894249a5bdae7ee88837bea7985a0    101
 1  2b010ce28baeff5cb6cb161e01000000  ProxyActor  ALIVE    SERVE_CONTROLLER_ACTOR:SERVE_PROXY_ACTOR-cc262f3dba544a49ea617d5611789b5613f8fe8c86018ef23c0131eb    133
 2  7abce9dd241b089c1172e9ca01000000  ProxyActor  ALIVE    SERVE_CONTROLLER_ACTOR:SERVE_PROXY_ACTOR-7589773fc62e08c2679847aee9416805bbbf260bee25331fa3389c4f    267

您可以使用 ray list actor 输出中的 NAME 来获取其中一个副本的句柄:

$ python

>>> import ray
>>> proxy_handle = ray.get_actor("SERVE_CONTROLLER_ACTOR:SERVE_PROXY_ACTOR-91f9a685e662313a0075efcb7fd894249a5bdae7ee88837bea7985a0", namespace="serve")
>>> ray.kill(proxy_handle, no_restart=False)
>>> exit()

在代理重新启动期间,其他代理可以继续接受请求。最终,代理会重新启动并继续接受请求。您可以使用 ray list actor 命令查看代理何时重新启动。

$ ray list actors --filter "class_name=ProxyActor"

======== List: 2022-10-04 21:58:41.193966 ========
Stats:
------------------------------
Total: 3

Table:
------------------------------
    ACTOR_ID                          CLASS_NAME      STATE    NAME                                                                                                 PID
 0  283fc11beebb6149deb608eb01000000  ProxyActor  ALIVE     SERVE_CONTROLLER_ACTOR:SERVE_PROXY_ACTOR-91f9a685e662313a0075efcb7fd894249a5bdae7ee88837bea7985a0  57317
 1  2b010ce28baeff5cb6cb161e01000000  ProxyActor  ALIVE    SERVE_CONTROLLER_ACTOR:SERVE_PROXY_ACTOR-cc262f3dba544a49ea617d5611789b5613f8fe8c86018ef23c0131eb    133
 2  7abce9dd241b089c1172e9ca01000000  ProxyActor  ALIVE    SERVE_CONTROLLER_ACTOR:SERVE_PROXY_ACTOR-7589773fc62e08c2679847aee9416805bbbf260bee25331fa3389c4f    267

请注意,第一个 ProxyActor 的 PID 已更改,这表明它已重新启动。