1.增加互斥锁:保证缓存和数据库数据一致性

2.增加消费队列,保证mysql数据的正常保存
This commit is contained in:
2026-04-20 14:13:48 +08:00
parent 614fb00ec4
commit 1eed3cf0f7
23 changed files with 836 additions and 255 deletions

View File

@@ -279,7 +279,7 @@
### 8.4 非功能
- **Redis已落地**
- **热点数据缓存**`app/common/service/GameHotDataRedis.php`,依赖 `webman/redis``config/redis.php`。缓存对象:`user` 行(鉴权与余额类读路径)、`game_config` 行(按 `config_key`)、`game_record`(活跃局、按 id、最新一条。写库后通过模型事件或服务内 **`gameRecordForget` / `gameConfigForget` / `userForget`** 失效;环境变量 `GAME_HOT_CACHE_*` 控制开关TTL`.env-example`)。
- **热点数据缓存**`app/common/service/GameHotDataRedis.php`,依赖 `webman/redis``config/redis.php`。缓存对象:`user` 行(鉴权与余额类读路径)、`game_config` 行(按 `config_key`)、`game_record`(活跃局、按 id、最新一条**写库后一致性**:统一经 **`GameHotDataCoordinator`**`afterUserCommitted` / `afterGameConfigKeyCommitted` / `afterGameRecordCommitted`)先 **同步回源**`userReplaceCacheFromDb``gameConfigReplaceFromDb``gameRecordSyncCachesAfterDbWrite`),再视配置将幂等任务写入 **`GameHotDataWriteQueue`**,由进程 **`GameHotDataQueueConsumer`** 批量消化以削峰。互斥:**`GameHotDataLock`** 用于同一用户、同一 `game_config` 键、同一对局记录等资源的并发写串行化(后台钱包加减点、独立配置表单、开奖相关服务、**移动端 `betPlace` 扣款**等与后台共用用户锁)。环境变量 `GAME_HOT_CACHE_*` 控制开关TTL、队列开关与背压(见 `.env-example``config/game_hot_cache.php`)。
- **规划/增强**(未替代上述):当前期注单池、近 30 期开奖列表缓存、纯 Redis 状态机等,可按压测需求迭代。
- **与 `CACHE_DRIVER`**`config/cache.php` 默认 `file` 仅影响 **系统配置表 `config`**(如 `get_sys_config`**不**控制 `GameHotDataRedis`
- MQ派彩异步消费**幂等**(如 `period_id` + 用户 + 业务单号)。
@@ -349,6 +349,7 @@
| V1.11 | 2026-04-16 | 落地运营公告:`operation_notice``user_notice_read` 表与菜单目录 `operation`(运营公告、用户阅读记录) |
| V1.12 | 2026-04-18 | 下注口径:`bet_order` 仅保留 `total_amount`(整笔压注),删除 `unit_amount``pick_count`DDL 与 Phinx 迁移 `20260418270000_bet_order_drop_unit_amount_pick_count` 对齐 |
| V1.14 | 2026-04-20 | 服务端 Redis 热点缓存:`GameHotDataRedis``user` / `game_config` / `game_record``config/game_hot_cache.php``.env-example``GAME_HOT_CACHE_*`;更新 §1.1、§2.1、§8.4、第九章风险与依赖、附录 `user`;与 `CACHE_DRIVER`(系统 `config` 表文件缓存)区分说明 |
| V1.15 | 2026-04-20 | 热点写路径收口:`GameHotDataCoordinator` + `GameHotDataLock` + `GameHotDataWriteQueue` / `GameHotDataQueueConsumer`;文档与实现对齐(替代仅 `*Forget` 描述);移动端 `betPlace` 与后台钱包共用用户互斥锁及 `coin` 乐观更新 |
---
@@ -381,7 +382,7 @@
| `total_deposit_coin` / `total_withdraw_coin` | 累计充值入账、累计提现出账(提现受理时累加);与「净充值」口径配合校验提现门槛 |
| `bet_flow_coin` | 打码量/流水(历史名 `total_valid_bet_coin`2026-04-18 迁移重命名);开奖结算成功时按注单 `total_amount` 1:1 累加,提现门槛 `bet_flow_coin >= (total_deposit_coin - total_withdraw_coin) × withdraw_bet_flow_ratio` |
| `risk_flags` | 风控位(如禁止登录/下注/提现,按位定义) |
| `current_streak` / `last_bet_period_no` | 连胜与期号兜底;**读路径**可通过 **`GameHotDataRedis` 缓存 `user` 行** 降低压力,**写路径**以下注/结算后落库为准并主动失效缓存 |
| `current_streak` / `last_bet_period_no` | 连胜与期号兜底;**读路径**可通过 **`GameHotDataRedis` 缓存 `user` 行** 降低压力,**写路径**以下注/结算后落库为准,并经 **`GameHotDataCoordinator::afterUserCommitted`** 同步刷新 Redis 与写队列入队 |
| `admin_id` | 归属子代理管理员 |
| 其余 | 账号、头像、状态、时间戳等见 DDL |

View File

@@ -105,19 +105,21 @@
**服务端缓存覆盖(与移动端直接相关的读路径)**
- **用户**:会员鉴权 `Auth::init` 优先读 Redis 中的 `user` 行快照,未命中再查库并回填**余额、连胜、打码量等变更**后服务端会删除该用户缓存,下次请求重新加载
- **游戏配置**`game_config``config_key` 缓存(如字典、`pick_max_number_count``withdraw_bet_flow_ratio`、连胜配置等);后台或脚本直连 `Db` 更新配置时,保存逻辑需调用 **`GameHotDataRedis::gameConfigForget($key)`**(已实现于模型事件及部分后台控制器),否则最长延迟为 TTL。
- **对局**:当前活跃局、按 `id` 加载的局、以及「最新一条 `game_record`」(与 `order id desc limit 1` 一致)缓存;**开奖封盘、新建下一期**等写库后会 **`gameRecordForget`**,保证关键状态尽快一致
- **用户**:会员鉴权优先读 Redis 中的 `user` 行快照,未命中再查库并回填**余额、连胜、打码量等变更**落库后,统一经 **`GameHotDataCoordinator::afterUserCommitted($userId)`**:先 **`GameHotDataRedis::userReplaceCacheFromDb`** 与 DB 对齐,再向 Redis 写队列投递幂等刷新任务(见 `GameHotDataWriteQueue` / `GameHotDataQueueConsumer`),用于削峰而非替代同步回源
- **游戏配置**`game_config``config_key` 缓存。后台直连 `Db` 更新时须 **`GameHotDataCoordinator::afterGameConfigKeyCommitted($key)`**(模型 `GameConfig` 事件与独立表单控制器已接入);独立保存接口在写入前对同一 `config_key` 使用 **`GameHotDataLock``TYPE_GAME_CONFIG`** 互斥。勿仅删除缓存键而不回源,否则最长不一致窗口为 TTL。
- **对局**:当前活跃局、按 `id` 的局、最新一条 `game_record` 等;写库后经 **`GameHotDataCoordinator::afterGameRecordCommitted`** 同步刷新相关 Redis 键并入队。开奖/封盘等路径另可按记录 id 使用 **`GameHotDataLock``TYPE_GAME_RECORD`** 串行化
**环境变量(示例见仓库根目录 `.env-example`**
- `GAME_HOT_CACHE_ENABLED`:是否启用上述 Redis 热点缓存(`false` 时全程回退数据库)。
- `GAME_HOT_CACHE_TTL_GAME_CONFIG` / `GAME_HOT_CACHE_TTL_GAME_RECORD` / `GAME_HOT_CACHE_TTL_USER`:各类缓存 TTL仍应以**写后失效**为主TTL 仅作兜底。
- `GAME_HOT_CACHE_TTL_GAME_CONFIG` / `GAME_HOT_CACHE_TTL_GAME_RECORD` / `GAME_HOT_CACHE_TTL_USER`:各类缓存 TTL**写后同步回源为主**TTL 仅作兜底。
- `GAME_HOT_CACHE_ENABLE_WRITE_QUEUE` 及队列长度、消费进程间隔等:控制写库后的**幂等刷新任务**是否入队及背压策略(见 `config/game_hot_cache.php`)。
**一致性提示(联调/测试)**
- 在 TTL 窗口内,若存在**绕过模型事件、且未调用 forget** 的手工 SQL 改库,可能出现短时不一致;生产环境应避免。
- 客户端仍可按 **§3.2 `dictionaryList``version`** 做本地缓存;服务端字典数据另有 Redis 加速,二者可同时存在
- 任何绕过协调入口、只改 DB 不调用 **`GameHotDataCoordinator`** 的手工脚本,都可能与 Redis 短期不一致;生产环境应避免。
- **`POST /api/game/betPlace`** 扣款路径使用与后台钱包加减点相同的 **用户维度 Redis 锁**`GameHotDataRedis::userAdminMutationLockTry`)及 **`WHERE coin = ?` 条件更新**,与并发派彩/后台调账互斥;失败时返回 **§4.2** 所列中文说明
- 客户端仍可按 **§3.2 `dictionaryList``version`** 做本地缓存;服务端字典另有 Redis 加速,二者可同时存在。
---
@@ -289,6 +291,10 @@
- `balance_after`string含义下单后余额
- `current_streak`int含义下单后连胜快照
**可能错误码(补充)**(其余见文档头部错误码分段;扣款与缓存一致性强相关):
- `5000`:系统繁忙;或 **用户 Redis 互斥锁**未获取(与后台钱包/并发写同一用户串行,文案与后台一致:「该用户正在被其他管理员操作(钱包/并发保存),请稍后再试」);或 **`coin` 条件更新**未命中(并发下注/派彩/后台已改余额:「扣款失败:该用户余额已被其他请求修改(如下注、派彩或其他管理员已保存),请刷新后重试」)。
> 说明:一键重复上一注、自动托管开启/停止均由前端控制,客户端在相应时机调用 `/api/game/betPlace` 即可完成,不再提供独立接口。
### 4.3 查询我的下注记录最近1个月