设计分布式锁
问题
如何用 Rust 实现分布式锁?
答案
Redis 分布式锁
use redis::AsyncCommands;
use uuid::Uuid;
use std::time::Duration;
pub struct RedisLock {
client: redis::Client,
key: String,
value: String, // 唯一标识(防止误删)
ttl: Duration,
}
impl RedisLock {
pub fn new(redis_url: &str, key: &str, ttl: Duration) -> Self {
Self {
client: redis::Client::open(redis_url).unwrap(),
key: key.to_string(),
value: Uuid::new_v4().to_string(),
ttl,
}
}
/// 尝试获取锁
pub async fn try_lock(&self) -> Result<bool, redis::RedisError> {
let mut conn = self.client.get_multiplexed_async_connection().await?;
let result: bool = redis::cmd("SET")
.arg(&self.key)
.arg(&self.value)
.arg("NX") // 仅当 key 不存在时设置
.arg("PX") // 过期时间(毫秒)
.arg(self.ttl.as_millis() as u64)
.query_async(&mut conn)
.await
.unwrap_or(false);
Ok(result)
}
/// 释放锁(Lua 脚本保证原子性)
pub async fn unlock(&self) -> Result<bool, redis::RedisError> {
let mut conn = self.client.get_multiplexed_async_connection().await?;
// Lua 脚本:只有 value 匹配才删除(防止误删别人的锁)
let script = redis::Script::new(
r#"
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end
"#,
);
let result: i32 = script
.key(&self.key)
.arg(&self.value)
.invoke_async(&mut conn)
.await?;
Ok(result == 1)
}
}
RAII 锁守卫
/// 自动释放的锁守卫
pub struct LockGuard {
lock: RedisLock,
}
impl Drop for LockGuard {
fn drop(&mut self) {
// 在 Drop 中释放锁
let lock_key = self.lock.key.clone();
let lock_value = self.lock.value.clone();
let client = self.lock.client.clone();
tokio::spawn(async move {
// 异步释放
let mut conn = client.get_multiplexed_async_connection().await.unwrap();
let script = redis::Script::new(
"if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) else return 0 end"
);
let _: i32 = script.key(&lock_key).arg(&lock_value)
.invoke_async(&mut conn).await.unwrap_or(0);
});
}
}
分布式锁方案对比
| 方案 | 实现 | 优点 | 缺点 |
|---|---|---|---|
| Redis SETNX | 简单 | 性能高 | 非强一致 |
| Redlock | 多 Redis 实例 | 更可靠 | 复杂,存在争议 |
| etcd | 基于 Raft | 强一致 | 性能较低 |
| ZooKeeper | 临时节点 | 成熟 | 运维复杂 |
常见面试问题
Q1: Redis 分布式锁有什么问题?
答案:
- 锁过期:任务未完成但锁已过期 → 使用看门狗续期
- Redis 故障:主从切换导致锁丢失 → Redlock 多节点
- 时钟偏差:不同节点时钟不同步 → 使用单调时钟
- 误删锁:其他客户端删掉自己的锁 → value 唯一标识 + Lua 脚本