1. 项目背景与核心需求
在电商系统中,库存数据的准确性直接关系到交易成功率与用户体验。我们经常遇到这样的场景:用户下单时系统显示有货,但支付时却提示库存不足。这种问题往往源于数据库(DB)与缓存(Redis)之间的数据不一致。
我最近为某跨境电商平台开发了一个高频对账脚本,主要解决以下痛点:
- Redis库存缓存可能因网络抖动、服务重启等原因未能及时同步
- 数据库事务提交延迟导致缓存更新滞后
- 秒杀场景下原子扣减操作可能出现的极端情况
这个PHP脚本需要每分钟执行一次,比对活跃商品在MySQL和Redis中的库存数据。当差异超过预设阈值时,立即触发告警并自动修复数据。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术架构设计
2.1 整体流程图解
mermaid复制graph TD
A[启动脚本] --> B[获取活跃商品ID列表]
B --> C[遍历商品ID]
C --> D[查询MySQL库存]
C --> E[查询Redis库存]
D --> F[数据比对]
E --> F
F -->|差异>阈值| G[记录差异日志]
F -->|差异>阈值| H[自动修复数据]
F -->|正常| I[记录检查点]
2.2 关键技术选型
-
PHP版本:建议使用PHP 7.4+,主要考虑:
- 稳定的类型声明支持
- 更好的Redis扩展兼容性
- 预加载提升性能
-
Redis客户端:
php复制$redis = new Redis(); $redis->connect('127.0.0.1', 6379, 1.5); // 1.5秒超时 $redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP); -
MySQL连接池:
php复制$dbh = new PDO( 'mysql:host=localhost;dbname=inventory', 'user', 'password', [ PDO::ATTR_PERSISTENT => true, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION ] );
3. 核心实现细节
3.1 活跃商品筛选策略
php复制function getActiveItems($dbh, $lastHours = 1) {
$stmt = $dbh->prepare("
SELECT item_id
FROM items
WHERE last_order_time > DATE_SUB(NOW(), INTERVAL ? HOUR)
ORDER BY sales_volume DESC
LIMIT 1000
");
$stmt->execute([$lastHours]);
return $stmt->fetchAll(PDO::FETCH_COLUMN);
}
3.2 原子化数据比对
php复制function compareInventory($itemId, $dbh, $redis) {
// 使用事务确保数据一致性
$dbh->beginTransaction();
try {
$dbQty = $dbh->query("SELECT stock FROM inventory WHERE item_id = $itemId FOR UPDATE")->fetchColumn();
$redisQty = $redis->get("inventory:$itemId");
if (abs($dbQty - $redisQty) > $this->threshold) {
$this->logDiscrepancy($itemId, $dbQty, $redisQty);
$this->repairInventory($itemId, max($dbQty, $redisQty));
}
$dbh->commit();
} catch (Exception $e) {
$dbh->rollBack();
throw $e;
}
}
3.3 差异修复策略
php复制function repairInventory($itemId, $correctQty) {
// 先更新数据库
$this->dbh->exec("UPDATE inventory SET stock = $correctQty WHERE item_id = $itemId");
// 再更新Redis(带事务)
$this->redis->multi()
->set("inventory:$itemId", $correctQty)
->publish('inventory.fix', json_encode(['item_id'=>$itemId]))
->exec();
}
4. 性能优化要点
-
批量处理:每次处理100-200个商品,避免长事务
php复制$batchSize = 150; $itemIds = array_chunk($itemIds, $batchSize); -
连接复用:保持持久化连接
php复制$this->redis->pconnect('127.0.0.1'); -
智能休眠:根据负载动态调整
php复制$sleepTime = min(200000, 50000 * log($processedCount)); usleep($sleepTime);
5. 异常处理机制
5.1 错误分类处理
| 错误类型 | 处理方式 | 重试策略 |
|---|---|---|
| Redis超时 | 记录日志 | 指数退避 |
| MySQL死锁 | 自动重试 | 最多3次 |
| 网络中断 | 告警通知 | 停止运行 |
5.2 监控指标设计
php复制$metrics = [
'checked_items' => 0,
'fixed_items' => 0,
'avg_process_time' => 0,
'max_difference' => 0
];
6. 部署方案
6.1 Linux定时任务配置
bash复制* * * * * /usr/bin/php /path/to/script.php >> /var/log/inventory_check.log 2>&1
6.2 Docker化部署
dockerfile复制FROM php:7.4-cli
RUN pecl install redis && docker-php-ext-enable redis
COPY script.php /app/
CMD ["php", "/app/script.php"]
7. 实战经验总结
-
数据预热技巧:
php复制// 启动时预加载热点数据 $hotItems = $dbh->query("SELECT item_id FROM items WHERE is_hot = 1")->fetchAll(); foreach ($hotItems as $item) { $redis->get("inventory:".$item['item_id']); } -
灰度验证方案:
- 先对10%的商品进行比对
- 逐步扩大范围至30%、50%、100%
- 观察系统负载和修复效果
-
关键日志规范:
php复制$logMsg = sprintf( "[%s] item_id=%d db=%d redis=%d diff=%d", date('Y-m-d H:i:s'), $itemId, $dbQty, $redisQty, abs($dbQty - $redisQty) );
这个脚本在我们生产环境运行后,库存不一致问题减少了92%,平均修复时间从原来的15分钟缩短到1分钟以内。最重要的是,它帮助我们在618大促期间实现了零库存超卖事故。
