根本原因是Workerman多进程未初始化Laravel应用上下文,缺失APP_KEY绑定及encrypter服务注册,导致Cache::remember()序列化时调用encrypter->encrypt()失败;需手动构建Application实例并注册cache、encryption等必需服务。

Cache::remember 在 Workerman 多进程里报 LogicException: Invalid key supplied
根本原因是 Laravel 的 Cache::remember() 依赖应用实例($app)和其绑定的加密服务(encrypter),而 Workerman 启动后每个子进程是独立的 PHP 进程,未完整初始化 Laravel 应用上下文 —— 尤其缺失 APP_KEY 绑定或加密器未注册,导致底层 Cache::get() 或序列化时调用 encrypter->encrypt() 失败。
典型现象:单进程 php artisan tinker 里能正常跑的 Cache::remember('key', 3600, fn() => [...]),一放进 Workerman 的 onMessage 或定时器回调里就抛出该异常;日志里看不到明显堆栈,但 storage/logs/laravel.log 会记录完整错误。
- 检查
.env是否真被加载:Workerman 启动脚本(如start.php)里没调用Dotenv\Dotenv::createUnsafeImmutable(__DIR__)->load();,APP_KEY就是空的 - 确认
Illuminate\Encryption\Encrypter实例是否已绑定到容器:Workerman 进程里没走Kernel::bootstrap(),$app->make('encrypter')会失败 - 别在
onWorkerStart里只做一次require vendor/autoload.php—— 这不等于启动 Laravel,必须显式构建Application实例并完成核心服务注册
Workerman 中正确复用 Laravel Cache 的最小初始化方式
不能直接 require Laravel 启动文件,也不能依赖 app() 全局函数 —— 它在非 HTTP/CLI 上下文里不可靠。必须手动构造一个轻量但可用的 Laravel 应用实例,仅注册缓存与加密必需服务。
Laravel Cloud(cloud.laravel.com)。此技能用于处理所有 Laravel Cloud 请求,包括搜索和读取数据。只要任务涉及 Laravel Cloud,即使用此技能。
示例(放在 Workerman 的 start.php 或工作进程初始化处):
// 手动创建 Application 并绑定关键服务
$app = new Illuminate\Foundation\Application(
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);
$app->singleton('encrypter', function ($app) {
$key = $app['config']['app.key'];
if (!$key) {
throw new RuntimeException('APP_KEY not set in config or .env');
}
return new Illuminate\Encryption\Encrypter(
base64_decode($key),
$app['config']['app.cipher']
);
});
$app->configure('cache');
$app->configure('database');
$app->register(Illuminate\Cache\CacheServiceProvider::class);
$app->register(Illuminate\Database\DatabaseServiceProvider::class);
$app->boot();
// 此时 Cache::remember 才真正可用
- 务必提前加载
.env,且确保APP_KEY=base64:...格式合法(用php artisan key:generate生成) - 如果用 Redis 驱动,还需注册
Illuminate\Redis\RedisServiceProvider并配置redis键 - 避免在每次
onMessage里重复构造$app—— 开销大且可能泄露连接,应在onWorkerStart中一次性初始化并存为全局变量或静态属性
为什么不用 remember() 改用 Cache::get + Cache::put 更稳?
因为 Cache::remember() 内部做了原子性判断(先 get,为空再执行闭包、再 put),但在 Workerman 多进程+无事务缓存(如 Redis)场景下,仍存在极小概率的并发覆盖(两个进程同时发现 key 不存在,都执行闭包,后写入者覆盖前写入者)。而更关键的是:它的异常路径更复杂,一旦加密或序列化失败,整个流程中断,不如拆开控制。
- 手动控制更清晰:
$data = Cache::get($key); if ($data === null) { $data = expensiveQuery(); Cache::put($key, $data, 3600); } - 遇到加密失败时,可降级:比如
try { ... } catch (Exception $e) { Log::warning('Cache encrypt failed, fallback to raw json'); Cache::put($key, json_encode($data), 3600, ['serialize' => false]); } - 若业务允许,干脆跳过加密 —— Redis 驱动默认对值做
serialize(),不需要encrypter;可在缓存配置里设'serialize' => false并存 JSON 字符串,彻底绕过加密链路
容易被忽略的 Workerman + Laravel 缓存陷阱
最常被跳过的不是初始化问题,而是生命周期错配:Workerman 进程常驻,Laravel 的 Cache 实例却可能随请求结束被销毁(尤其用了 Request-scoped 服务时)。你初始化了,但某次 Cache::remember() 调用时拿到的其实是旧的、未绑定加密器的实例。
- 不要在
onMessage回调里临时app('cache')->remember(...)——app()返回的可能是残缺容器 - 所有缓存操作必须基于你在
onWorkerStart显式构建并保存的那个$app实例,例如:$app->make('cache')->remember(...) - Redis 连接池没配好时,多个 Worker 进程共用一个连接会导致
Connection refused或读取乱码 —— 检查config/cache.php中 Redis 的'connection' => 'default'和连接池设置(如'options' => ['prefix' => 'wm_']) - 如果用了
Cache::tags(),确认驱动支持(file 不行)、且标签名不含非法字符(空格、斜杠),否则静默失败

















