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可能返回的错误主要包括:
-
脚本缓存错误:NOSCRIPT(最常见)
-
脚本执行错误:Lua语法或运行时错误
-
参数错误:参数数量或类型不正确
-
连接错误:Redis服务不可用
-
资源错误:内存不足、执行超时
在您的代码中,这些错误会被 try-catch捕获,然后调用 parseException方法处理。建议根据错误类型实现不同的处理策略,特别是对于可恢复的错误(如NOSCRIPT)应该实现自动恢复机制。