宝塔fastadmin:配置Redis搭建SimpleLock,解决多并发问题

一、Redis环境

1、安装Redis软件

在宝塔的软件商店中搜索Redis,并进行安装

2、为php安装Redis

搜索之前安装的php版本,对您所需要的php进行redis的安装

在安装的部分可以查看redis的信息,便于后续部署

3、配置redis.php(没有创建)

在路径application/extra的路径中,如果没有就新建文件redis|.php文件

配置redis.php的文件

4、给端口6379放行

需要确保服务器的安全组是否存在6379(阿里云,腾讯云等的配置),并且在安全->系统防火墙中添加端口

5、测试是否连接成功

随便写个方法,添加链接是否成功

public function testRedis()
{
    try {
        $redis = new \think\cache\driver\Redis(config('redis')); // 配置数组名‘redis’需对应你的配置文件
        $redis->set('test_key', 'Hello Redis from FastAdmin!');
        $value = $redis->get('test_key');
        return '连接成功!值:' . $value;
    } catch (\Exception $e) {
        return '连接失败:' . $e->getMessage();
    }
}

apifox之类的软件都可以测试

二、SimpleLock基础搭建

1、文件创建

在文件路径/application/common中,创建service文件夹,并创建SimpleLock.php文件

SimpleLock类是一个生产可用的分布式锁实现

  • ✅ 实现了正确的分布式锁逻辑

  • ✅ 防止了死锁

  • ✅ 保证了锁的安全性

  • ✅ 提供了良好的用户体验(等待超时提示)

例如:

  1. 解决库存计算并发问题

  2. 数据重复插入问题

  3. 事务隔离问题(可以脏读、不可重复读、幻读)

  4. 业务逻辑一致性问题

2、代码文件

<?php
// app/common/service/SimpleLock.php
namespace app\common\service;

class SimpleLock
{
    private static $redis = null;
    
    /**
     * 获取Redis实例(带连接测试)
     */
    private static function getRedis()
    {
        if (self::$redis === null) {
            self::$redis = new \Redis();
            
            // 尝试连接Redis
            try {
                // 这里使用localhost,如果需要可以修改
                $connected = self::$redis->connect('127.0.0.1', 6379, 2.0);
                
                if (!$connected) {
                    throw new \Exception('无法连接到Redis服务器');
                }
                
                // 测试连接
                $ping = self::$redis->ping();
                if (!$ping) {
                    throw new \Exception('Redis连接测试失败');
                }
                
            } catch (\Exception $e) {
                // 记录错误并抛出
                error_log('Redis连接错误: ' . $e->getMessage());
                throw new \Exception('缓存服务暂时不可用,请稍后重试');
            }
        }
        
        return self::$redis;
    }
    
    /**
     * 分布式锁核心方法
     * @param string $lockName 锁名称
     * @param callable $callback 业务逻辑回调函数
     * @param int $waitSeconds 最大等待时间(秒)
     * @return mixed
     */
    public static function run($lockName, callable $callback, $waitSeconds = 3)
    {
        $redis = null;
        
        try {
            // 获取Redis连接
            $redis = self::getRedis();
            
            $lockKey = "lock:{$lockName}";
            $lockValue = uniqid('', true); // 生成唯一值
            
            $startTime = microtime(true);
            $maxWait = $waitSeconds * 1000000; // 微秒
            
            // 尝试获取锁
            while (true) {
                // 尝试设置锁(NX: 不存在才设置,EX: 过期时间30秒)
                $setResult = $redis->set($lockKey, $lockValue, ['nx', 'ex' => 30]);
                
                if ($setResult) {
                    // 成功获得锁
                    break;
                }
                
                // 检查是否超时
                $elapsed = (microtime(true) - $startTime) * 1000000;
                if ($elapsed > $maxWait) {
                    throw new \Exception('系统繁忙,请稍后重试');
                }
                
                // 等待一段时间再重试
                usleep(100000); // 100毫秒
            }
            
            // 执行业务逻辑
            try {
                return $callback();
            } finally {
                // 确保释放锁
                self::releaseLock($redis, $lockKey, $lockValue);
            }
            
        } catch (\Exception $e) {
            // 如果有Redis连接且在获取锁阶段出错,尝试释放锁
            if ($redis && isset($lockKey) && isset($lockValue)) {
                try {
                    self::releaseLock($redis, $lockKey, $lockValue);
                } catch (\Exception $releaseError) {
                    // 释放锁失败,记录但不影响主错误
                    error_log('释放锁失败: ' . $releaseError->getMessage());
                }
            }
            
            // 重新抛出异常
            throw $e;
        }
    }
    
    /**
     * 释放锁(原子操作)
     */
    private static function releaseLock($redis, $lockKey, $lockValue)
    {
        try {
            // 使用Lua脚本确保原子性
            $luaScript = "
                if redis.call('get', KEYS[1]) == ARGV[1] then
                    return redis.call('del', KEYS[1])
                else
                    return 0
                end
            ";
            
            $redis->eval($luaScript, [$lockKey, $lockValue], 1);
            
        } catch (\Exception $e) {
            // 记录错误但不影响主流程
            error_log('释放锁异常: ' . $e->getMessage());
        }
    }
    
    /**
     * 更简单的调用方式
     */
    public static function sync($lockName, callable $callback)
    {
        return self::run($lockName, $callback);
    }
}

3、使用方法

这里主要想要解决SimpleLock强制让事务串行执行,从而避免了所有隔离问题

// 我的代码逻辑
SimpleLock::run('upshelf_lock', function() {
    Db::startTrans();
    // 事务A的所有操作
    Db::commit();
});

效果是:

  • 事务A执行时,事务B等待

  • 事务A完成后,事务B才开始

4、完成测试

(1)创建测试方法

创建测试方法,/application/api/Demo/testRealConcurrent

public function testRealConcurrent()
{
    // 模拟多个并发请求
    $results = [];
    $logFile = '/tmp/real_concurrent.log';
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

25号底片~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值