添加端到端容错功能#

本节将帮助你

  • 为你的 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.")

工作节点恢复#

默认情况下,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
---

此配置不适用于生产环境,但它适用于开发和测试。迁移到生产环境时,强烈建议你将此 1 节点 Redis 集群替换为高可用性 Redis 集群。

步骤 2:向 RayService 添加 Redis 信息#

添加 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_node 不同:Deployment1 在每个节点上最多可有一个副本,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 operator

然后,部署上面的 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 重新启动时,你可以继续查询存活的工作节点。

$ 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("http://localhost: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("http://localhost:8000").json()
383

代理故障#

你可以通过手动杀死 ProxyActor Actor 来模拟代理故障。如果你正在运行 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 已经改变,表明它已重启。