rs-zero 0.2.8

Rust-first microservice framework inspired by go-zero engineering practices
Documentation
# 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`