分布式锁详解
在高并发和分布式系统中,为保证资源的一致性和互斥访问,分布式锁是必不可少的技术手段。本文将系统地讲解分布式锁的概念、场景、实现原理、使用方式,并附 PHP 示例。
1. 什么是分布式锁
定义:
分布式锁(Distributed Lock)是用于在 多台机器或多个服务实例中控制共享资源访问的机制。它保证在同一时间,只有一个客户端可以操作某个资源,从而避免并发冲突。
特点:
互斥性:同一时间只有一个客户端持有锁。
可重入性(可选):同一客户端可以重复获取锁。
可靠性:锁过期或客户端异常可自动释放,避免死锁。
可扩展性:适用于分布式系统和微服务架构。
2. 分布式锁的应用场景
分布式锁在企业场景非常常见,典型使用场景包括:
库存扣减
- 秒杀或抢购场景,防止库存超卖。
订单号生成
- 保证全局唯一订单号。
任务调度
- 多实例系统中,保证任务只被单实例执行。
缓存更新
- 防止缓存击穿时,多实例同时重建缓存。
支付或转账操作
- 防止重复扣款或资金异常。
3. 分布式锁的实现原理
3.1 数据库锁
原理:
- 利用数据库唯一索引或事务机制创建锁记录。
示例:
INSERT INTO locks(name, create_time) VALUES('order_123', NOW()); -- 成功表示获得锁,失败表示锁已被占用优缺点:
优点:简单易用,直接使用现有数据库。
缺点:高并发性能较差,数据库可能成为瓶颈。
3.2 Redis 锁
原理:
利用 Redis 的 SETNX 或 SET key value NX PX 原子操作。
设置锁过期时间,防止死锁。
(RedLock红锁可以解决在 Redis 主从复制或哨兵模式下,使用单实例 Redis 锁可能遇到的锁失效问题。)
释放锁:
- 需要验证当前客户端持有锁,防止误删他人锁。
优点:
- 高性能,适合秒级或毫秒级操作。
缺点:
- 锁过期可能导致业务执行未完成就释放,需要设计安全释放策略。
3.3 ZooKeeper 锁
原理:
使用 ZooKeeper 的 临时顺序节点实现锁。
创建节点表示加锁,最小顺序节点获得锁。
节点释放或客户端宕机,其他节点继续竞争。
优点:
- 强一致性,可靠。
缺点:
- 部署复杂,开发成本高。
4. 分布式锁使用注意事项
锁过期时间
- 避免死锁,锁持有时间需大于业务执行时间。
持有者验证
- 释放锁前需确认自己是持有者。
重试机制
- 获取锁失败时可等待重试或立即返回错误。
业务幂等性
- 即使锁失败或重复执行,业务结果保持一致。
减少锁粒度
- 尽量缩短锁持有时间,提高并发性能。
5. 实践示例 (PHP Redis 分布式锁示例)
<?php
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$lockKey = 'order_123';
$lockValue = uniqid(); // 锁持有者标识
$expire = 3000; // 锁过期时间:3秒(毫秒)
// 尝试获取锁
$acquired = $redis->set($lockKey, $lockValue, ['nx', 'px' => $expire]);
if ($acquired) {
try {
// 执行业务逻辑,比如扣减库存
echo "获得锁,执行业务\n";
} finally {
// 释放锁(验证持有者)
$script = "
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
";
$redis->eval($script, [$lockKey, $lockValue], 1);
echo "释放锁\n";
}
} else {
echo "获取锁失败,稍后重试\n";
}
说明:
NX:只有锁不存在时才设置成功。PX:设置锁自动过期时间。evalLua 脚本保证释放锁操作的原子性。uniqid()确保锁持有者标识唯一。