rs-bff
rs-bff 是一个基于 Rust 的 BFF(Backend for Frontend)应用网关,对外暴露 HTTP API,对内通过 gRPC 调用后端微服务,并完成 JSON 与 Protobuf 之间的协议转换。
一、项目架构
项目采用分层架构组织代码:
text
src/
├── main.rs # 入口:初始化配置、日志、路由、metrics、优雅退出
├── rust_grpc/ # build.rs 自动生成的本地 proto 代码
│ ├── mod.rs
│ ├── user.rs
│ └── order.rs
├── infra/ # 基础设施层
│ ├── mod.rs
│ ├── config/ # 配置系统 + AppState
│ │ ├── mod.rs
│ │ ├── app.rs # AppState 定义
│ │ └── config.rs # Config 解析
│ └── errors/ # 统一错误类型
│ ├── mod.rs
│ └── error.rs
├── interfaces/ # 接口层(HTTP handler + 路由)
│ ├── mod.rs
│ ├── handler/ # HTTP handler 实现
│ │ ├── mod.rs
│ │ └── greeter.rs
│ └── router/ # axum 路由定义
│ ├── mod.rs
│ └── api.rs
└── providers/ # 提供者层(gRPC 客户端等外部服务连接)
├── mod.rs
├── provider.rs # AppState 组装
└── grpc/
├── mod.rs
├── client.rs # GrpcClientManager 实现
└── readme.md
核心分层职责
- infra:配置解析、错误定义、
AppState等基础能力,被上层依赖。 - providers:管理外部连接(gRPC client、数据库等),负责组装
AppState。 - interfaces:处理 HTTP 请求,包含路由注册和 handler 实现,依赖
AppState调用底层服务。
二、技术栈
| 用途 | 依赖 |
|---|---|
| HTTP Web 框架 | axum 0.8.9 |
| gRPC 客户端/运行时 | tonic 0.14.6 + tonic-prost 0.14.6 |
| Protobuf 序列化 | prost 0.14.3 |
| 异步运行时 | tokio 1.52.3 |
| 配置/JSON | serde + serde_json + serde_yaml |
| 日志 | log + env_logger + chrono |
| 错误处理 | thiserror 2 |
| 可观测性/Metrics | autometrics 3.0.0 + monitor |
| 优雅退出 | shutdown |
| 外部 PB 协议托管 | hello-pb |
三、配置系统
通过 app.yaml 驱动:
yaml
app_debug: true # 调试模式(线上设为 false)
app_port: 8080 # HTTP 服务端口
monitor_port: 8091 # Prometheus metrics 端口
log_level: info # 日志级别
graceful_wait_time: 5 # 优雅退出等待时间(秒)
services:
- name: greeter-svc
target: http://127.0.0.1:50051
- name: user
target: http://127.0.0.1:50052
配置读取禁止默认值:找不到服务时直接报错退出,确保配置缺失能被及时发现。
四、Protobuf 协议
本地 proto(build.rs 生成)
build.rs 自动扫描 proto/ 目录,通过 tonic-prost-build 生成代码到 src/rust_grpc/,并自动创建 mod.rs:
- 编译前清理旧文件,删除协议后不会残留旧模块
- 自动按文件名排序并生成
pub mod xxx;
当前本地 proto:
proto/user.protoproto/order.proto
外部 PB 协议托管(推荐)
对于多服务共享的协议,推荐通过独立仓库 + git 依赖托管,避免各服务重复生成和版本不一致。
本项目已接入 hello-pb 作为外部协议依赖:
toml
hello-pb = { git = "https://github.com/daheige/hello-pb", tag = "v1.1.2" }
五、gRPC 客户端
GrpcClientManager 采用懒加载模式:初始化时不建立连接,首次使用时通过 tokio::sync::OnceCell 自动创建并缓存。
rust
pub struct GrpcClientManager {
target: TargetServices,
greeter_client: OnceCell<GreeterClient<Channel>>,
}
连接参数配置:
| 参数 | 值 | 说明 |
|---|---|---|
http2_keep_alive_interval |
30s | HTTP/2 心跳间隔 |
keep_alive_timeout |
20s | 心跳超时(内网建议 10s,公网建议 20s) |
keep_alive_while_idle |
true | 保持空闲连接 |
timeout |
30s | 单次 RPC 超时 |
connect_timeout |
10s | 连接建立超时 |
设计与权衡
-
Channel 而非直连
- tonic 的
Endpoint::connect()返回Channel,其内部基于tower_buffer::Buffer在后台任务中维护连接,天然支持 HTTP/2 多路复用。 Channel的Clone成本极低(内部为Arc),因此 handler 中每次取到的GreeterClient都是独立克隆实例,线程安全且无锁竞争。- 相比
connect直连,Channel能精确配置 TLS、超时、并发限制、拦截器、负载均衡策略,扩展性更好。
- tonic 的
-
懒加载(
OnceCell)而非启动时全量连接- 使用
tokio::sync::OnceCell保证并发安全且仅初始化一次。 - 收益:降低启动耗时;避免后端服务未就绪导致 BFF 启动失败;减少长期闲置的无用连接。
- 代价:首次请求需承担一次 TCP/TLS 握手 RTT(冷启动);连接问题会推迟到首次调用时才暴露。
- 使用
-
连接保活与超时策略
keep_alive_while_idle(true)配合 30s 心跳 + 20s 超时,维持长连接以减少重复握手开销。timeout(30s)限制单次 RPC,防止后端雪崩级联阻塞;connect_timeout(10s)避免 hung 连接拖死资源。- 权衡:超时阈值需与后端实际 P99 对齐,过短会误杀慢请求,过长则失去熔断意义。
-
错误分层映射
- URI 无效 →
AppError::Internal(配置错误,返回 HTTP 500)。 - 网络/连接失败 →
AppError::GrpcTransport(上游不可用,返回 HTTP 502)。 - 与
axum::response::IntoResponse联动,自动转换为对应 HTTP 状态码,无需 handler 额外处理。
- URI 无效 →
-
扩展预留
TargetServices和GrpcClientManager采用显式字段扩展:新增微服务时,只需在TargetServices中增加地址字段、在GrpcClientManager中增加对应的OnceCell字段即可,无需重构整体结构。
六、错误处理
统一错误类型 AppError:
rust
pub enum AppError {
Grpc(tonic::Status),
GrpcTransport(tonic::transport::Error),
Serialization(serde_json::Error),
Internal(String),
}
已实现 axum::response::IntoResponse,自动映射 HTTP 状态码:
| gRPC Code | HTTP Status |
|---|---|
| NotFound | 404 |
| InvalidArgument | 400 |
| Unauthenticated | 401 |
| PermissionDenied | 403 |
| AlreadyExists | 409 |
| Unavailable | 503 |
| DeadlineExceeded | 504 |
| 其他 | 500 |
七、HTTP API
当前暴露的路由:
text
GET / -> hello,bff
GET /api/v1/greeter/say/{name} -> 调用 greeter gRPC 服务
未匹配路由会返回统一的 JSON 错误:
json
{
"code": 404,
"message": "api not found",
"data": {}
}
八、可观测性
基于 autometrics + monitor 自动暴露 Prometheus 指标:
- 访问地址:
http://localhost:8091/metrics - 每个 HTTP handler 通过
#[autometrics(objective = API_SLO)]自动采集请求延迟、成功率等指标
效果预览:

九、运行方式
bash
# 开发运行
cargo run
# 确保 app.yaml 中配置的 gRPC 后端服务已启动
服务默认监听 0.0.0.0:8080,metrics 监听 8091。
十、关键演进记录
- 架构分层:从扁平结构演进为
infra / providers / interfaces分层,职责更清晰。 - PB 协议托管:从本地
build.rs生成所有协议,演进为本地 proto + 外部hello-pbgit 依赖混合模式。 - 可观测性:引入
autometrics自动埋点 + Prometheus exporter,替代手动指标采集。 - 优雅退出:引入
shutdown组件,支持SIGTERM/SIGINT信号平滑关闭 HTTP 和 metrics 服务。 - 日志系统:从
tracing演进为log + env_logger + chrono,当前场景更轻量(trace.md保留了 tracing 用法参考)。 - 配置系统:从硬编码环境变量演进为 YAML 配置驱动,禁止默认值,缺失即报错。
- AppState 下沉:从
providers层上提到infra/config/app.rs,避免循环依赖,分层更纯粹。 - gRPC 懒加载:从启动时全量连接演进为
OnceCell按需初始化,降低启动耗时与无效连接。
