# Redis 生产配置

`cache-redis` 是可选 feature，默认 workspace 不引入 Redis 依赖。启用后可使用 `RedisCacheStore`、应用级 `RedisShardedCacheStore` 以及 Redis-backed limiter。

## 单节点 cache store

```rust,no_run
use rs_zero::cache_redis::{RedisCacheConfig, RedisCacheStore};

let store = RedisCacheStore::new(RedisCacheConfig {
    url: "redis://127.0.0.1:6379".to_string(),
    namespace: "orders".to_string(),
    breaker: rs_zero::cache_redis::RedisBreakerConfig::go_zero_defaults(),
    ..RedisCacheConfig::default()
})?;
# Ok::<(), rs_zero::cache_redis::RedisCacheError>(())
```

生产环境必须设置连接超时、命令超时和稳定 namespace。不要把 Redis URL、密码或 cache key 写入指标标签或日志字段。

## Redis breaker

`RedisCacheStore` 可在 adapter 内启用 breaker 保护，覆盖连接获取和命令执行。默认 `RedisBreakerConfig::default()` 保持关闭以兼容既有行为；生产配置建议显式使用 `RedisBreakerConfig::go_zero_defaults()`，或按服务容量设置 `failure_threshold`、`reset_timeout` 和 rolling-window policy。

breaker 打开后，请求不会继续打到底层 Redis client。拒绝结果会进入现有 `RedisUnavailablePolicy`：

- fail-closed：返回包含 circuit breaker 的错误。
- fail-open：`get` 返回 miss，`set` / `delete` / `delete_many` 跳过写入。

`NOSCRIPT` 和 Cluster redirect 这类可恢复结果不计为 breaker 失败；连接错误、timeout 和普通 backend 错误会计为失败。

## Redis Cluster 与应用级分片

rs-zero 明确区分两类能力：

- Redis Cluster：`RedisCacheStore` 在 `cluster.enabled = true` 时使用 `redis-rs` 原生 `ClusterClient`，由成熟客户端处理 16384 slot 路由、`MOVED` / `ASK`、拓扑刷新和重试。
- 应用级分片：`RedisShardedCacheStore` 使用 rendezvous hashing 在多个单节点 Redis 间选 shard，不是 Redis Cluster 协议 adapter。

Cluster 模式下 `RedisCacheConfig.url` 支持逗号分隔 startup nodes，例如 `redis://127.0.0.1:7000,redis://127.0.0.1:7001`。`RedisClusterRouter`、`RedisClusterSlotMap`、`RedisClusterRedirect` 和 `redis_cluster_slot` 保留为诊断与测试工具，不再承担生产请求路由。

## Lua 脚本缓存

`RedisLuaScript` 会按脚本文本计算 SHA1，优先使用 `EVALSHA`。当 Redis 返回 `NOSCRIPT` 时，会执行 `SCRIPT LOAD` 并重试一次。Redis token bucket 与 fixed-window period limiter 通过该封装保留原有 fail-open / fail-closed 配置。

## Redis 不可用降级

默认 `RedisUnavailablePolicy::fail_closed()` 保持兼容：Redis 连接或命令失败会返回错误。只有显式配置 `RedisUnavailablePolicy::fail_open_for_cache()` 时，cache store 才会：

- `get` 失败返回 cache miss。
- `set` 失败跳过写入。
- `delete` / `delete_many` 失败跳过删除。

所有显式降级都会记录低基数降级事件。降级适合“缓存不可用但数据库仍可承载”的场景；不适合依赖 Redis 保证强一致、配额或互斥的路径。

## 指标

启用 `observability` 后，Redis 相关指标包括：

- `rs_zero_redis_commands_total`
- `rs_zero_redis_command_errors_total`
- `rs_zero_redis_command_duration_seconds`
- `rs_zero_redis_events_total`
- `rs_zero_redis_degradations_total`

标签保持低基数：`command`、`event`、`shard`、`result`、`operation`、`action`。breaker 事件使用 `event="breaker"`，结果包括 `success`、`timeout`、`error`、`breaker_rejected`。指标不得包含 Redis key、Redis URL、密码或用户输入。

## 外部测试

默认 CI 只运行离线测试。手动验证外部 Redis 时可使用：

```bash
RS_ZERO_TEST_REDIS_URL=redis://127.0.0.1:6379 \
  cargo test --features cache-redis --test cache_redis_external -- --ignored
```

也可使用脚本同时覆盖单节点 Redis roundtrip 和本地 breaker 故障注入：

```bash
scripts/external-integration.sh redis redis-fault
```

手动验证 Redis Cluster 时设置：

```bash
RS_ZERO_TEST_REDIS_CLUSTER_URL=redis://127.0.0.1:7000,redis://127.0.0.1:7001 \
  cargo test --features cache-redis --test cache_redis_external -- --ignored
```

或运行：

```bash
RS_ZERO_TEST_REDIS_CLUSTER_URL=redis://127.0.0.1:7000,redis://127.0.0.1:7001 \
  scripts/external-integration.sh redis-cluster
```

`redis-cluster` 目标不会自动创建集群，需要传入已有 Redis Cluster startup nodes；这能避免脚本误修改本机 Redis 配置。
