Redis evalsha 可能返回的错误类型

RedisService::center()->evalsha($hash, 1, ...$params);

这行代码执行时可能返回的报错信息包括:

🔍 Redis evalsha 可能返回的错误类型

1. NOSCRIPT 错误(最常见)

复制代码
NOSCRIPT No matching script. Please use EVAL.

原因:Redis服务器上没有缓存这个SHA1哈希对应的Lua脚本。

2. Lua脚本执行错误

复制代码
ERR Error running script (call to f_xxxxxxxxx): @user_script:行号: 错误描述

示例

复制代码
ERR Error running script (call to f_a1b2c3d4): @user_script:5: Attempt to call field 'nonexistent' (a nil value)

3. 参数数量错误

复制代码
ERR wrong number of arguments for 'evalsha' command

原因:传递给evalsha的参数数量不正确。

4. 键名参数错误

复制代码
ERR value is not an integer or out of range

原因:第二个参数(keys的数量)不是整数或范围不正确。

5. 数据类型错误

复制代码
ERR Error running script (call to f_xxxxxxxxx): @user_script:行号: user_script:行号: 数据类型错误

6. 内存不足错误

复制代码
OOM command not allowed when used memory > 'maxmemory'

7. 连接错误

复制代码
Connection refused
Connection timeout

📊 针对您代码的具体错误场景

场景1:$hash无效

复制代码
// 如果 $hash 为空、null 或无效的SHA1
$hash = null;  // 或 ''
$result = RedisService::center()->evalsha($hash, 1, ...$params);
// 错误:-ERR value is not a valid SHA-1 hex digest string

场景2:参数数量不匹配

复制代码
// Lua脚本期望1个key,但传入了错误的参数
$params = ['key1', 'value1', 'value2'];  // 参数数量不对
$result = RedisService::center()->evalsha($hash, 1, ...$params);
// 可能错误:ERR wrong number of arguments

场景3:Redis连接问题

复制代码
// Redis服务不可用
$result = RedisService::center()->evalsha($hash, 1, ...$params);
// 错误:Predis\Connection\ConnectionException
// 或:Connection refused

场景4:脚本执行超时

复制代码
// Lua脚本执行时间过长
$result = RedisService::center()->evalsha($hash, 1, ...$params);
// 错误:BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.

🔧 完整的错误处理改进

php 复制代码
public static function exec($command, $params)
{
    $hash = self::loadScript($command);
    
    // 验证hash有效性
    if (empty($hash) || !preg_match('/^[a-f0-9]{40}$/i', $hash)) {
        Log::error('Invalid SHA1 hash for Lua script', [
            'command' => $command,
            'hash' => $hash,
            'params' => $params
        ]);
        
        return [
            'current_amount' => 0,
            'error' => 'INVALID_SCRIPT_HASH',
            'message' => 'Script hash is invalid'
        ];
    }
    
    // 验证参数
    if (!is_array($params) || empty($params)) {
        Log::warning('Empty parameters for Lua script', [
            'command' => $command,
            'hash' => $hash
        ]);
    }
    
    try {
        $begin_time = microtime(true);
        
        // 执行evalsha
        $result = RedisService::center()->evalsha($hash, 1, ...$params);
        
        self::timeCost($command, $params, $begin_time);
        
        // 验证返回结果
        if (!is_array($result) || count($result) < 8) {
            Log::error('Invalid Lua script return format', [
                'command' => $command,
                'hash' => $hash,
                'result' => $result,
                'result_type' => gettype($result)
            ]);
            
            return [
                'current_amount' => 0,
                'error' => 'INVALID_RESULT_FORMAT',
                'message' => 'Lua script returned invalid format'
            ];
        }
        
        // 解析返回的数组
        return [
            'current_amount' => $result[0] ?? 0,
            'lower_limit' => $result[1] ?? 0,
            'upper_limit' => $result[2] ?? 0,
            'round' => $result[3] ?? 0,
            'round_changed' => (bool)($result[4] ?? 0),
            'actual_increment' => $result[5] ?? 0,
            'operate_state' => $result[6] ?? 0,
            'current_timestamp' => $result[7] ?? time(),
            'success' => true
        ];
        
    } catch (\Predis\Response\ServerException $e) {
        // Redis服务器错误(Lua脚本错误、NOSCRIPT等)
        return self::parseRedisServerException($command, $params, $e);
        
    } catch (\Predis\Connection\ConnectionException $e) {
        // 连接错误
        return self::parseConnectionException($command, $params, $e);
        
    } catch (\Exception $e) {
        // 其他异常
        return self::parseGenericException($command, $params, $e);
    }
}

/**
 * 解析Redis服务器异常
 */
private static function parseRedisServerException($command, $params, $exception)
{
    $message = $exception->getMessage();
    
    // 分类处理不同的Redis错误
    if (strpos($message, 'NOSCRIPT') !== false) {
        Log::warning('Redis script not loaded', [
            'command' => $command,
            'error' => 'NOSCRIPT',
            'message' => $message
        ]);
        
        // 清除缓存,触发重新加载
        self::clearScriptCache($command);
        
        return [
            'current_amount' => 0,
            'error' => 'NOSCRIPT',
            'message' => 'Script not loaded in Redis',
            'recoverable' => true
        ];
        
    } elseif (strpos($message, 'user_script:') !== false) {
        // Lua脚本执行错误
        Log::error('Lua script execution error', [
            'command' => $command,
            'error' => 'LUA_EXECUTION',
            'message' => $message
        ]);
        
        return [
            'current_amount' => 0,
            'error' => 'LUA_EXECUTION_ERROR',
            'message' => 'Script execution failed'
        ];
        
    } elseif (strpos($message, 'wrong number of arguments') !== false) {
        // 参数数量错误
        Log::error('Wrong number of arguments for Lua script', [
            'command' => $command,
            'params' => $params,
            'error' => 'WRONG_ARG_COUNT',
            'message' => $message
        ]);
        
        return [
            'current_amount' => 0,
            'error' => 'INVALID_ARGUMENTS',
            'message' => 'Wrong number of arguments'
        ];
        
    } elseif (strpos($message, 'OOM') !== false) {
        // 内存不足
        Log::critical('Redis out of memory', [
            'command' => $command,
            'error' => 'OUT_OF_MEMORY',
            'message' => $message
        ]);
        
        return [
            'current_amount' => 0,
            'error' => 'REDIS_OOM',
            'message' => 'Redis out of memory'
        ];
    }
    
    // 其他服务器错误
    return [
        'current_amount' => 0,
        'error' => 'REDIS_SERVER_ERROR',
        'message' => $message
    ];
}

/**
 * 解析连接异常
 */
private static function parseConnectionException($command, $params, $exception)
{
    $message = $exception->getMessage();
    
    if (strpos($message, 'Connection refused') !== false) {
        Log::critical('Redis connection refused', [
            'command' => $command,
            'error' => 'CONNECTION_REFUSED'
        ]);
        
    } elseif (strpos($message, 'Connection timeout') !== false) {
        Log::warning('Redis connection timeout', [
            'command' => $command,
            'error' => 'CONNECTION_TIMEOUT'
        ]);
    }
    
    return [
        'current_amount' => 0,
        'error' => 'CONNECTION_ERROR',
        'message' => 'Redis connection failed',
        'recoverable' => true
    ];
}

📈 错误分类和处理策略

错误类型 示例消息 严重程度 处理策略 是否可恢复
NOSCRIPT NOSCRIPT No matching script 清除缓存,重新加载脚本 ✅ 是
Lua执行错误 @user_script:5: nil value 记录日志,告警,修复脚本 ❌ 否
参数错误 wrong number of arguments 验证参数,修复调用 ✅ 是
连接错误 Connection refused 严重 重试,检查Redis服务 ✅ 是
内存错误 OOM command not allowed 严重 扩容,清理内存 ⚠️ 可能
超时错误 BUSY Redis is busy 增加超时时间,优化脚本 ✅ 是

🎯 针对您代码的建议

1. 添加参数验证

复制代码
// 在执行前验证参数
if (count($params) < 2) {
    throw new \InvalidArgumentException('Insufficient parameters for Lua script');
}

2. 实现重试机制

php 复制代码
public static function execWithRetry($command, $params, $maxRetries = 2)
{
    $attempts = 0;
    $lastError = null;
    
    while ($attempts <= $maxRetries) {
        try {
            return self::exec($command, $params);
        } catch (\Exception $e) {
            $lastError = $e;
            $attempts++;
            
            // 只有可恢复的错误才重试
            if (self::isRecoverableError($e)) {
                usleep(100000 * $attempts); // 指数退避
                continue;
            }
            
            break;
        }
    }
    
    return self::parseException($command, $params, $lastError->getMessage());
}

3. 添加监控指标

复制代码
// 记录错误统计
private static function recordErrorMetrics($errorType)
{
    // 使用StatsD、Prometheus等记录
    Metrics::increment("redis.lua.errors.{$errorType}");
}

💡 总结

evalsha可能返回的错误主要包括:

  1. 脚本缓存错误:NOSCRIPT(最常见)

  2. 脚本执行错误:Lua语法或运行时错误

  3. 参数错误:参数数量或类型不正确

  4. 连接错误:Redis服务不可用

  5. 资源错误:内存不足、执行超时

在您的代码中,这些错误会被 try-catch捕获,然后调用 parseException方法处理。建议根据错误类型实现不同的处理策略,特别是对于可恢复的错误(如NOSCRIPT)应该实现自动恢复机制。

相关推荐
祁白_1 小时前
PHP回调函数
web安全·php·ctf·代码审计·writeup
我是一颗柠檬2 小时前
【Redis】Redis面试高频考点汇总Day15(2026年)
数据库·redis·缓存·面试
姚愚谦2 小时前
Redis源码阅读1-SDS
redis
着迷不白2 小时前
七、Linux网络管理
服务器·网络·php
小森林之主2 小时前
凌晨3点的闹钟:分布式定时任务设计实战
java·redis·任务调度·cron·分布式定时任务
金融支付架构实战指南2 小时前
秒杀&支付订单异步落地|Redis Stream 可靠队列实战
数据库·redis·缓存·stream·秒杀
Ze3G90nYt3 小时前
Redis 分布式锁进阶第一百二十篇
数据库·redis·分布式
隔窗听雨眠3 小时前
VMware迁移上云的十个关键关卡
开发语言·php·vmware
无涯大者3 小时前
php中redis的简单示例学习
redis·学习·php