Skip to content

乐观锁开发指南

本文档介绍 DSPlatform 项目中乐观锁的使用方法。

概述

乐观锁通过版本号机制确保数据一致性,适用于读多写少的场景。项目采用分布式锁 + 乐观锁的双重保护机制,既减少并发冲突,又确保数据一致性。

实现原理

  1. 版本号字段:数据表需要 version 字段(int类型,默认0)
  2. 读取时获取版本号:读取数据时同时获取 version
  3. 更新时验证版本号:更新时在 WHERE 条件中检查 version 是否匹配
  4. 版本冲突重试:如果更新失败,说明版本冲突,自动重试

使用示例

基本用法

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;
    }
}

注意事项

  1. 必须配合分布式锁使用,减少版本冲突概率
  2. 设置合理的重试次数,通常 3 次足够
  3. 使用指数退避延迟,避免惊群效应
  4. 数据库表必须有 version 字段,默认值为 0
  5. WHERE 条件包含业务字段和版本号,双重验证更安全

相关链接


最后更新:2024-12-20
维护者:DSPlatform技术团队(德尚网络)

获取帮助

如果您在使用过程中遇到问题,可以通过以下方式获取帮助:

  • 官方网站https://www.csdeshang.com
  • 电话咨询:15364080101(微信同号)
  • QQ咨询:858761000
  • 邮箱咨询:858761000@qq.com
  • 工作时间:工作日 9:00-18:00
  • 微信咨询:扫码添加微信
微信二维码

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