# Resilience 手册
`rs-zero` 的 `resil` feature 提供本进程服务韧性组件:超时、并发保护、滑动窗口熔断、fallback/acceptable error API,以及轻量 adaptive shedding。
## 核心类型
- `BreakerConfig`:连续失败阈值和半开重试时间。
- `BreakerPolicyConfig`:滑动窗口、失败比例、概率丢弃和半开并发策略。
- `BreakerPolicyConfig::google_sre()`:更接近 go-zero / Google SRE client-side throttling 的自适应拒绝策略。
- `SharedCircuitBreaker`:可在异步服务中共享的熔断器。
- `BreakerRegistry`:按稳定 key 复用熔断器。
- `AdaptiveShedder`:基于 in-flight、近期吞吐、平均延迟和可替换 CPU provider 做本进程降载。
- `LinuxCpuUsageProvider`:Linux 下基于 `/proc/stat` 的 CPU provider;非 Linux 保持 `NoopCpuUsageProvider`。
- `RedisTokenLimiter`:`cache-redis` feature 下的 Redis token bucket limiter。
- `RedisPeriodLimiter`:`cache-redis` feature 下的 Redis fixed-window period limiter。
- `RestRateLimiterConfig` / `RpcRateLimiterConfig`:把 Redis limiter 接入默认 REST/RPC 韧性链路。
- `RpcResilienceLayer`:RPC unary 调用的 timeout、concurrency、breaker、shedder helper。
- `RpcServerLayerStack`:推荐挂到 tonic server 外层的 Tower-first unary 自动韧性层。
- `RpcUnaryResilienceLayer`:底层兼容 layer,可用于手写高级 tonic/Tower service。
## 熔断器
```rust
use std::time::Duration;
use rs_zero::resil::{BreakerConfig, SharedCircuitBreaker};
let breaker = SharedCircuitBreaker::new(BreakerConfig {
failure_threshold: 5,
reset_timeout: Duration::from_secs(30),
});
let result = breaker
.do_with_acceptable(
|| async { Ok::<_, &'static str>("ok") },
|err| *err == "not-found",
)
.await;
```
需要 go-zero 风格自适应拒绝时:
```rust
use rs_zero::resil::{BreakerConfig, BreakerPolicyConfig, SharedCircuitBreaker};
let breaker = SharedCircuitBreaker::with_policy(
BreakerConfig::default(),
BreakerPolicyConfig::google_sre(),
);
```
语义:
- `do_request`:成功记录 success,错误记录 failure,打开时返回 rejection。
- `do_with_fallback`:仅在 breaker 拒绝时走 fallback。
- `do_with_acceptable`:允许业务把部分错误标记为“可接受”,这类错误不会推动熔断。
- `allow` / `BreakerGuard` 仍保留,适合自定义中间件。
## REST 接入
启用 feature:
```toml
rs-zero = { version = "0.1", features = ["rest", "resil", "observability"] }
```
配置:
```rust
use std::time::Duration;
use rs_zero::rest::{RestConfig, RestResilienceConfig};
let mut config = RestConfig::default();
config.middlewares.resilience = RestResilienceConfig {
breaker_enabled: true,
breaker_sre_enabled: true,
breaker_failure_threshold: 5,
breaker_reset_timeout: Duration::from_secs(30),
max_concurrency: Some(1024),
request_timeout: Some(Duration::from_secs(3)),
shedding_enabled: true,
shedding_max_in_flight: Some(1024),
shedding_min_request_count: 20,
shedding_max_latency: Duration::from_millis(250),
..RestResilienceConfig::default()
};
```
生产入口可以直接使用生产默认值,再按服务容量微调:
```rust
let config = RestConfig::production_defaults("user-api");
```
`production_defaults` 会开启连续失败 breaker、SRE 自适应 breaker、并发保护、adaptive shedder 和基础 metrics。Redis limiter 默认关闭,需要显式配置。
启用 `cache-redis` feature 后,可以用 `production_defaults_with_redis_limiter` 保留生产默认值并显式打开 Redis limiter:
```rust
use rs_zero::resil::RedisTokenLimiterConfig;
use rs_zero::rest::{RestConfig, RestRateLimiterConfig};
let config = RestConfig::production_defaults_with_redis_limiter(
"user-api",
RestRateLimiterConfig::RedisToken(RedisTokenLimiterConfig::go_zero_defaults()),
);
```
fixed-window period limiter 使用同一入口:
```rust
use rs_zero::resil::RedisPeriodLimiterConfig;
use rs_zero::rest::{RestConfig, RestRateLimiterConfig};
let config = RestConfig::production_defaults_with_redis_limiter(
"user-api",
RestRateLimiterConfig::RedisPeriod(RedisPeriodLimiterConfig::default()),
);
```
REST 默认层使用 service + method + matched route 作为 resilience key。拒绝结果会返回统一响应:
- breaker open:`SERVICE_UNAVAILABLE`
- concurrency exhausted:`CONCURRENCY_LIMIT`
- Redis limiter exhausted:`RATE_LIMITED`
- adaptive shedding:`OVERLOADED`
- timeout:`TIMEOUT`
如果同时配置 `metrics_registry`,REST resilience 会记录低基数事件:
- `component="breaker"`,`outcome="allowed|dropped|success|failure"`
- `component="concurrency"`,`outcome="allowed|rejected"`
- `component="limiter"`,`outcome="allowed|rejected|error_open|error_closed"`
- `component="shedder"`,`outcome="pass|drop"`
## RPC unary 接入
`RpcResilienceLayer` 是 helper,不替代 tonic 的 service 生成代码。生成或手写 RPC adapter 可在调用业务逻辑前后包一层:
```rust
use std::time::Duration;
use rs_zero::rpc::{RpcResilienceConfig, RpcResilienceLayer};
let layer = RpcResilienceLayer::new(
"hello-rpc",
RpcResilienceConfig {
breaker_enabled: true,
breaker_sre_enabled: true,
breaker_failure_threshold: 5,
breaker_reset_timeout: Duration::from_secs(30),
max_concurrency: Some(1024),
request_timeout: Duration::from_secs(3),
shedding_enabled: true,
shedding_max_in_flight: Some(1024),
shedding_min_request_count: 20,
shedding_max_latency: Duration::from_millis(250),
..RpcResilienceConfig::default()
},
);
let response = layer
.run_unary("SayHello", || async {
Ok::<_, tonic::Status>("ok")
})
.await;
```
RPC 配置同样提供生产默认入口:
```rust
use rs_zero::rpc::{RpcClientConfig, RpcServerConfig};
let client = RpcClientConfig::production_defaults("http://127.0.0.1:50051");
let server = RpcServerConfig::production_defaults("hello-rpc", "127.0.0.1:50051".parse()?);
```
错误映射:
- breaker open:`Code::Unavailable`
- Redis limiter exhausted:`Code::ResourceExhausted`
- Redis limiter fail-closed:`Code::Unavailable`
- adaptive shedding / concurrency limit:`Code::ResourceExhausted`
- timeout:`Code::DeadlineExceeded`
- `InvalidArgument`、`NotFound` 等业务错误默认不计为韧性失败。
当前版本的 unary 主路径推荐通过 `RpcServerLayerStack` 挂到 tonic server 外层;`run_unary` 和 `RpcUnaryResilienceLayer` 继续作为兼容 helper 与手写高级场景入口。streaming 服务提供组合式 wrapper,覆盖 send、recv、finish 和 timeout 观察边界,但仍不替代 tonic-build 生成的业务 stream 类型。
## Redis 限流
启用 `resil + cache-redis` 后,可使用 Redis token bucket 或 fixed-window period limiter,让多个实例共享限流状态:
```rust
use rs_zero::resil::{RedisTokenLimiter, RedisTokenLimiterConfig};
let limiter = RedisTokenLimiter::new(RedisTokenLimiterConfig::default())?;
if limiter.allow("tenant-a").await? {
// run request
}
```
Redis 不可用时默认 fail-closed;如果业务更适合保护可用性,可显式设置 `fail_open = true`。需要生产默认保护时,使用 `RedisTokenLimiterConfig::go_zero_defaults()` 或显式开启 `rescue.enabled`,Redis 异常会切换到本地 token bucket,并后台 ping Redis,恢复后自动切回 Redis。
也可以按业务故障策略显式设置 fail-open:
```rust
use rs_zero::rest::{RestConfig, RestRateLimiterConfig};
use rs_zero::resil::RedisTokenLimiterConfig;
let config = RestConfig::production_defaults_with_redis_limiter(
"user-api",
RestRateLimiterConfig::RedisToken(RedisTokenLimiterConfig {
fail_open: true,
..RedisTokenLimiterConfig::default()
}),
);
```
限流 key 使用 method + route pattern 或 service + method,不包含 raw path、用户 ID、Redis key 等高基数字段。
Period limiter 默认固定 TTL,不对齐自然边界。需要“每分钟/每天自然边界重置”时可启用 align:
```rust
use rs_zero::resil::RedisPeriodLimiterConfig;
let config = RedisPeriodLimiterConfig::default()
.aligned_to_utc_offset(8 * 60 * 60);
```
## Adaptive shedding 模型
`AdaptiveShedder` 参考 go-zero 的 `maxPass`、`minRt`、`avgFlying` 和 CPU 阈值思路:
- `maxPass`:滚动窗口内单 bucket 最大成功数。
- `minRt`:滚动窗口内最小平均延迟。
- `avgFlying`:请求完成时平滑更新的 in-flight 估计。
- CPU 高于阈值时降低 `maxFlight`,近期 drop 后在 `cool_off` 内保持保守。
该模型先保护实例容量,再通过低基数指标观察调参效果。默认 CPU provider 在 Linux 下使用 `/proc/stat` 做 250ms 采样和 EMA 平滑;非 Linux 或无法读取 CPU 时保持 Noop。生产也可以继续注入业务已有的 CPU provider。
## 生产建议
- 所有外部调用都设置 timeout;REST 可用 `request_timeout` 覆盖默认入口超时。
- breaker key 使用服务名、method 和路由模板,不要使用原始 URL 或请求参数。
- `max_concurrency` 和 `shedding_max_in_flight` 先按实例资源保守配置,再结合指标调整。
- 对可预期的业务错误使用 acceptable error,避免无效熔断。
- 如果需要 CPU-aware shedding,Linux 可直接使用默认真实采样;非 Linux 请注入业务已有的 CPU provider。
- 默认测试不依赖外部服务;上线前再结合压测验证阈值。
## 相关测试
- `cargo test -p rs-zero resil --lib`
- `cargo test --test resilience_rest_integration`
- `cargo test --test resilience_rpc_integration`