rs-zero 0.2.6

Rust-first microservice framework inspired by go-zero engineering practices
Documentation
# Distributed lock 手册

`rs-zero` 的分布式锁用于跨进程协调短时临界区。第一版提供 Redis backend,使用 `SET key token NX PX ttl` 获取锁,并用 Lua 脚本校验 owner token 后删除,避免误删他人锁。

## Feature

```toml
rs-zero = { version = "0.2.1", default-features = false, features = ["cache-redis"] }
```

`cache-redis` 会同时启用 `cache` 与 `resil`,因此 Redis lock 可复用 Redis client、timeout、breaker 和低基数 Redis 指标。

## 基本用法

```rust
use std::time::Duration;

use rs_zero::lock::{DistributedLock, RedisDistributedLock, RedisLockConfig};

let lock = RedisDistributedLock::new(RedisLockConfig::default())?;
let guard = lock
    .try_acquire("order:{123}", Duration::from_secs(5))
    .await?;

// 执行业务临界区。

lock.release(&guard).await?;
```

如果锁已被其他 owner 持有,`try_acquire` 返回 `LockError::Busy`。如果希望自行处理竞争,可以使用 `acquire`:

```rust
if let Some(guard) = lock.acquire("order:{123}", Duration::from_secs(5)).await? {
    lock.release(&guard).await?;
}
```

## 配置

```rust
use std::time::Duration;

use rs_zero::{
    cache_redis::{RedisBreakerConfig, RedisCacheConfig},
    lock::{RedisDistributedLock, RedisLockConfig},
};

let lock = RedisDistributedLock::new(RedisLockConfig {
    redis: RedisCacheConfig {
        url: "redis://127.0.0.1:6379".to_string(),
        connect_timeout: Duration::from_secs(1),
        command_timeout: Duration::from_millis(500),
        ..RedisCacheConfig::default()
    },
    namespace: "order-service:lock".to_string(),
    command_timeout: Duration::from_millis(500),
    breaker: RedisBreakerConfig::go_zero_defaults(),
    ..RedisLockConfig::default()
})?;
```

规则:

- `namespace` 必须稳定,避免不同服务或环境共享锁空间。
- TTL 必须大于 0,禁止无过期锁。
- owner token 是随机值,不应写入日志、metrics label 或错误消息。
- 解锁只删除 token 匹配的锁;锁已过期或被新 owner 获取时,旧 guard release 会返回 `LockError::OwnerMismatch`- 默认不自动续租。业务临界区必须短于 TTL,并保留足够安全余量。

## Redis Cluster

Cluster 模式使用 `RedisCacheConfig.cluster.enabled = true`。分布式锁 key 默认要求包含 Redis hash tag:

```rust
let guard = lock.try_acquire("order:{123}", Duration::from_secs(5)).await?;
```

原因:获取锁和 Lua 解锁都必须访问同一个 Redis slot。不要使用没有 hash tag 的 Cluster lock key。确有兼容需求时可以关闭 `require_cluster_hash_tag`,但生产环境不推荐。

## 观测与韧性

Redis lock 复用 Redis command recorder:

- `LOCK_ACQUIRE` 记录获取锁命令耗时和结果。
- `LOCK_RELEASE` 记录释放锁命令耗时和结果。
- `LOCK_PING` 记录健康检查。
- breaker 事件走 Redis breaker event。

指标必须保持低基数:只记录 command、shard、result,不记录原始 lock key、owner token、URL、用户 ID 或业务参数。

## 生产边界

- 分布式锁不是数据库唯一约束、事务或幂等设计的替代品。
- Redis 单实例锁适合短时互斥;强一致协调场景优先考虑 etcd lease/txn 或数据库事务。
- 第一版不实现 Redlock,也不默认自动续租。
- 若锁保护外部资源写入,建议业务额外设计 fencing token 或版本号检查。
- Redis down、breaker open、timeout 都应作为明确错误处理,不要静默放行业务临界区。

## 验证

默认测试不依赖真实 Redis:

```bash
cargo test -p rs-zero --features cache-redis --test redis_lock
```

真实 Redis 验证走 opt-in:

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