# 第五阶段:性能优化审查报告 ## 审查概述 | 项目 | 详情 | |------|------| | 审查日期 | 2026年5月19日 | | 审查范围 | 数据库查询、缓存策略、队列处理、同步阻塞 | | 发现问题数 | **35个** | | 高风险 | 10个 | | 中风险 | 15个 | | 低风险 | 10个 | --- ## 1. 数据库查询性能 ### 🔴 高风险问题 #### 1.1 渠道层级遍历 N+1 查询 **位置**:`common.php:437,464,483,510` ```php // get_top_channel() - 每次循环执行一次查询 while ($info = $channelModel->where(['id' => $channelId])->find()) { if ($info['level'] == 1 || ...) return $info; $channelId = $info['parent_id']; } ``` **影响**: - 渠道层级最多 4 级,每次调用 4-5 次查询 - Pay.php 分页列表循环调用,100 条数据 = 500+ 次额外查询 **建议**:使用 `id_path` 字段一次性查询 #### 1.2 Pay 列表循环内调用查询函数 **位置**:`admin/controller/Pay.php:366-376` ```php foreach ($list as $key => $item) { $channel = get_top_second_channel_name($list[$key]['channel_id']); // 2-5次查询 $list[$key]['union_name'] = get_union_channel($list[$key]['channel_id'])['name']; // 2-5次查询 } ``` **影响**:分页 100 条 → 每条 4-10 次查询 → 400-1000 次额外查询 **建议**:批量预查询 + 数组映射 #### 1.3 循环内重复查询(Bug) **位置**:`api/controller/Index.php:607-629` ```php foreach ($child_list as $v) { // 每次循环都查一次!且条件完全相同 $promoter_list = Db::name('nw_channel') ->where(['parent_id' => ['in', $child_list_ids], 'level' => 3]) ->select(); } ``` **建议**:提到循环外 #### 1.4 大表重复聚合查询 **位置**:`admin/controller/Pay.php:382-409` ```php // 同一请求中,对 cy_pay 表执行了 3 次几乎相同的聚合查询 $totalAmount = $PayModel->...->find()['total_amount']; $todayTotalAmount = model('Pay')->...->find()['total_amount']; $lastDayTotalAmount = model('Pay')->...->find()['total_amount']; ``` **建议**:合并为单条查询 ### 🟡 中风险问题 #### 1.5 缺少 field() 的 SELECT * **位置**:`admin/controller/Pay.php:1468-1477` ```php $pay = Db::name('cy_pay')->where(['orderid' => $orderid])->find(); // SELECT * $members = Db::name('cy_members')->where(['id' => $userid])->find(); // SELECT * $pyb = Db::name('nw_channel_admin')->where(['id' => $chal_id])->find(); // SELECT * ``` **影响**:连续 6 次 `SELECT *`,读取不必要的字段 **建议**:使用 `->field('id,orderid,amount')` 指定字段 #### 1.6 LIKE '%xxx%' 前缀通配符 **位置**:`api/controller/Index.php:564` ```php ->where(['id_path' => ['like', '%'.$qy_child_id_path.'%']]) ``` **影响**:`%` 前缀导致索引失效,全表扫描 **建议**:改用前缀匹配 `LIKE 'xxx%'` #### 1.7 3+ 表复杂 JOIN **位置**:`api/controller/JhApi.php:357-362` ```php ->join('cy_members cm', 'ns.member_id = cm.id', 'left') ->join('cy_game cg', 'ns.game_id = cg.id', 'left') ->join('nw_channel nc', 'ns.channel_id = nc.id', 'left') ->join('nw_channel p1', 'nc.parent_id = p1.id', 'left') ->join('nw_channel p2', 'p1.parent_id = p2.id', 'left') ``` **影响**:`nw_channel` 被 JOIN 3 次 **建议**:使用 `id_path` 拆分或冗余父级名称 #### 1.8 limit(10000) 无分页 **位置**:`admin/controller/FkExceptionRecords.php:191-195` ```php $data = Db::name('fk_exception_records') ->where($where) ->order('record_id desc') ->limit(10000) ->select(); ``` **建议**:使用分页或流式查询 #### 1.9 3 层嵌套子查询 UNION **位置**:`admin/controller/Pay.php:904-912` ```php $regSql = model('MemberGameServer')->field(...)->group(...)->buildSql(); $unionSql = model('Pay')->field(...)->group(...)->union([$regSql])->buildSql(); $unionSqlNew = Db::table($unionSql . ' bb')->field(...)->group(...)->buildSql(); $list = Db::table($unionSqlNew . ' cc')->paginate(...); ``` **建议**:使用临时表或预计算汇总数据 #### 1.10 循环内 COUNT 查询 **位置**:`admin/controller/Setting.php:149-151` ```php foreach ($channelList as $v) { $hasChildren = Db::name('nw_channel') ->where(['parent_id' => $v['value'], 'status' => 1]) ->count() > 0; } ``` **建议**:使用 GROUP BY 批量查询 --- ## 2. 缓存策略分析 ### 2.1 缓存配置 | 配置项 | 值 | |--------|-----| | 驱动 | complex (默认 Redis) | | 前缀 | `newsdk_` | | Redis 过期 | 7天 | | 文件缓存过期 | 30天 | | Session | Redis,前缀 `think_`,过期 35小时 | ### 2.2 缓存 Key 设计模式 | 类别 | Key 模式 | 过期时间 | |------|----------|----------| | Token | `{token字符串}` | 15/30天 | | 订单号 | `orderid:{orderid}` | 24小时 | | 渠道缓存 | `clearable_channel_list_*` | 24小时-30天 | | 游戏列表 | `clearable_game_list{md5}` | 300秒 | | 配置 | `clearable_get_setting:{name}` | 24小时 | | 风控名单 | `risk_control:inspect_list:{type}:{value}` | 7天 | ### 🔴 高风险问题 #### 2.1 缓存穿透风险 **问题**:查不到数据时不缓存空值 ```php // Setting::getSetting() - 不缓存空值 $info = self::where(['name' => $name])->value('value'); if (empty($info)) { return false; // 不缓存,下次继续查库 } ``` **建议**:缓存空值,短过期时间 #### 2.2 缓存击穿风险 **问题**:热点 key 无互斥锁 ```php // Game::getAllByCondition() - 无并发保护 $result = Cache::get($cacheKey); if (empty($result)) { $result = Db::table(...)->select(); // 高并发下同时查库 Cache::set($cacheKey, $result, 300); } ``` **建议**:使用互斥锁 #### 2.3 缓存雪崩风险 **问题**:同类 key 过期时间高度集中 - 渠道缓存全是 24h - 游戏列表全是 300s - `Cache::clear('channel_cache')` 瞬间删除所有渠道缓存 **建议**:过期时间加随机偏移 ### 🟡 中风险问题 #### 2.4 Token 存储不一致 - Register 存 token:过期 30 天 - Login 存 token:过期 15 天 - 相同 key 不同过期时间,存在覆盖问题 #### 2.5 直接操作 Redis handler 不一致 - 部分代码使用 `Cache::store('default')->handler()` - 部分代码使用 `Cache::set/get` - 绕过框架的前缀、序列化、Tag 机制 #### 2.6 缓存 Key 设计缺乏统一规范 存在多种分隔符混用: - `token|sdk|{userid}|{gameid}`(竖线) - `risk_control:rule:frequency:*`(冒号) - `clearable_channel_list_`(下划线) --- ## 3. 队列和异步处理 ### 3.1 队列使用现状 | 机制 | 用途 | 位置 | |------|------|------| | ThinkPHP Queue (Redis) | 支付回调异步通知 CP | `extra/queue.php` | | 原生 Redis List | 打包任务、报表生成 | `SubPackage.php` | | 原生 Redis ZSet | 抖音广告推送 | `Tiktok.php` | | Kafka | 通用消息队列(示例阶段) | `Kafka.php` | ### 🔴 高风险问题 #### 3.1 Redis DB 选择混乱 **位置**:`Tiktok.php:22` ```php $this->_redis->select(Env::get('redis.seelect_go', '')); // 拼写错误! ``` **问题**:配置键 `redis.seelect_go` 拼写错误,可能连接到错误的 DB #### 3.2 异步回调重试机制缺陷 **位置**:`crontab/controller/asyncCallback.php` - 第 33 行和第 55 行重复判断 `$job->attempts() > 5` - 重试间隔固定 120 秒,无指数退避 - 回调结果比较未做 trim/大小写统一 #### 3.3 同步回调重试名存实亡 **位置**:`PayCallback.php:78-95` ```php private function _doCallback($callback_url, $callback_params, $retry_times = 1, $sleep = 5) // retry_times = 1,sleep 被注释掉,实际只请求一次 ``` ### 🟡 中风险问题 #### 3.4 原生 Redis 与框架 Queue 并存 - `MakeReport.php`、`Tiktok.php` 等直接 `new \Redis()` - 连接不复用,无连接数控制 - 无统一的队列抽象层 #### 3.5 Kafka 仅处于示例阶段 - 事件处理只有 placeholder - 消费者未配置 `enable.auto.commit => false` - 无 graceful shutdown #### 3.6 队列监控完全缺失 - 无队列深度监控 - 无死信队列 - 无告警机制 - 无消费速率统计 #### 3.7 定时任务 N+1 查询 **位置**:`crontab/ChannelClean.php:347-376` ```php for($i=1; $i<=$day_limit; $i++){ // 每天单独查询一次数据库 $temp_ids = Db::table('cy_member_channel_game_rel')... } ``` **建议**:改为单条 SQL 的 `BETWEEN` --- ## 4. 同步阻塞问题 ### 🔴 高风险问题 #### 4.1 curl 无超时设置 | 位置 | 函数 | 问题 | |------|------|------| | `common.php:1290-1296` | `timingCurl()` | **未设置任何超时** | | `api/complex/Yehua.php:121-133` | `curlInit()` | **未设置任何超时** | | `api/controller/v2/Huge.php:175-188` | `curlJson()` | **未设置任何超时** | | `admin/controller/Tasks.php:814-830` | curl 调用 | **未设置任何超时** | | `crontab/DealGameChannelDivide.php:156-167` | curl 调用 | **未设置任何超时** | #### 4.2 curl 超时过长 | 位置 | 超时 | 说明 | |------|------|------| | `common/library/Sms.php:533-548` | **300秒** | 短信发送不应超过 30 秒 | | `common/library/jssdk.php:125-139` | **500秒** | 微信 JSSDK,荒谬超时 | | `common.php:365-388` | **60秒** | 通用 HTTP 工具函数 | | `home/common.php:391-406` | **60秒** | Web 控制器调用 | #### 4.3 curl 句柄泄漏 **位置**:`service/PayService.php:922-926`、`api/controller/v1/Pay.php:1885-1889` ```php return $xml; // return 在 curl_close($ch) 之前 // curl 句柄永远不会关闭 ``` ### 🟡 中风险问题 #### 4.4 file_get_contents 访问外部 URL | 位置 | 说明 | |------|------| | `api/controller/v1/Login.php:298` | 淘宝 IP 查询,无超时 | | `common/model/CscAppeal.php:137` | 淘宝 IP 查询,无超时 | | `common.php:1662` | IP 查询,无超时 | #### 4.5 CONNECTTIMEOUT 与 TIMEOUT 配置不合理 **位置**:`api/common.php:14-52` ```php CONNECTTIMEOUT=30, TIMEOUT=5 // 连接超时30秒过长,总超时仅5秒,配置反了 ``` #### 4.6 数据库读写未分离 **位置**:`config.php:327-345` ```php 'deploy' => 0, // 集中式 'rw_separate' => false, // 读写未分离 // 从库配置与主库相同,从库形同虚设 ``` #### 4.7 长时间运行任务无保护 **位置**:`crontab/mlbb/TaskHandle.php:52-86` ```php do { // 调用外部 API usleep(10000); // 仅 10ms } while(true); // 无限循环 ``` #### 4.8 递归调用无深度限制 **位置**:`common.php:394-408` ```php function makeOrderid($head = 'WL') { if ($redis->has($cache_key)) { return makeOrderid(); // 无递归深度保护 } } ``` --- ## 5. 问题汇总 ### 按风险等级分类 #### 🔴 高风险 | 序号 | 问题 | 位置 | |------|------|------| | 1 | 渠道层级遍历 N+1 查询 | `common.php:437` | | 2 | Pay 列表循环内调用查询函数 | `admin/controller/Pay.php:366` | | 3 | 循环内重复查询(Bug) | `api/controller/Index.php:620` | | 4 | 大表重复聚合查询 | `admin/controller/Pay.php:382` | | 5 | 缓存穿透风险 | 全局 | | 6 | 缓存击穿风险 | 全局 | | 7 | 缓存雪崩风险 | 全局 | | 8 | Redis DB 选择混乱 | `Tiktok.php:22` | | 9 | curl 无超时设置 | 5 处 | | 10 | curl 超时过长(300-500秒) | 2 处 | #### 🟡 中风险 | 序号 | 问题 | 数量/位置 | |------|------|----------| | 1 | 缺少 field() 的 SELECT * | 10+ 处 | | 2 | LIKE '%xxx%' 前缀通配符 | 2 处 | | 3 | 3+ 表复杂 JOIN | 多处 | | 4 | limit(10000) 无分页 | 1 处 | | 5 | 3 层嵌套子查询 UNION | 1 处 | | 6 | 循环内 COUNT 查询 | 1 处 | | 7 | Token 存储不一致 | 全局 | | 8 | 缓存 Key 设计不规范 | 全局 | | 9 | 异步回调重试缺陷 | `asyncCallback.php` | | 10 | 同步回调重试名存实亡 | `PayCallback.php` | | 11 | 原生 Redis 与框架并存 | 多处 | | 12 | Kafka 仅示例阶段 | `Kafka.php` | | 13 | 队列监控缺失 | 全局 | | 14 | 定时任务 N+1 查询 | `ChannelClean.php` | | 15 | curl 句柄泄漏 | 2 处 | #### 🟢 低风险 | 序号 | 问题 | |------|------| | 1 | file_get_contents 访问外部 URL | | 2 | CONNECTTIMEOUT 配置不合理 | | 3 | 数据库读写未分离 | | 4 | 长时间运行任务无保护 | | 5 | 递归调用无深度限制 | | 6 | 缓存 Key 分隔符混用 | | 7 | 直接操作 Redis handler 不一致 | | 8 | 定时任务 usleep 过短 | | 9 | 密钥文件每次请求读取 | | 10 | 文件日志锁竞争 | --- ## 6. 改进建议 ### 第一优先级(查询优化) 1. **修复渠道层级查询**:使用 `id_path` 一次性查询 2. **Pay 列表批量预查询**:收集所有 ID 后批量查询 3. **修复循环内重复查询 Bug**:提到循环外 4. **合并重复聚合查询**:单条 SQL 完成 ### 第二优先级(缓存优化) 5. **防缓存穿透**:缓存空值 6. **防缓存击穿**:互斥锁 7. **防缓存雪崩**:过期时间加随机偏移 8. **统一 Token 存储**:统一过期时间 ### 第三优先级(队列优化) 9. **修复拼写错误**:`redis.seelect_go` → `redis.select_go` 10. **完善异步回调**:指数退避 + 结果比较容错 11. **增加死信队列**:失败任务可追溯 12. **增加队列监控**:深度 + 告警 ### 第四优先级(阻塞优化) 13. **统一 curl 超时**:CONNECTTIMEOUT=5, TIMEOUT=10 14. **修复 curl 句柄泄漏**:`curl_close` 移到 `return` 前 15. **替换 file_get_contents**:使用 curl 并设置超时 16. **启用数据库读写分离** --- ## 7. 参考代码 ### 渠道层级查询优化 ```php // ❌ 当前:循环查询 while ($info = $channelModel->where(['id' => $channelId])->find()) { $channelId = $info['parent_id']; } // ✅ 建议:使用 id_path $ids = explode(',', trim($channel->id_path, ',')); $channels = $channelModel->whereIn('id', $ids)->select(); ``` ### 缓存穿透防护 ```php // ❌ 当前:不缓存空值 if (empty($info)) return false; // ✅ 建议:缓存空值 if (empty($info)) { Cache::set($cache_key, '', 300); return false; } ``` ### 缓存击穿防护 ```php // ✅ 建议:互斥锁 $result = Cache::get($cacheKey); if (empty($result)) { $lockKey = $cacheKey . ':lock'; if (Cache::set($lockKey, 1, 5)) { $result = Db::table(...)->select(); Cache::set($cacheKey, $result, 3600); Cache::rm($lockKey); } else { usleep(100000); return $this->getData($cacheKey); } } ``` ### curl 超时统一 ```php // ✅ 建议 curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5); curl_setopt($ch, CURLOPT_TIMEOUT, 10); ``` --- *审查报告生成时间:2026年5月19日*