锁机制完全指南
文档类型: 锁机制深度解析
更新时间: 2025-10-28
目录
1. 锁的基本概念
1.1 为什么需要锁?
并发问题示例:
线程1读取: balance = 100
线程2读取: balance = 100
线程1计算: balance = 100 + 50 = 150
线程2计算: balance = 100 - 30 = 70
线程1写入: balance = 150
线程2写入: balance = 70 ← 覆盖了线程1的结果
期望结果: 100 + 50 - 30 = 120
实际结果: 70 ❌
锁的作用:保证同一时刻只有一个线程/进程访问共享资源。
1.2 锁的分类
按粒度分:
├─ 表锁 (锁整张表)
├─ 行锁 (锁某一行)
├─ 页锁 (锁数据页)
└─ 间隙锁 (锁范围)
按模式分:
├─ 共享锁(S锁) (读锁,可并发读)
├─ 排他锁(X锁) (写锁,独占)
└─ 意向锁 (表级锁的标记)
按实现分:
├─ 悲观锁 (先加锁后操作)
├─ 乐观锁 (先操作后检查)
├─ 自旋锁 (忙等待)
└─ 互斥锁 (阻塞等待)
按范围分:
├─ 本地锁 (单机)
└─ 分布式锁 (跨机器)
1.3 锁兼容性矩阵
当前持有\请求 | S锁 | X锁
─────────────┼───────┼───────
S锁 | ✅ | ❌
X锁 | ❌ | ❌
说明:
✅ 兼容:可以并发
❌ 冲突:需要等待
示例:
- 多个事务可以同时持有S锁(并发读)
- 持有X锁时,其他事务不能获取任何锁(独占写)
2. 应用层锁
2.1 互斥锁(Mutex)
// PHP中的互斥锁
class MutexLock
{
private $lock;
public function __construct()
{
$this->lock = new \Swoole\Lock(SWOOLE_MUTEX);
}
public function execute(callable $callback)
{
$this->lock->lock(); // 获取锁
try {
return $callback();
} finally {
$this->lock->unlock(); // 释放锁
}
}
}
// 使用示例:保护临界区
$counter = 0;
$mutex = new MutexLock();
// 100个协程并发累加
for ($i = 0; $i < 100; $i++) {
go(function () use ($mutex, &$counter) {
for ($j = 0; $j < 100; $j++) {
$mutex->execute(function () use (&$counter) {
$counter++; // 临界区
});
}
});
}
// 结果:10000 ✅
2.2 读写锁(RWLock)
// 读写锁:读共享,写独占
class RWLock
{
private $lock;
public function __construct()
{
$this->lock = new \Swoole\Lock(SWOOLE_RWLOCK);
}
public function read(callable $callback)
{
$this->lock->lock_read(); // 读锁(可并发)
try {
return $callback();
} finally {
$this->lock->unlock();
}
}
public function write(callable $callback)
{
$this->lock->lock(); // 写锁(独占)
try {
return $callback();
} finally {
$this->lock->unlock();
}
}
}
// 使用示例:缓存场景
class Cache
{
private $data = [];
private $rwlock;
public function __construct()
{
$this->rwlock = new RWLock();
}
public function get(string $key)
{
return $this->rwlock->read(function () use ($key) {
return $this->data[$key] ?? null;
});
}
public function set(string $key, $value)
{
$this->rwlock->write(function () use ($key, $value) {
$this->data[$key] = $value;
});
}
}
// 性能对比:
// 互斥锁:所有操作串行
// 读写锁:多个读操作可并发 ✅
2.3 自旋锁(Spinlock)
// 自旋锁:忙等待,不挂起线程
class SpinLock
{
private $lock;
public function __construct()
{
$this->lock = new \Swoole\Lock(SWOOLE_SPINLOCK);
}
public function execute(callable $callback)
{
while (!$this->lock->trylock()) {
// 自旋等待(消耗CPU)
usleep(1);
}
try {
return $callback();
} finally {
$this->lock->unlock();
}
}
}
// 适用场景:
// ✅ 临界区执行时间极短(微秒级)
// ✅ CPU核心数充足
// ❌ 临界区执行时间长(浪费CPU)
2.4 信号量(Semaphore)
// 信号量:允许N个并发
class Semaphore
{
private $sem;
public function __construct(int $count)
{
$this->sem = new \Swoole\Lock(SWOOLE_SEM);
// 初始化信号量为$count
}
public function acquire()
{
$this->sem->lock(); // P操作(-1)
}
public function release()
{
$this->sem->unlock(); // V操作(+1)
}
}
// 使用示例:连接池
class ConnectionPool
{
private $pool = [];
private $semaphore;
public function __construct(int $size)
{
$this->semaphore = new Semaphore($size);
// 初始化连接池
for ($i = 0; $i < $size; $i++) {
$this->pool[] = $this->createConnection();
}
}
public function getConnection()
{
$this->semaphore->acquire(); // 获取许可
return array_pop($this->pool);
}
public function releaseConnection($conn)
{
$this->pool[] = $conn;
$this->semaphore->release(); // 释放许可
}
}
// 场景:限制并发数量
// 10个协程竞争3个连接
// 只有3个能同时执行,其他7个等待
2.5 条件变量(Condition Variable)
// 条件变量:线程间协作
class ConditionVariable
{
private $mutex;
private $cond;
public function __construct()
{
$this->mutex = new \Swoole\Lock(SWOOLE_MUTEX);
// PHP没有原生条件变量,用Channel模拟
$this->cond = new \Swoole\Coroutine\Channel(1);
}
public function wait()
{
$this->mutex->unlock(); // 释放锁
$this->cond->pop(); // 阻塞等待
$this->mutex->lock(); // 重新获取锁
}
public function signal()
{
$this->cond->push(true); // 唤醒一个等待者
}
public function broadcast()
{
// 唤醒所有等待者
while (!$this->cond->isEmpty()) {
$this->cond->push(true);
}
}
}
// 使用示例:生产者-消费者
class Queue
{
private $items = [];
private $notEmpty;
private $notFull;
private $capacity;
public function __construct(int $capacity)
{
$this->capacity = $capacity;
$this->notEmpty = new ConditionVariable();
$this->notFull = new ConditionVariable();
}
public function put($item)
{
while (count($this->items) >= $this->capacity) {
$this->notFull->wait(); // 队列满,等待
}
$this->items[] = $item;
$this->notEmpty->signal(); // 通知消费者
}
public function get()
{
while (empty($this->items)) {
$this->notEmpty->wait(); // 队列空,等待
}
$item = array_shift($this->items);
$this->notFull->signal(); // 通知生产者
return $item;
}
}
3. 数据库锁
3.1 MySQL锁类型
表锁
-- 显式表锁
LOCK TABLES users READ; -- 读锁(共享)
SELECT * FROM users;
UNLOCK TABLES;
LOCK TABLES users WRITE; -- 写锁(排他)
UPDATE users SET name = 'John';
UNLOCK TABLES;
-- 特点:
-- ✅ 开销小
-- ❌ 粒度大,并发度低
-- 使用场景:MyISAM引擎
行锁
-- InnoDB行锁(隐式)
-- 共享锁(S锁)
SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE;
-- 排他锁(X锁)
SELECT * FROM users WHERE id = 1 FOR UPDATE;
-- 特点:
-- ✅ 粒度小,并发度高
-- ❌ 开销大
-- 使用场景:InnoDB引擎
3.2 InnoDB锁详解
记录锁(Record Lock)
-- 锁定某一行
BEGIN;
SELECT * FROM users WHERE id = 1 FOR UPDATE;
-- 只锁id=1这一行
COMMIT;
间隙锁(Gap Lock)
-- 锁定范围,防止幻读
BEGIN;
SELECT * FROM users WHERE id BETWEEN 5 AND 10 FOR UPDATE;
-- 锁定(5, 10)区间,防止插入id=6,7,8,9的记录
COMMIT;
-- 示例:
-- 现有记录: id = 3, 5, 10, 15
-- 执行: SELECT * FROM users WHERE id > 5 AND id < 15 FOR UPDATE;
-- 锁定间隙: (5, 10), (10, 15)
-- 阻止插入: id = 6,7,8,9,11,12,13,14
Next-Key Lock
-- Next-Key Lock = Record Lock + Gap Lock
-- 现有记录: 10, 20, 30
BEGIN;
SELECT * FROM users WHERE id >= 20 FOR UPDATE;
-- 锁定:
-- Record Lock: id = 20, 30
-- Gap Lock: (20, 30), (30, +∞)
-- Next-Key Lock: (10, 20], (20, 30], (30, +∞)
-- 阻止:
-- INSERT id = 21, 25, 31, 100... (间隙内的所有插入)
COMMIT;
3.3 锁等待与死锁
查看锁等待
-- 查看当前锁
SELECT * FROM information_schema.INNODB_LOCKS;
-- 查看锁等待
SELECT * FROM information_schema.INNODB_LOCK_WAITS;
-- 查看事务
SELECT * FROM information_schema.INNODB_TRX;
-- 实战:找出阻塞的事务
SELECT
waiting_trx_id AS '等待的事务',
waiting_pid AS '等待的进程',
blocking_trx_id AS '阻塞的事务',
blocking_pid AS '阻塞的进程'
FROM (
SELECT
r.trx_id AS waiting_trx_id,
r.trx_mysql_thread_id AS waiting_pid,
b.trx_id AS blocking_trx_id,
b.trx_mysql_thread_id AS blocking_pid
FROM information_schema.INNODB_LOCK_WAITS w
JOIN information_schema.INNODB_TRX r ON r.trx_id = w.requesting_trx_id
JOIN information_schema.INNODB_TRX b ON b.trx_id = w.blocking_trx_id
) t;
-- 杀死阻塞的进程
KILL blocking_pid;
死锁示例
-- 事务1
BEGIN;
UPDATE account SET balance = balance - 100 WHERE id = 1; -- 锁A
-- 等待1秒
UPDATE account SET balance = balance + 100 WHERE id = 2; -- 等待锁B
COMMIT;
-- 事务2
BEGIN;
UPDATE account SET balance = balance - 100 WHERE id = 2; -- 锁B
UPDATE account SET balance = balance + 100 WHERE id = 1; -- 等待锁A
COMMIT;
-- 结果:死锁检测
-- InnoDB检测到死锁,回滚其中一个事务
-- ERROR 1213 (40001): Deadlock found when trying to get lock
死锁日志
-- 查看最近一次死锁
SHOW ENGINE INNODB STATUS\G
-- 输出:
*** (1) TRANSACTION:
TRANSACTION 1234, ACTIVE 5 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)
UPDATE account SET balance = balance + 100 WHERE id = 2
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 0 page no 3 n bits 72 index PRIMARY of table `test`.`account`
trx id 1234 lock_mode X locks rec but not gap waiting
*** (2) TRANSACTION:
...
*** WE ROLL BACK TRANSACTION (1)
3.4 悲观锁 vs 乐观锁
悲观锁
// 悲观锁:先加锁后操作
class PessimisticLock
{
public function transfer(int $fromId, int $toId, int $amount)
{
DB::beginTransaction();
try {
// 加排他锁
$from = DB::table('accounts')
->where('id', $fromId)
->lockForUpdate() // FOR UPDATE
->first();
$to = DB::table('accounts')
->where('id', $toId)
->lockForUpdate()
->first();
if ($from->balance < $amount) {
throw new Exception('余额不足');
}
// 扣款
DB::table('accounts')
->where('id', $fromId)
->decrement('balance', $amount);
// 入账
DB::table('accounts')
->where('id', $toId)
->increment('balance', $amount);
DB::commit();
} catch (Exception $e) {
DB::rollBack();
throw $e;
}
}
}
// 特点:
// ✅ 数据一致性强
// ❌ 并发性能低
// 适用:写操作多、冲突频繁
乐观锁
// 乐观锁:版本号机制
class OptimisticLock
{
public function updateUser(int $userId, array $data)
{
// 读取数据和版本号
$user = DB::table('users')
->where('id', $userId)
->first();
$currentVersion = $user->version;
// 更新(比较版本号)
$affected = DB::table('users')
->where('id', $userId)
->where('version', $currentVersion) // 版本号未变
->update([
'name' => $data['name'],
'version' => $currentVersion + 1 // 版本号+1
]);
if ($affected === 0) {
throw new Exception('数据已被修改,请重试');
}
}
}
// SQL实现
UPDATE users
SET name = 'John',
version = version + 1
WHERE id = 1
AND version = 10; -- 当前版本号
-- affected_rows = 0 表示冲突
// 特点:
// ✅ 并发性能高
// ❌ 冲突时需要重试
// 适用:读操作多、冲突少
3.5 MVCC多版本并发控制
MVCC原理:
- 每行记录保存多个版本
- 事务根据事务ID选择对应版本
- 实现无锁读
记录结构:
┌────┬──────┬─────────┬────────┬───────────┐
│ id │ name │ trx_id │roll_ptr│ delete_bit│
├────┼──────┼─────────┼────────┼───────────┤
│ 1 │ Tom │ 100 │ NULL │ 0 │ ← 当前版本
│ 1 │ John │ 90 │ ptr │ 0 │ ← 历史版本1
│ 1 │ Mike │ 80 │ ptr │ 0 │ ← 历史版本2
└────┴──────┴─────────┴────────┴───────────┘
事务隔离级别与MVCC:
READ COMMITTED:
- 每次读取都生成新的Read View
- 可以读到其他事务已提交的修改
REPEATABLE READ:
- 事务开始时生成Read View
- 整个事务期间使用同一个Read View
- 读到的数据始终一致(可重复读)
示例:
T1: BEGIN; (trx_id = 100)
T2: BEGIN; (trx_id = 101)
T1: SELECT * FROM users WHERE id = 1; -- name = 'Tom'
T2: UPDATE users SET name = 'John' WHERE id = 1;
T2: COMMIT;
T1: SELECT * FROM users WHERE id = 1;
-- REPEATABLE READ: name = 'Tom' (读旧版本)
-- READ COMMITTED: name = 'John' (读新版本)
4. 分布式锁
4.1 为什么需要分布式锁?
单机锁的局限:
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Server1 │ │ Server2 │ │ Server3 │
│ Lock A │ │ Lock A │ │ Lock A │
└─────────┘ └─────────┘ └─────────┘
▲ ▲ ▲
│ │ │
不能跨进程 → 需要分布式锁
分布式锁要求:
✅ 互斥性:任何时刻只有一个客户端持有锁
✅ 不会死锁:即使客户端崩溃,锁也能被释放
✅ 容错性:只要大部分节点正常,就能工作
4.2 Redis分布式锁
基础实现
class RedisLock
{
private $redis;
private $lockKey;
private $lockValue;
/**
* 获取锁
*/
public function acquire(string $key, int $ttl = 10): bool
{
$this->lockKey = "lock:{$key}";
$this->lockValue = uniqid(gethostname(), true);
// SET key value NX EX ttl
// NX: 不存在才设置
// EX: 过期时间
$result = $this->redis->set(
$this->lockKey,
$this->lockValue,
['NX', 'EX' => $ttl]
);
return $result === true;
}
/**
* 释放锁(Lua脚本保证原子性)
*/
public function release(): bool
{
$script = <<<LUA
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
LUA;
return $this->redis->eval(
$script,
[$this->lockKey, $this->lockValue],
1
) === 1;
}
}
// 使用
$lock = new RedisLock($redis);
if ($lock->acquire('order:123', 10)) {
try {
// 业务逻辑
processOrder(123);
} finally {
$lock->release();
}
} else {
// 获取锁失败
echo "系统繁忙,请稍后重试\n";
}
自动续期(看门狗)
class RedisLockWithWatchdog extends RedisLock
{
private $watchdogRunning = false;
public function acquire(string $key, int $ttl = 10): bool
{
if (parent::acquire($key, $ttl)) {
$this->startWatchdog($ttl);
return true;
}
return false;
}
/**
* 看门狗:自动续期
*/
private function startWatchdog(int $ttl)
{
$this->watchdogRunning = true;
go(function () use ($ttl) {
while ($this->watchdogRunning) {
// 每TTL/3时间续期一次
co::sleep($ttl / 3);
if (!$this->watchdogRunning) {
break;
}
// 续期
$script = <<<LUA
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("expire", KEYS[1], ARGV[2])
else
return 0
end
LUA;
$this->redis->eval(
$script,
[$this->lockKey, $this->lockValue, $ttl],
1
);
}
});
}
public function release(): bool
{
$this->watchdogRunning = false; // 停止看门狗
return parent::release();
}
}
RedLock算法(高可用)
/**
* RedLock: 多个Redis实例实现高可用分布式锁
*/
class RedLock
{
private $redisInstances = [];
private $quorum; // 至少需要获取的锁数量
public function __construct(array $redisInstances)
{
$this->redisInstances = $redisInstances;
$this->quorum = count($redisInstances) / 2 + 1;
}
/**
* 获取锁
*/
public function acquire(string $key, int $ttl = 10): bool
{
$lockValue = uniqid(gethostname(), true);
$startTime = microtime(true);
$successCount = 0;
// 尝试在所有实例上获取锁
foreach ($this->redisInstances as $redis) {
if ($this->acquireOnInstance($redis, $key, $lockValue, $ttl)) {
$successCount++;
}
}
// 检查是否获取到足够数量的锁
$elapsedTime = microtime(true) - $startTime;
$validityTime = $ttl - $elapsedTime - 0.001; // 减去网络延迟
if ($successCount >= $this->quorum && $validityTime > 0) {
return true; // 成功获取锁
}
// 获取失败,释放已获取的锁
$this->release($key, $lockValue);
return false;
}
private function acquireOnInstance($redis, string $key, string $value, int $ttl): bool
{
return $redis->set(
"lock:{$key}",
$value,
['NX', 'EX' => $ttl]
) === true;
}
/**
* 释放锁
*/
public function release(string $key, string $lockValue)
{
$script = <<<LUA
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
LUA;
foreach ($this->redisInstances as $redis) {
$redis->eval($script, ["lock:{$key}", $lockValue], 1);
}
}
}
// 使用
$redLock = new RedLock([
new Redis(['host' => '192.168.1.1']),
new Redis(['host' => '192.168.1.2']),
new Redis(['host' => '192.168.1.3']),
new Redis(['host' => '192.168.1.4']),
new Redis(['host' => '192.168.1.5']),
]);
if ($redLock->acquire('resource', 10)) {
// 业务逻辑
$redLock->release('resource', $lockValue);
}
4.3 ZooKeeper分布式锁
/**
* ZooKeeper临时顺序节点实现分布式锁
*/
class ZookeeperLock
{
private $zk;
private $lockPath = '/locks';
private $nodePath;
public function acquire(string $key): bool
{
$path = "{$this->lockPath}/{$key}";
// 创建临时顺序节点
$this->nodePath = $this->zk->create(
"{$path}/lock_",
'',
[
'flags' => Zookeeper::EPHEMERAL | Zookeeper::SEQUENCE
]
);
// 获取所有子节点
$children = $this->zk->getChildren($path);
sort($children);
// 判断自己是否是最小节点
$myNode = basename($this->nodePath);
if ($children[0] === $myNode) {
return true; // 获取锁成功
}
// 监听前一个节点
$index = array_search($myNode, $children);
$prevNode = $children[$index - 1];
// 等待前一个节点删除
$watcher = function ($event) {
if ($event['type'] === Zookeeper::DELETED_EVENT) {
// 前一个节点删除,尝试获取锁
$this->acquire($key);
}
};
$this->zk->exists("{$path}/{$prevNode}", $watcher);
return false;
}
public function release()
{
if ($this->nodePath) {
$this->zk->delete($this->nodePath); // 删除临时节点
}
}
}
// 特点:
// ✅ 公平锁(按顺序获取)
// ✅ 崩溃自动释放(临时节点)
// ✅ 避免惊群效应(只监听前一个节点)
4.4 数据库分布式锁
/**
* 数据库实现分布式锁
*/
class DatabaseLock
{
/**
* 获取锁
*/
public function acquire(string $key, int $ttl = 10): bool
{
$expireAt = time() + $ttl;
try {
// 插入记录
DB::table('distributed_locks')->insert([
'lock_key' => $key,
'lock_value' => uniqid(gethostname(), true),
'expire_at' => $expireAt,
'created_at' => time(),
]);
return true;
} catch (\Exception $e) {
// 唯一索引冲突,锁已存在
return false;
}
}
/**
* 释放锁
*/
public function release(string $key, string $lockValue): bool
{
$affected = DB::table('distributed_locks')
->where('lock_key', $key)
->where('lock_value', $lockValue)
->delete();
return $affected > 0;
}
/**
* 清理过期锁(定时任务)
*/
public function cleanExpired()
{
DB::table('distributed_locks')
->where('expire_at', '<', time())
->delete();
}
}
// 表结构
CREATE TABLE distributed_locks (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
lock_key VARCHAR(255) NOT NULL,
lock_value VARCHAR(255) NOT NULL,
expire_at INT NOT NULL,
created_at INT NOT NULL,
UNIQUE KEY uk_lock_key (lock_key),
KEY idx_expire_at (expire_at)
) ENGINE=InnoDB;
4.5 分布式锁对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Redis | 性能高、实现简单 | 单点故障、时钟漂移 | 高性能、短时锁 |
| RedLock | 高可用 | 实现复杂、性能略低 | 关键业务 |
| ZooKeeper | 可靠性高、支持公平锁 | 性能较低、依赖ZK | 对可靠性要求高 |
| 数据库 | 简单、不依赖外部组件 | 性能低 | 小规模、低并发 |
| Etcd | 高可用、强一致性 | 运维复杂 | 分布式系统 |
5. 锁的性能优化
5.1 减小锁粒度
// ❌ 粗粒度锁:锁整个方法
class BadExample
{
private $lock;
private $data = [];
public function processData(string $key, $value)
{
$this->lock->lock(); // 锁住整个方法
// 业务逻辑(耗时)
sleep(1);
// 更新数据
$this->data[$key] = $value;
$this->lock->unlock();
}
}
// ✅ 细粒度锁:只锁关键部分
class GoodExample
{
private $lock;
private $data = [];
public function processData(string $key, $value)
{
// 业务逻辑(不需要锁)
$processedValue = $this->process($value);
sleep(1);
// 只锁数据更新部分
$this->lock->lock();
$this->data[$key] = $processedValue;
$this->lock->unlock();
}
}
5.2 锁分段
/**
* 分段锁:将数据分成多个段,每段一个锁
*/
class SegmentLock
{
private $segments = 16; // 16个分段
private $locks = [];
private $data = [];
public function __construct()
{
for ($i = 0; $i < $this->segments; $i++) {
$this->locks[$i] = new \Swoole\Lock(SWOOLE_MUTEX);
$this->data[$i] = [];
}
}
private function getSegment(string $key): int
{
return crc32($key) % $this->segments;
}
public function set(string $key, $value)
{
$segment = $this->getSegment($key);
$this->locks[$segment]->lock();
$this->data[$segment][$key] = $value;
$this->locks[$segment]->unlock();
}
public function get(string $key)
{
$segment = $this->getSegment($key);
$this->locks[$segment]->lock();
$value = $this->data[$segment][$key] ?? null;
$this->locks[$segment]->unlock();
return $value;
}
}
// 优势:
// 16个锁可以支持16个并发操作
// 减少锁冲突概率:1/16
5.3 读写分离
/**
* 读写锁:读不互斥,写互斥
*/
class OptimizedCache
{
private $rwlock;
private $data = [];
public function __construct()
{
$this->rwlock = new RWLock();
}
// 读操作:可并发
public function get(string $key)
{
return $this->rwlock->read(function () use ($key) {
return $this->data[$key] ?? null;
});
}
// 写操作:独占
public function set(string $key, $value)
{
$this->rwlock->write(function () use ($key, $value) {
$this->data[$key] = $value;
});
}
}
// 性能提升:
// 读多写少场景:提升10-100倍
5.4 无锁编程
/**
* CAS (Compare And Swap) 无锁操作
*/
class AtomicCounter
{
private $atomic;
public function __construct(int $initial = 0)
{
$this->atomic = new \Swoole\Atomic($initial);
}
// 原子加法(无锁)
public function increment(int $delta = 1): int
{
return $this->atomic->add($delta);
}
// CAS操作
public function compareAndSet(int $expect, int $update): bool
{
return $this->atomic->cmpset($expect, $update);
}
public function get(): int
{
return $this->atomic->get();
}
}
// 使用
$counter = new AtomicCounter(0);
// 1000个协程并发累加
for ($i = 0; $i < 1000; $i++) {
go(function () use ($counter) {
$counter->increment();
});
}
// 性能:比互斥锁快10-100倍
6. 死锁问题
6.1 死锁检测
/**
* 死锁检测器
*/
class DeadlockDetector
{
private $waitGraph = []; // 等待图
/**
* 添加等待关系
*/
public function addWait(int $txId, int $holderId)
{
$this->waitGraph[$txId][] = $holderId;
}
/**
* 检测死锁(查找环)
*/
public function detectDeadlock(): bool
{
foreach ($this->waitGraph as $txId => $holders) {
$visited = [];
if ($this->hasCycle($txId, $visited)) {
return true; // 检测到死锁
}
}
return false;
}
private function hasCycle(int $node, array &$visited): bool
{
if (isset($visited[$node])) {
return true; // 找到环
}
$visited[$node] = true;
if (isset($this->waitGraph[$node])) {
foreach ($this->waitGraph[$node] as $next) {
if ($this->hasCycle($next, $visited)) {
return true;
}
}
}
unset($visited[$node]);
return false;
}
}
// 示例:
// TX1等待TX2: detector->addWait(1, 2)
// TX2等待TX3: detector->addWait(2, 3)
// TX3等待TX1: detector->addWait(3, 1) ← 形成环
// detector->detectDeadlock() → true
6.2 死锁预防
/**
* 按顺序获取锁(破坏循环等待)
*/
class OrderedLock
{
private $locks = [];
public function acquire(array $resourceIds)
{
// 排序资源ID
sort($resourceIds);
// 按顺序获取锁
foreach ($resourceIds as $id) {
$this->locks[$id]->lock();
}
}
public function release(array $resourceIds)
{
// 按相反顺序释放
rsort($resourceIds);
foreach ($resourceIds as $id) {
$this->locks[$id]->unlock();
}
}
}
// 转账示例
public function transfer(int $fromId, int $toId, int $amount)
{
$resourceIds = [$fromId, $toId];
$this->acquire($resourceIds);
try {
// 转账逻辑
} finally {
$this->release($resourceIds);
}
}
// 保证:所有事务都按ID顺序获取锁
// 结果:不会出现循环等待
7. 实战案例
7.1 商品库存扣减
/**
* 高并发库存扣减
*/
class InventoryService
{
/**
* 方案1:悲观锁
*/
public function decreaseWithPessimisticLock(int $productId, int $quantity): bool
{
DB::beginTransaction();
try {
// 加排他锁
$product = DB::table('products')
->where('id', $productId)
->lockForUpdate()
->first();
if ($product->stock < $quantity) {
throw new Exception('库存不足');
}
// 扣减库存
DB::table('products')
->where('id', $productId)
->decrement('stock', $quantity);
DB::commit();
return true;
} catch (Exception $e) {
DB::rollBack();
return false;
}
}
/**
* 方案2:乐观锁
*/
public function decreaseWithOptimisticLock(int $productId, int $quantity, int $maxRetry = 3): bool
{
for ($i = 0; $i < $maxRetry; $i++) {
$product = DB::table('products')
->where('id', $productId)
->first();
if ($product->stock < $quantity) {
return false;
}
// CAS更新
$affected = DB::table('products')
->where('id', $productId)
->where('stock', $product->stock) // 版本检查
->where('stock', '>=', $quantity)
->decrement('stock', $quantity);
if ($affected > 0) {
return true; // 成功
}
// 失败,重试
usleep(rand(10000, 50000)); // 随机退避
}
return false;
}
/**
* 方案3:Redis原子操作
*/
public function decreaseWithRedis(int $productId, int $quantity): bool
{
$key = "product:stock:{$productId}";
// Lua脚本保证原子性
$script = <<<LUA
local stock = redis.call('get', KEYS[1])
if not stock then
return -1
end
stock = tonumber(stock)
if stock < tonumber(ARGV[1]) then
return 0
end
redis.call('decrby', KEYS[1], ARGV[1])
return 1
LUA;
$result = $this->redis->eval($script, [$key, $quantity], 1);
return $result === 1;
}
}
7.2 分布式ID生成
/**
* 基于Redis的分布式ID生成器(号段模式)
*/
class DistributedIdGenerator
{
private $redis;
private $segmentSize = 1000; // 每次获取1000个ID
private $currentId;
private $maxId;
public function nextId(string $bizType): int
{
// 检查当前号段是否用完
if ($this->currentId >= $this->maxId) {
$this->getNewSegment($bizType);
}
return ++$this->currentId;
}
private function getNewSegment(string $bizType)
{
$key = "id_generator:{$bizType}";
// 分布式锁
$lock = new RedisLock($this->redis);
$lock->acquire($key, 10);
try {
// 获取当前最大ID
$currentMax = (int) $this->redis->get($key) ?: 0;
// 预分配号段
$this->currentId = $currentMax;
$this->maxId = $currentMax + $this->segmentSize;
// 更新Redis
$this->redis->set($key, $this->maxId);
} finally {
$lock->release();
}
}
}
// 优势:
// - 减少Redis访问次数(批量获取)
// - 高性能(本地缓存)
// - 分布式环境下不重复
7.3 幂等性保证
/**
* 接口幂等性实现
*/
class IdempotentService
{
/**
* 基于分布式锁的幂等性
*/
public function createOrder(string $requestId, array $orderData): array
{
$lockKey = "idempotent:{$requestId}";
$cacheKey = "idempotent:result:{$requestId}";
// 检查缓存
$cachedResult = $this->redis->get($cacheKey);
if ($cachedResult) {
return json_decode($cachedResult, true);
}
// 获取分布式锁
$lock = new RedisLock($this->redis);
if (!$lock->acquire($lockKey, 30)) {
throw new Exception('请勿重复提交');
}
try {
// 再次检查缓存(双重检查)
$cachedResult = $this->redis->get($cacheKey);
if ($cachedResult) {
return json_decode($cachedResult, true);
}
// 创建订单
$result = $this->doCreateOrder($orderData);
// 缓存结果(24小时)
$this->redis->setex($cacheKey, 86400, json_encode($result));
return $result;
} finally {
$lock->release();
}
}
}
8. 面试高频题
Q1: 什么是死锁?如何避免?
A: 见6节
核心要点:
- 4个必要条件:互斥、持有并等待、不可剥夺、循环等待
- 预防方法:破坏任一条件
- 最常用:按固定顺序获取锁
Q2: 乐观锁和悲观锁的区别?
A:
| 维度 | 悲观锁 | 乐观锁 |
|---|---|---|
| 锁时机 | 操作前加锁 | 操作后检查 |
| 实现方式 | 数据库锁(FOR UPDATE) | 版本号/CAS |
| 并发性能 | 低(串行) | 高(并发) |
| 冲突处理 | 等待 | 重试 |
| 适用场景 | 写多、冲突频繁 | 读多、冲突少 |
示例:
-- 悲观锁
BEGIN;
SELECT * FROM products WHERE id = 1 FOR UPDATE; -- 加锁
UPDATE products SET stock = stock - 1 WHERE id = 1;
COMMIT;
-- 乐观锁
-- 1. 读取
SELECT stock, version FROM products WHERE id = 1;
-- 2. 更新(比较版本号)
UPDATE products
SET stock = stock - 1, version = version + 1
WHERE id = 1 AND version = 10; -- 版本号未变才更新
Q3: Redis分布式锁有什么问题?如何解决?
A:
问题1:超时释放
客户端A获取锁,超时10秒
客户端A执行业务,耗时15秒
10秒后锁自动释放
客户端B获取锁
客户端A完成,释放锁(误删B的锁)
解决:使用唯一标识
// 加锁时保存唯一值
$lockValue = uniqid();
$redis->set($lockKey, $lockValue, ['NX', 'EX' => 10]);
// 释放时比较值
$script = "if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) end";
$redis->eval($script, [$lockKey, $lockValue], 1);
问题2:单点故障
Redis宕机 → 锁不可用
主从切换 → 锁丢失
解决:RedLock算法(多个Redis实例)
问题3:时钟漂移
锁设置10秒过期
系统时钟快进20秒
锁立即过期
解决:使用单调时钟,或ZooKeeper
Q4: MySQL的行锁是怎么实现的?
A:
实现机制:通过索引实现
-- ✅ 命中索引:行锁
SELECT * FROM users WHERE id = 1 FOR UPDATE; -- 只锁id=1
-- ❌ 未命中索引:表锁
SELECT * FROM users WHERE name = 'John' FOR UPDATE; -- 锁整表
-- 解决:添加索引
CREATE INDEX idx_name ON users(name);
锁类型:
- Record Lock:锁单行
- Gap Lock:锁间隙(防幻读)
- Next-Key Lock:Record + Gap
示例:
-- 现有记录:id = 1, 5, 10
SELECT * FROM users WHERE id >= 5 FOR UPDATE;
-- 锁定:
-- Record: id=5, id=10
-- Gap: (5,10), (10,+∞)
-- 阻止插入:id=6,7,8,9,11,12...
Q5: 如何设计一个高性能的分布式锁?
A:
设计要点:
- 互斥性
// 使用NX保证
$redis->set($key, $value, ['NX', 'EX' => $ttl]);
- 防止死锁
// 设置过期时间
['EX' => 10]
// 自动续期(看门狗)
go(function () {
while ($holding) {
sleep($ttl / 3);
$redis->expire($key, $ttl);
}
});
- 防止误删
// 唯一标识
$value = uniqid();
// Lua脚本原子释放
$script = "if redis.call('get',KEYS[1])==ARGV[1] then ...";
- 高可用
// RedLock:多个Redis实例
// 超过半数成功才算获取锁
- 高性能
// 号段模式:批量获取
$ids = range($currentId, $currentId + 1000);
// 减少锁竞争:分段锁
$segment = crc32($key) % 16;
总结
核心要点
- 锁分类:表锁/行锁、悲观/乐观、本地/分布式
- 数据库锁:InnoDB行锁、MVCC、死锁检测
- 分布式锁:Redis、ZooKeeper、数据库实现
- 性能优化:减小粒度、锁分段、读写分离
- 死锁预防:按顺序获取、超时机制
面试建议
- 理解各种锁的原理和适用场景
- 掌握分布式锁的常见实现方式
- 能够分析死锁问题并提出解决方案
- 准备实际项目中的锁使用案例
推荐资源:
- 《高性能MySQL》
- 《Redis设计与实现》
- 《分布式系统原理与范型》