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)应该实现自动恢复机制。

相关推荐
leeyi2 天前
Checkpoint 机制:Agent 怎么在断电后接着跑
redis·aigc·agent
云技纵横3 天前
一个 @Async 让循环依赖暴雷:Spring 代理的暗坑
redis
犯困蛋挞yy4 天前
用Claude快速解决Redis代码报错反复无解的问题
redis
两个人的幸福8 天前
Windows 桌面应用自研 PHP 队列(下):完整代码与六大工程化优化
php
用户31693538118310 天前
Java连接Redis
redis
BingoGo10 天前
PHP 泛型之殇 泛型 RFC 提案被拒绝
后端·php
JaguarJack10 天前
PHP 泛型之殇 泛型 RFC 提案被拒绝
后端·php
用户30745969820711 天前
PHP 扩展——从入门到理解
php
鹏仔先生12 天前
拷贝漫画APP下载页PHP程序,后台带免费AI写作
php
小小工匠12 天前
Redis - 事务机制:能实现 ACID 属性吗
数据结构·redis·性能优化·并发·持久化