引言:为什么需要分布式锁?
在现代分布式系统中,当多个服务节点需要协调访问共享资源(如数据库、文件存储、API调用)时,传统的单机锁机制(如mutex)将不再适用。分布式锁通过跨节点协调机制,确保全局资源访问的互斥性。
一、分布式锁核心特性
-
互斥性:同一时刻仅有一个客户端持有锁
-
容错性:锁服务必须高可用
-
超时释放:防止死锁的自动释放机制
-
可重入性:同一线程可重复获取锁(可选)
二、基于Redis的C++分布式锁实现
环境准备
-
使用hiredis客户端库(v1.1.0+)
-
Redis服务器(5.0+版本支持Lua脚本)
代码实现
#include <hiredis/hiredis.h>
#include <uuid.h>
#include <chrono>
class RedisDistributedLock {
public:
RedisDistributedLock(redisContext* conn, const std::string& key)
: conn_(conn), lock_key_(key), lock_val_(generate_uuid()) {}
bool try_lock(int expire_ms = 30000, int retry_count = 3) {
std::string cmd = "SET " + lock_key_ + " " + lock_val_
+ " NX PX " + std::to_string(expire_ms);
for (int i = 0; i < retry_count; ++i) {
redisReply* reply = (redisReply*)redisCommand(conn_, cmd.c_str());
if (reply && reply->type == REDIS_REPLY_STATUS
&& strcmp(reply->str, "OK") == 0) {
freeReplyObject(reply);
return true;
}
freeReplyObject(reply);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
return false;
}
bool unlock() {
const char* lua_script =
"if redis.call('get', KEYS[1]) == ARGV[1] then "
" return redis.call('del', KEYS[1]) "
"else "
" return 0 "
"end";
redisReply* reply = (redisReply*)redisCommand(conn_,
"EVAL %s 1 %s %s",
lua_script,
lock_key_.c_str(),
lock_val_.c_str());
bool success = (reply && reply->type == REDIS_REPLY_INTEGER
&& reply->integer == 1);
freeReplyObject(reply);
return success;
}
private:
std::string generate_uuid() {
uuids::uuid_random_generator gen{};
return uuids::to_string(gen());
}
redisContext* conn_;
std::string lock_key_;
std::string lock_val_;
};
使用示例
int main() {
// 创建Redis连接
redisContext* conn = redisConnect("127.0.0.1", 6379);
if (conn == nullptr || conn->err) {
// 错误处理
return -1;
}
RedisDistributedLock lock(conn, "order_lock");
if (lock.try_lock()) {
try {
// 临界区操作
process_order();
} catch (...) {
// 异常处理
}
lock.unlock();
} else {
std::cerr << "获取锁失败" << std::endl;
}
redisFree(conn);
return 0;
}
三、六大常见陷阱与解决方案
陷阱1:锁未设置超时
现象:客户端崩溃导致死锁
方案:必须设置PX参数指定过期时间
陷阱2:非原子性释放
错误代码:
if (get(lock_key) == my_val) del(lock_key) // 非原子操作!
方案:使用Lua脚本保证原子性
陷阱3:锁续期问题
现象:业务执行时间超过锁有效期
方案:
// 启动独立线程定期续期
void renew_thread() {
while (running_) {
redisCommand(conn_, "PEXPIRE %s %d",
lock_key_.c_str(), expire_ms_);
std::this_thread::sleep_for(expire_ms_/3);
}
}
陷阱4:客户端时钟不同步
现象:Redis服务器与客户端时间差异导致锁提前失效
方案:依赖Redis服务器时间,避免使用本地时间计算
陷阱5:网络分区风险
现象:Redis主从切换导致锁状态不一致
方案:使用RedLock算法(需部署多个独立Redis实例)
陷阱6:错误的重试策略
错误做法:立即无限重试
正确方案:采用指数退避算法
int retry_delay = 100; // 初始100ms
while (retries_left--) {
if (try_lock()) break;
std::this_thread::sleep_for(retry_delay);
retry_delay = std::min(retry_delay * 2, 1000); // 上限1秒
}
四、生产环境最佳实践
-
监控指标:
-
锁获取平均耗时
-
锁冲突率
-
锁续期成功率
-
-
降级策略:
// 伪代码示例 if (distributed_lock_failed) { if (allow_local_lock) { std::lock_guard<std::mutex> lk(local_mutex); process(); } else { throw ServiceUnavailableException(); } } -
组件选型建议:
-
Redis:高性能但需处理脑裂问题
-
ZooKeeper:强一致性但写性能较低
-
etcd:Kubernetes生态首选
-
五、进阶话题
-
公平锁实现:使用Redis的LIST结构维护等待队列
-
可重入锁:通过ThreadLocal存储持有计数
-
异步锁:基于Redis的PUB/SUB实现非阻塞通知
结语
分布式锁是分布式系统的双刃剑——使用得当可提升系统可靠性,滥用则可能成为性能瓶颈。建议:
-
优先考虑无锁化设计
-
锁粒度尽可能细化
-
使用经过验证的库(如Redisson)而非重复造轮子

257

被折叠的 条评论
为什么被折叠?



