Ray Serve:基于 Ray 的可扩展模型服务框架
Ray Serve 部署完全指南:从零搭建可扩展模型服务
目录
1. Ray Serve 是什么?
Ray Serve 是基于分布式计算框架 Ray 构建的模型服务子系统。它能够将任意 Python 函数或类转化为可弹性伸缩的在线推理 API,从根本上解决了模型服务中常见的三个痛点:
- 高可用性:每个副本独立运行,单个副本失效不会影响整体服务。
- 弹性扩展:可手动或根据请求负载自动增加/减少副本数量。
- 异构部署:同一个 Serve 应用内可以同时部署多个不同模型,彼此独立扩缩容。
与传统模型服务框架(如 Flask + Gunicorn)相比,Ray Serve 原生具备分布式调度能力,无需额外集成负载均衡器或服务发现组件;同时它深度嵌入 Ray 生态,能直接复用 Ray 的 Actor 模型与内存存储,非常适合需要低延迟、高吞吐的在线推理场景。
2. 环境准备与安装
2.1 基础依赖
- Python 3.8 及以上
- pip 或 conda 包管理器
- Linux/macOS 系统(Windows 支持受限,建议使用 WSL2)
2.2 安装 Ray 与 Serve
# 安装包含 Serve 的 Ray 完整包
pip install "ray[serve]"
# 验证安装
python -c "import ray; from ray import serve; print(ray.__version__)"
提示:若仅需最小化安装,可使用
pip install ray后再安装ray[serve],但推荐直接安装完整版以避免依赖缺失。
2.3 启动 Ray 集群(可选)
单机开发可直接使用 Ray 的隐式启动,无需显式配置:
import ray
ray.init() # 自动检测本地资源
如需多节点集群,请先在所有节点安装相同版本的 Ray,然后在主节点执行 ray start --head,工作节点执行 ray start --address='主节点IP:6379'。
3. 快速入门:第一个 Serve 应用
我们从一个极简文本翻译模型开始,展示完整的部署流程。
3.1 定义服务
# my_serve.py
from ray import serve
from starlette.requests import Request
@serve.deployment(num_replicas=2, ray_actor_options={"num_cpus": 0.1})
class Translator:
def __init__(self):
self.translate_dict = {"hello": "你好", "world": "世界"}
async def __call__(self, request: Request):
payload = await request.json()
text = payload.get("text", "")
results = [self.translate_dict.get(w, w) for w in text.split()]
return " ".join(results)
translator_app = Translator.bind()
3.2 启动 Serve 并部署
import ray
from ray import serve
ray.init()
serve.start(detached=True)
serve.run(translator_app, name="translator", route_prefix="/translate")
3.3 测试服务
import requests
resp = requests.post("http://127.0.0.1:8000/translate", json={"text": "hello world"})
print(resp.text) # 输出: 你好 世界
至此,一个具备双副本的高可用推理服务已经运行。所有请求会自动分发到两个副本上,且每个副本仅使用 0.1 个 CPU 资源。
4. 核心概念详解
4.1 Deployment(部署单元)
一个 Deployment 是对某个 Python 类或函数的声明式封装。通过 @serve.deployment 装饰器,可以指定副本数、资源需求、自动伸缩策略等。
关键参数:
num_replicas:静态副本数。ray_actor_options:每个副本在 Ray 集群中的资源申请(CPU/GPU/内存)。max_concurrent_queries:每个副本可同时处理的最大请求数(默认 100)。
4.2 Application(应用)
一个 Application 由一个或多个 Deployment 通过 .bind() 组合而成。它是 serve.run() 接收的基本部署单元。
@serve.deployment
class Preprocessor:
pass
@serve.deployment
class Model:
pass
app = Model.bind(Preprocessor.bind())
这种链式绑定会自动构建处理流水线,前一个 Deployment 的输出成为后一个的输入。
4.3 Ingress(入口)
直接暴露为 HTTP 的 Deployment 称为 Ingress。它可以通过 __call__ 方法接收 Starlette 的 Request 对象,并返回 JSON 兼容的响应。
4.4 Backend(已废弃)
在旧版本中 Serve 有 Backend 与 Endpoint 分离的概念,目前统一为 Deployment,请直接使用 Deployment 设计服务。
5. 部署模式:单节点与集群
5.1 单节点开发模式
使用 ray.init() 启动本地 Ray 运行环境,所有副本运行在单机上。适合开发与测试。
5.2 生产集群部署
- 启动 Ray 集群(前文已述)
- 使用
serve.start(detached=True)启动 Serve 控制器,它会将配置持久化到 Ray 的全局状态中。 - 提交应用
serve.run(app) - 更新应用:重新运行脚本或调用
serve.run(new_app)即可实现零停机滚动更新。
示例:在集群中提交任务
# 主节点执行
python deploy_script.py
脚本内容:
ray.init(address="auto")
serve.start(detached=True)
# 假设 app 已定义
serve.run(app)
5.3 使用 Serve CLI(实验性)
Ray 2.8+ 提供了命令行工具:
serve deploy config.yaml # 从 YAML 文件部署
serve status # 查看服务状态
serve shutdown # 关闭所有 Serve 服务
6. 高级配置:自动伸缩与请求批处理
6.1 自动伸缩
通过在 @serve.deployment 中设置 autoscaling_config,可以根据实时 QPS 或延迟动态调整副本数。
@serve.deployment(
autoscaling_config={
"min_replicas": 1,
"max_replicas": 8,
"target_num_ongoing_requests_per_replica": 10,
"upscale_delay_s": 30,
"downscale_delay_s": 300,
}
)
class MyModel:
...
target_num_ongoing_requests_per_replica:每个副本的理想并发请求数,超出即扩容。upscale_delay_s:扩容冷却时间。downscale_delay_s:缩容冷却时间。
6.2 请求批处理
Serve 支持将多个请求合并为一个批次以提高 GPU 利用率。
from ray.serve.batching import batch
@serve.deployment
class BatchModel:
@batch(max_batch_size=16, batch_wait_timeout_s=0.2)
async def handle_batch(self, inputs: list):
results = self.model.predict(np.array(inputs))
return results
async def __call__(self, request):
data = await request.json()
return await self.handle_batch(data["input"])
当请求在 batch_wait_timeout_s 内聚集足够数量时,它们会被合并调用 handle_batch,大幅提升计算密度。
6.3 多模型组合与路由
Serve 支持根据请求路径或参数动态路由到不同 Deployment。
@serve.deployment
class ModelA: ...
@serve.deployment
class ModelB: ...
@serve.deployment
class Router:
def __init__(self, model_a, model_b):
self.model_a = model_a
self.model_b = model_b
async def __call__(self, request):
data = await request.json()
if data["type"] == "A":
return await self.model_a.remote(data)
else:
return await self.model_b.remote(data)
app = Router.bind(ModelA.bind(), ModelB.bind())
7. 生产化部署最佳实践
7.1 资源配置细粒度控制
- 为每个 Deployment 指定真实的资源需求,避免过度分配。例如 NLP 模型通常需要 GPU,使用
ray_actor_options={"num_gpus": 1}可确保每个副本独占一张 GPU。 - 使用
num_cpus的小数(如 0.1)来允许多个轻量模型共享 CPU 核心。
7.2 健康检查与优雅退出
- Serve 内置副本健康检查,一旦副本挂掉会自动重启。
- 如需优雅关闭,可发送
SIGTERM至 Raylet,Serve 会等待该副本的进行中请求完成再退出。
7.3 监控与日志
- 通过 Ray Dashboard(默认
http://127.0.0.1:8265)查看 Serve 状态、副本数量、请求延迟等。 - 日志默认输出到
/tmp/ray/session_latest/logs/,可在部署信息中查看每个副本的日志。 - 使用
serve.status()API 获取当前所有 Deployment 的状态。 - 集成 Prometheus 监控:设置
serve.start(proxy_http_options={"metrics_port": 8001}),指标端点暴露在:8001。
7.4 安全与访问控制
- 生产环境建议在前面放置反向代理(如 Nginx,Caddy)处理 HTTPS 和请求限流。
- Serve 默认不进行鉴权,需要在路由内实现或使用 API 网关。
7.5 模型加载优化
- 将模型初始化放在
__init__方法中,每个副本只加载一次,避免每次请求重新加载。 - 对于大型模型,可使用 Ray 的对象引用将自己加载的模型存入 Ray Plasma,多个副本共享只读内存。
7.6 版本管理与滚动更新
- 重新运行
serve.run即触发滚动更新,新副本逐步替换旧副本,期间不会中断服务。 - 可通过
serve.run(app, name="v2")部署多个版本,再通过路由逐步切换流量。
8. 常见问题与排障
Q:启动 Serve 后访问 8000 端口无响应?
A:检查是否调用了 serve.run(),以及是否有 Ingress Deployment 绑定到该路由。可使用 serve.list_deployments() 确认。
Q:副本一直处于 RECOVERING 状态? A:通常是资源不足导致,检查集群是否有可用 CPU/GPU/内存,或降低资源需求。
Q:如何调试 Deployment 中的错误?
A:查看对应副本的日志文件,或使用 ray.get(deployment.get_logs.remote()) 获取特定副本日志。
Q:GPU 推理时性能很差?
A:确认 ray_actor_options={"num_gpus": 1} 正确设置,且模型在 __init__ 中加载,必要时启用批处理。
Q:是否支持 gRPC 或 WebSocket? A:Serve HTTP 入口基于 Starlette,可以封装 ASGI 应用支持 WebSocket;gRPC 目前需通过自定义 Ray Actor 实现,未原生集成。
通过本教程,你已掌握从安装到生产级部署的完整 Ray Serve 知识。建议在本地尝试组合多个模型并启用自动伸缩,亲身体验其分布式服务编排的灵活性。更多高级特性可查阅 Ray Serve 官方文档。