本文目录导读:

在 PHP 项目中,锁的粒度控制是提升并发性能的关键。粒度越细(锁定代码范围越小),并发能力越强,但实现复杂度越高。 以下是具体的细化策略和实践方法:
核心原则:只锁"临界区"
// ❌ 错误:锁定整个方法
function processOrder($orderId) {
$lock = new Lock('order_'.$orderId);
$lock->acquire();
// 大量非共享操作
$this->validateUser();
$this->checkInventory();
$this->calculateDiscount();
// 真正需要锁的操作
$this->updateOrderStatus();
$this->deductStock();
$lock->release();
}
// ✅ 正确:只锁定修改共享资源的部分
function processOrder($orderId) {
// 非共享操作放在锁外
$user = $this->validateUser();
$inventory = $this->checkInventory($orderId);
$discount = $this->calculateDiscount($user);
// 仅锁定更新操作
$lock = new Lock('order_'.$orderId);
$lock->acquire();
try {
$this->updateOrderStatus($orderId, 'processing');
$this->deductStock($orderId, $inventory);
} finally {
$lock->release();
}
}
关键路径分离法
将单个大锁拆分为多个小锁,实现"读写分离":
// 使用读写锁(需要底层支持,如 Redis RedLock 扩展)
class OrderInventoryManager {
private $readLocks = [];
private $writeLocks = [];
public function checkAndReduceStock($productId, $quantity) {
// 读阶段:使用读锁(可共享)
$readLock = $this->getReadLock('stock_'.$productId);
$readLock->acquire();
$currentStock = $this->redis->get('stock:'.$productId);
$readLock->release();
// 业务逻辑判断(无锁)
if ($currentStock < $quantity) {
throw new \Exception('库存不足');
}
// 写阶段:使用写锁(互斥)
$writeLock = $this->getWriteLock('stock_'.$productId);
$writeLock->acquire();
try {
// 再次检查(防止并发修改)
$currentStock = $this->redis->get('stock:'.$productId);
if ($currentStock >= $quantity) {
$this->redis->decrBy('stock:'.$productId, $quantity);
return true;
}
return false;
} finally {
$writeLock->release();
}
}
}
资源级锁定(最细粒度)
为每个独立资源创建独立锁键:
class FineGrainedLockManager {
public function updateUserBalance($userId, $amount) {
// 用户级别锁:不同用户互不影响
$lockKey = 'lock:user:'.$userId;
$lock = new RedisLock($lockKey, 3); // 3秒超时
$lock->acquire();
try {
$balance = $this->getBalance($userId);
$this->setBalance($userId, $balance + $amount);
$this->logTransaction($userId, $amount);
} finally {
$lock->release();
}
}
public function updateOrderItemQuantity($orderId, $itemId, $newQty) {
// 订单+商品级别锁(最细)
$lockKey = 'lock:order:'.$orderId.':item:'.$itemId;
$lock = new RedisLock($lockKey, 5);
$lock->acquire();
try {
// 仅修改这个订单项
$this->updateItem($orderId, $itemId, $newQty);
} finally {
$lock->release();
}
}
}
乐观锁(无锁/极细粒度)
使用版本号或条件更新,避免显式加锁:
// 使用 MySQL 乐观锁(推荐)
class OptimisticLockExample {
public function updateWithVersion($orderId) {
// 1. 读取当前数据及版本号
$order = $this->db->fetchOne(
"SELECT * FROM orders WHERE id = ?", [$orderId]
);
$currentVersion = $order['version'];
// 2. 业务处理(无锁)
$newStatus = $this->calculateNewStatus($order);
// 3. 使用版本号更新(原子操作)
$affected = $this->db->execute(
"UPDATE orders SET status = ?, version = version + 1
WHERE id = ? AND version = ?",
[$newStatus, $orderId, $currentVersion]
);
if ($affected === 0) {
throw new \Exception('并发冲突,请重试');
}
}
}
// 使用 Redis 原子操作(更细粒度)
class RedisAtomicExample {
public function decrementStock($productId, $quantity) {
$stockKey = 'stock:'.$productId;
// 原子检查并减少(一条命令完成)
$luaScript = <<<'LUA'
local stock = redis.call('GET', KEYS[1])
if stock and tonumber(stock) >= tonumber(ARGV[1]) then
return redis.call('DECRBY', KEYS[1], ARGV[1])
end
return -1
LUA;
$result = $this->redis->eval($luaScript, [$stockKey], [$quantity]);
return $result >= 0;
}
}
分片锁(分散热点)
针对高频访问资源,使用哈希分片:
class ShardedLockManager {
private $shardCount = 128; // 分片数
public function lockUser($userId) {
// 计算分片编号
$shardId = crc32("user_{$userId}") % $this->shardCount;
$lockKey = "lock:shard:{$shardId}";
$lock = new RedisLock($lockKey, 2);
$lock->acquire();
return $lock;
}
}
// 使用示例
$shardManager = new ShardedLockManager();
foreach ($userIds as $userId) {
$lock = $shardManager->lockUser($userId);
// 不同用户大概率映射到不同分片,实现细粒度并发
}
锁超时与降级
为锁设置合理超时,避免长时间占用:
class SmartLock {
private $lockTimeout = 2; // 秒
private $retryDelay = 100; // 毫秒
private $maxRetries = 3;
public function acquire($key, $callback) {
// 尝试获取锁
$lock = new RedisLock($key, $this->lockTimeout);
if (!$lock->acquire()) {
// 锁超时降级:执行降级逻辑
return $this->fallbackExecute($callback);
}
try {
// 设置更短的内部超时
$start = microtime(true);
$result = $callback();
$elapsed = microtime(true) - $start;
if ($elapsed > $this->lockTimeout * 0.8) {
// 超过锁有效期的80%,记录警告
$this->logger->warning("Lock too long: ".$key);
}
return $result;
} finally {
$lock->release();
}
}
private function fallbackExecute($callback) {
// 降级策略:无锁执行但标记脏数据
$result = $callback();
$this->markAsDirty($result);
return $result;
}
}
粒度选择决策树
需要锁保护吗?
├── 是 → 修改共享资源吗?
│ ├── 是 → 可以接受失败重试吗?
│ │ ├── 是 → 使用乐观锁(版本号/CAS)
│ │ └── 否 → 需要原子性吗?
│ │ ├── 是 → Redis Lua 脚本/事务
│ │ └── 否 → 悲观锁(最小化临界区)
│ └── 否 → 读多写少?
│ ├── 是 → 读写锁(共享锁)
│ └── 否 → 细粒度资源锁
└── 否 → 数据一致性要求高?
├── 是 → 分布式事务(补偿锁)
└── 否 → 无锁设计(最终一致性)
实际建议
- 先无锁,再加锁:多数 PHP 应用(尤其是页面渲染)不需要锁
- 优先使用乐观锁:数据库行级锁+版本号,实现最简单、粒度最细
- Redis 分布式锁+短超时:2-3秒最合适
- 做好性能监控:如果锁争用率 > 5%,说明粒度可能过粗
- 考虑业务边界:用户级锁"通常比"全局锁"好100倍
最佳实践示例:电商下单场景,只锁定"库存扣减"和"订单状态更新"两个原子操作,而用户验证、价格计算等都放在锁外部。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播