当 Swoole 服务重启后出现数据丢失,先别急着拍桌子——这不是“重启”本身的锅,而是设计上缺少对关键状态的容错机制。比如用户 Session 凭空消失、数据库事务因连接池失效而中断、内存表里的缓存数据蒸发、Task 进程的中间结果没落库……这些问题的本质,都是持久化或连接健康检测没做到位。

## 确认数据丢失范围与源头
先搞清楚丢的是哪一类数据:用户 Session?数据库连接中未提交的事务?SwooleTable 里的运行时缓存?还是 Task 进程里未落库的中间结果?
查日志定位时间点,这一步不能省:
```bash
grep -i "worker exit|task exit|coroutine max" /var/log/swoole/error.log
```
重点盯住 **【manager进程退出】** 或者 `task_worker_num` 耗尽的报错。如果日志中出现 `PHP Warning: SwooleTable::set(): table is full`,说明 SwooleTable 容量不够,写入失败且没有 fallback 机制——这部分数据必然是丢了的。
## 恢复已丢失的Session数据
**方法一:从Redis重建Session**
前提是你已经用 Redis 存储 Session。登录 Redis 执行 `keys "session:*"`,看看存活键还在不在。如果键存在但 PHP 端读不到,大概率是 `session_name` 设置错了,或者 cookie domain 不匹配。
**方法二:强制刷新客户端Session ID并回填关键字段**
在请求入口处判断 `$_SESSION` 为空时,尝试从 JWT token 或数据库用户表中拉取基础信息,调用 `session_regenerate_id(true)` 生成新 ID,再用 `setcookie()` 同步到客户端。
> **注意:不要直接 `unserialize($_COOKIE['PHPSESSID'])` 来还原,该值已被加密且不可逆。**
## 修复数据库连接池失效问题
**第一步:确认MySQL是否真的重启过**
执行 `systemctl status mysqld` 或 `docker ps | grep mysql`。如果 MySQL 也重启了,Swoole 协程客户端持有的旧连接句柄全部失效。
**第二步:启用连接健康检测与自动重建**
在数据库操作前插入 ping 检测逻辑:
```php
if (!$mysql->connected || !$mysql->ping()) {
$mysql = new Swoole\Coroutine\MySQL();
$mysql->connect($config);
}
```
**第三步:替换原有连接池实现**
放弃那种固定数量连接的静态池。改用 `Swoole\Runtime::enableCoroutine(true)` + 每次按需 `new Swoole\Coroutine\MySQL()`,配合 `try/catch` 捕获连接异常并重试3次——这才是协程环境下的正确姿势。
## 防止SwooleTable数据丢失
SwooleTable 是内存数据结构,不持久、无备份、满了就丢弃。所以:
1. 立即停止向 table 写入非关键数据。
2. 把高频读写状态迁移到 Redis,例如设备在线状态用 `SET device:1001 online EX 60`,利用 EXPIRE 自动清理。
3. 如果必须用 Table 存临时索引,启动时从 Redis 预热:
```php
$table->foreach(function ($key, $value) use ($redis) {
$table->set($key, ['data' => $redis->get("cache:$key")]);
});
```
> **警告:不要在 `worker_start` 回调中执行耗时的 Redis 批量读取,会导致 worker 启动卡死。**
## 重建Task进程未完成任务
**方法1:启用任务队列兜底**
所有发往 taskworker 的任务,先 push 到 Redis List(如 `task:pending`),taskworker 消费成功后再 `LREM` 删除。服务重启后,用定时器扫描 list 长度,对积压任务重新 dispatch。
**方法2:记录任务指纹+幂等标记**
每个任务携带唯一 `trace_id`,入库前先 `SELECT COUNT(*) FROM task_log WHERE trace_id = ?`。存在则跳过,不存在则 INSERT + 执行业务逻辑。这样即使任务重复执行,也不会造成数据不一致。
**方法3:利用 MySQL binlog 或 Canal 监听变更,补偿缺失的异步动作。**
这一层可以作为保底手段——当上游重试也来不及的时候,从数据库日志里找回丢失的操作。
本文转载于:https://www.php.cn/faq/2813836.html 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。