# 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。
- `RpcUnaryResilienceLayer`：可挂到 tonic/Tower service 的 unary 自动韧性层。

## 熔断器

```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()
};
```

生产入口可以直接使用 go-zero 风格默认值，再按服务容量微调：

```rust
let config = RestConfig::go_zero_defaults("user-api");
```

`go_zero_defaults` 会开启连续失败 breaker、SRE 自适应 breaker、并发保护、adaptive shedder 和基础 metrics。Redis limiter 默认关闭，需要显式配置。

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::go_zero_defaults("http://127.0.0.1:50051");
let server = RpcServerConfig::go_zero_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 主路径既支持生成代码显式调用 `run_unary`，也支持通过 `RpcUnaryResilienceLayer` 挂到 tonic/Tower service。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`。需要 go-zero 风格保护时，使用 `RedisTokenLimiterConfig::go_zero_defaults()` 或显式开启 `rescue.enabled`，Redis 异常会切换到本地 token bucket，并后台 ping Redis，恢复后自动切回 Redis。

默认 REST/RPC 链路也能直接接入 Redis limiter：

```rust
use rs_zero::rest::{RestConfig, RestRateLimiterConfig};
use rs_zero::resil::RedisTokenLimiterConfig;

let mut config = RestConfig::go_zero_defaults("user-api");
config.middlewares.resilience.rate_limiter =
    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`
