ok
This commit is contained in:
92
plans/symfony-lock-distributed-lock-5511b223.plan.md
Normal file
92
plans/symfony-lock-distributed-lock-5511b223.plan.md
Normal file
@@ -0,0 +1,92 @@
|
||||
<!-- 5511b223-7953-4aa5-bd6e-94f0d6c56b21 -->
|
||||
---
|
||||
todos:
|
||||
- id: "dep"
|
||||
content: "在 php82 容器执行 composer require symfony/lock:7.4.9"
|
||||
status: pending
|
||||
- id: "factory"
|
||||
content: "新增 app/support/OtaLockFactory.php 封装 symfony LockFactory + RedisStore(用 support\\Redis::connection()->client())"
|
||||
status: pending
|
||||
- id: "refactor"
|
||||
content: "重构 OtaCheckLogic 锁方法:acquire(false) 重试+SERVICE_BUSY,release(),删除手写 SET/Lua 与 support\\Redis 依赖"
|
||||
status: pending
|
||||
- id: "verify"
|
||||
content: "运行 phpunit + php -l + 完成门禁,确认全绿"
|
||||
status: pending
|
||||
isProject: false
|
||||
---
|
||||
# 用 symfony/lock 7.4.9 替换 OTA 分布式锁
|
||||
|
||||
## 目标与范围
|
||||
- 仅替换 [app/logic/OtaCheckLogic.php](app/logic/OtaCheckLogic.php) 中的锁实现(`withDeviceFirmwareLock` / `acquireLock` / `releaseLock`)。
|
||||
- 业务流程(先查后建/复用、§7.5 幂等)、错误码 `SERVICE_BUSY`、TTL/重试参数语义保持不变。
|
||||
- 单测/集成测试结构不变(仍依赖 docker `redis`,跳过守卫不变)。
|
||||
|
||||
## 为什么可行
|
||||
- `php82` = 8.2.24,满足 symfony/lock 7.4(PHP ≥ 8.2)。
|
||||
- symfony `RedisStore` 接受原生 `\Redis`,可由 `support\Redis::connection()->client()` 取得(见 `vendor/illuminate/redis/Connections/Connection.php:81` 的 `client()`)。
|
||||
- symfony `Lock` 自带唯一 token + 所有者校验的 `release()`,可删除手写 Lua 释放脚本。
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Logic[OtaCheckLogic] -->|create resource| Factory[OtaLockFactory]
|
||||
Factory -->|RedisStore| RawRedis["support Redis connection client (\\Redis)"]
|
||||
Logic -->|"acquire(false) 重试 / release()"| LockObj[Symfony Lock]
|
||||
```
|
||||
|
||||
## 改动点
|
||||
|
||||
### 1. 依赖
|
||||
- 在 `php82` 容器内执行(dev-environment 规则,禁止宿主机 php/composer):
|
||||
`docker exec -w /app/www/ai-device/ota php82 composer require symfony/lock:7.4.9`
|
||||
- 结果会更新 [composer.json](composer.json) / `composer.lock` / `vendor`。
|
||||
|
||||
### 2. 新增 `app/support/OtaLockFactory.php`
|
||||
- 职责:封装 symfony 锁与 phpredis 客户端的装配,返回可用的 `LockInterface`。
|
||||
- 每次调用基于「当前」连接构建 `RedisStore`(不缓存客户端,规避 webman 连接池跨请求换连接导致的陈旧句柄):
|
||||
|
||||
```php
|
||||
use Symfony\Component\Lock\LockFactory;
|
||||
use Symfony\Component\Lock\LockInterface;
|
||||
use Symfony\Component\Lock\Store\RedisStore;
|
||||
use support\Redis;
|
||||
|
||||
final class OtaLockFactory
|
||||
{
|
||||
/** 基于当前 redis 连接创建一个带 TTL 的命名分布式锁 */
|
||||
public static function create(string $resource, float $ttlSeconds): LockInterface
|
||||
{
|
||||
$store = new RedisStore(Redis::connection()->client());
|
||||
return (new LockFactory($store))->createLock($resource, $ttlSeconds);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 重构 [app/logic/OtaCheckLogic.php](app/logic/OtaCheckLogic.php)
|
||||
- 删除 `use support\Redis;`;新增 `use Symfony\Component\Lock\LockInterface;` 与 `use app\support\OtaLockFactory;`。
|
||||
- 保留常量 `LOCK_KEY_PREFIX` / `LOCK_TTL_SECONDS` / `LOCK_MAX_RETRY` / `LOCK_RETRY_INTERVAL_US`。
|
||||
- `withDeviceFirmwareLock`:
|
||||
|
||||
```php
|
||||
$lock = OtaLockFactory::create(self::LOCK_KEY_PREFIX . $deviceId . ':' . $firmwareId, (float) self::LOCK_TTL_SECONDS);
|
||||
$this->acquireOrFail($lock);
|
||||
try {
|
||||
return $criticalSection();
|
||||
} finally {
|
||||
$this->releaseQuietly($lock);
|
||||
}
|
||||
```
|
||||
|
||||
- `acquireOrFail(LockInterface $lock)`:循环 `LOCK_MAX_RETRY` 次调用 `$lock->acquire(false)`,成功 return;否则 `usleep(LOCK_RETRY_INTERVAL_US)`;用尽后 `Log::warning` + `throw new BusinessException(OtaErrorCode::SERVICE_BUSY)`。
|
||||
- `releaseQuietly(LockInterface $lock)`:`try { $lock->release(); } catch (Throwable) { Log::warning(...) }`(释放失败不影响主流程,TTL 兜底)。
|
||||
- 删除手写 token 生成与 Lua 释放脚本(`acquireLock`/`releaseLock` 旧实现)。
|
||||
|
||||
## 验证
|
||||
- `docker exec -w /app/www/ai-device/ota php82 ./vendor/bin/phpunit`(含 `OtaCheckLogicTest` 真连 redis 验证加锁/幂等)。
|
||||
- 对改动文件容器内 `php -l`。
|
||||
- 运行完成门禁 `~/.cursor/skills/slot-backend-completion-report/scripts/report.sh`。
|
||||
|
||||
## 备注 / 取舍
|
||||
- 可选:将 [composer.json](composer.json) 的 `"php": ">=8.1"` 提升为 `>=8.2` 以与 symfony/lock 对齐(非必需,运行时已是 8.2)。
|
||||
- 备选方案:不新增 `OtaLockFactory`,把装配内联为 `OtaCheckLogic` 私有方法;本方案选独立 support 类以隔离基础设施、便于复用与替换 Store。
|
||||
- 阻塞策略:沿用 `acquire(false)` 有界重试而非 `acquire(true)` 无限阻塞,避免阻塞 webman worker,并保留 `SERVICE_BUSY` 行为。
|
||||
Reference in New Issue
Block a user