Appearance
乐观锁开发指南
本文档介绍 DSPlatform 项目中乐观锁的使用方法。
概述
乐观锁通过版本号机制确保数据一致性,适用于读多写少的场景。项目采用分布式锁 + 乐观锁的双重保护机制,既减少并发冲突,又确保数据一致性。
实现原理
- 版本号字段:数据表需要
version字段(int类型,默认0) - 读取时获取版本号:读取数据时同时获取
version - 更新时验证版本号:更新时在 WHERE 条件中检查
version是否匹配 - 版本冲突重试:如果更新失败,说明版本冲突,自动重试
使用示例
基本用法
php
class UserBalanceService
{
public function modifyUserBalance($data)
{
// 第一层:分布式锁(减少并发冲突)
$lockKey = sprintf(LockKeyManager::LOCK_USER_BALANCE_KEY, $data['user_id']);
$lockValue = KvManager::lock()->acquire($lockKey, 5);
if (!$lockValue) {
throw new SystemException('系统繁忙,请稍后重试');
}
try {
// 第二层:乐观锁(确保数据一致性)
$maxRetries = 3;
$retryCount = 0;
while ($retryCount < $maxRetries) {
$result = $this->tryModifyUserBalance($data);
if ($result) {
return true;
}
$retryCount++;
// 指数退避延迟
if ($retryCount < $maxRetries) {
ds_retry_delay($retryCount);
}
}
throw new SystemException('版本冲突,已重试' . $maxRetries . '次', 409);
} finally {
KvManager::lock()->release($lockValue, $lockKey);
}
}
private function tryModifyUserBalance($data)
{
// 读取数据(包含 version)
$user_info = (new UserDao())->getUserInfoById(
$data['user_id'],
'id,balance,version'
);
if (empty($user_info)) {
throw new CommonException('用户不存在');
}
// 计算新余额
$after_balance = $user_info['balance'] + $data['change_amount'];
// 条件更新(验证 version)
$affectedRows = (new UserDao())->updateUser(
[
['id', '=', $data['user_id']],
['balance', '=', $user_info['balance']], // 双重验证
['version', '=', $user_info['version']] // 乐观锁
],
[
'balance' => $after_balance,
'version' => $user_info['version'] + 1 // 版本号+1
]
);
// 更新失败返回 false,触发重试
return $affectedRows > 0;
}
}完整示例
php
class UserPointsService
{
public function modifyUserPoints($data)
{
// 分布式锁
$lockKey = sprintf(LockKeyManager::LOCK_USER_POINTS_KEY, $data['user_id']);
$lockValue = KvManager::lock()->acquire($lockKey, 5);
if (!$lockValue) {
throw new SystemException('积分更新失败,系统繁忙,请稍后重试');
}
try {
// 乐观锁重试机制
$maxRetries = 3;
$retryCount = 0;
while ($retryCount < $maxRetries) {
$result = $this->tryModifyUserPoints($data);
if ($result) {
return true;
}
$retryCount++;
if ($retryCount < $maxRetries) {
ds_retry_delay($retryCount); // 指数退避
}
}
throw new SystemException('积分更新失败,版本冲突,已重试' . $maxRetries . '次', 409);
} finally {
KvManager::lock()->release($lockValue, $lockKey);
}
}
private function tryModifyUserPoints($data)
{
// 读取(包含 version)
$user_info = (new UserDao())->getUserInfoById(
$data['user_id'],
'id,points,version'
);
if (empty($user_info)) {
throw new CommonException('用户不存在');
}
// 业务校验
if ($data['change_mode'] == UserPointsEnum::MODE_DECREASE) {
if ($user_info['points'] < $data['change_num']) {
throw new CommonException('积分不足');
}
}
// 计算新积分
$after_points = $data['change_mode'] == UserPointsEnum::MODE_INCREASE
? $user_info['points'] + $data['change_num']
: $user_info['points'] - $data['change_num'];
// 条件更新(乐观锁)
$affectedRows = (new UserDao())->updateUser(
[
['id', '=', $data['user_id']],
['points', '=', $user_info['points']],
['version', '=', $user_info['version']]
],
[
'points' => $after_points,
'version' => $user_info['version'] + 1
]
);
return $affectedRows > 0;
}
}注意事项
- 必须配合分布式锁使用,减少版本冲突概率
- 设置合理的重试次数,通常 3 次足够
- 使用指数退避延迟,避免惊群效应
- 数据库表必须有
version字段,默认值为 0 - WHERE 条件包含业务字段和版本号,双重验证更安全
相关链接
最后更新:2024-12-20
维护者:DSPlatform技术团队(德尚网络)
获取帮助
如果您在使用过程中遇到问题,可以通过以下方式获取帮助:
- 官方网站:https://www.csdeshang.com
- 电话咨询:15364080101(微信同号)
- QQ咨询:858761000
- 邮箱咨询:858761000@qq.com
- 工作时间:工作日 9:00-18:00
- 微信咨询:扫码添加微信

版权所有 © 2014-至今 德尚网络