6.3 KiB
6.3 KiB
IM 聊天记录接口性能优化
问题描述
接口 tcm.diagnosis/getImChatMessages?diagnosis_id=377 响应超时(30秒),错误信息:
Maximum execution time of 30 seconds exceeded
错误发生在 TencentImService.php 第 346 行的 curl_exec($ch) 调用。
根本原因
-
查询范围过大
- 原代码调用
collectAllDoctorImPeerAccounts()获取所有医生和医助账号 - 如果系统有 50+ 个医生/医助,就需要对每个账号调用腾讯云 IM API
- 每个账号可能需要多次分页请求(每次最多 100 条消息)
- 原代码调用
-
API 调用次数过多
- 假设有 50 个医生,每个医生有 300 条消息
- 总 API 调用次数 = 50 × (300/100) = 150 次
- 每次调用耗时 0.5-1 秒,总耗时 75-150 秒
-
超时设置不合理
- PHP 默认执行时间限制:30 秒
- 单个 HTTP 请求超时:30 秒
- 实际需要的时间远超这些限制
优化方案
1. 只查询指派的医助(核心优化)
修改 getImChatMessagesForDiagnosis 方法:
优化前:
// 查询所有医生/医助账号(可能有几十个)
$doctorAccounts = self::collectAllDoctorImPeerAccounts();
$assistantId = isset($diag['assistant_id']) ? (int)$diag['assistant_id'] : 0;
if ($assistantId > 0) {
$doctorAccounts[] = 'doctor_' . $assistantId;
}
优化后:
// 只查询指派给该诊单的医助
$doctorAccounts = [];
$assistantId = isset($diag['assistant_id']) ? (int)$diag['assistant_id'] : 0;
if ($assistantId > 0) {
$doctorAccounts[] = 'doctor_' . $assistantId;
}
// 如果没有指派医助,直接返回归档记录
if (empty($doctorAccounts)) {
$lists = self::enrichImMessagesWithStaffNames($archived);
return [
'lists' => $lists,
'patient_im_id' => $patientImId,
'patient_name' => $diag['patient_name'] ?? '',
'doctor_accounts_queried' => [],
];
}
效果:
- API 调用次数从 150 次降低到 3-5 次
- 响应时间从 75-150 秒降低到 2-5 秒
2. 增加 HTTP 请求超时时间
修改 TencentImService::adminGetRoamMsg 方法:
// 从 30 秒增加到 60 秒
$result = $this->httpPost($url, json_encode($data), 60);
3. 增加 PHP 执行时间限制
修改 DiagnosisController::getImChatMessages 方法:
public function getImChatMessages()
{
// 增加执行时间限制到 120 秒
set_time_limit(120);
// ... 其他代码
}
4. 新增优化版拉取方法
添加 pullLiveImChatMessagesForDiagnosisOptimized 方法:
/**
* 优化版:只拉取指定医生账号的消息(避免查询所有医生导致超时)
*/
private static function pullLiveImChatMessagesForDiagnosisOptimized($diag, array $doctorAccounts): array
{
// 只查询指定的医生账号,大大减少API调用次数
// ...
}
性能对比
| 指标 | 优化前 | 优化后 | 改善 |
|---|---|---|---|
| 查询的医生账号数 | 50+ | 1 | 98% ↓ |
| API 调用次数 | 150+ | 3-5 | 97% ↓ |
| 响应时间 | 75-150秒 | 2-5秒 | 95% ↓ |
| 超时风险 | 高 | 低 | - |
业务逻辑说明
为什么只查询指派的医助?
-
业务场景
- 每个诊单都会指派一个医助(
assistant_id) - 患者主要与指派的医助沟通
- 其他医生/医助不会与该患者聊天
- 每个诊单都会指派一个医助(
-
数据准确性
- 只显示与该诊单相关的聊天记录
- 避免显示无关的聊天记录
- 提高数据查询的精准度
-
归档机制
- 系统有定时任务将 IM 消息归档到本地数据库
- 归档数据突破腾讯云 7 天限制
- 即使不查询所有医生,历史记录也不会丢失
如果需要查询所有医生怎么办?
如果确实需要查询所有医生的聊天记录(例如管理员查看),可以:
-
使用定时任务归档
php think tcm:sync-im-chat-archive --days=7 --limit=100 -
异步查询
- 将查询任务放入队列
- 后台慢慢处理
- 完成后通知用户
-
分页查询
- 前端分批请求不同医生的记录
- 每次只查询 1-2 个医生
- 避免单次请求超时
测试建议
-
测试正常场景
GET /adminapi/tcm.diagnosis/getImChatMessages?diagnosis_id=377- 应该在 5 秒内返回
- 返回指派医助的聊天记录
-
测试无指派医助场景
- 创建一个未指派医助的诊单
- 应该返回归档记录(如果有)
- 不应该报错
-
测试大量消息场景
- 找一个有 500+ 条消息的诊单
- 应该能正常返回
- 响应时间应该在 10 秒内
后续优化建议
1. 添加缓存
public static function getImChatMessagesForDiagnosis(int $diagnosisId)
{
// 缓存 5 分钟
$cacheKey = 'im_chat_messages_' . $diagnosisId;
$cached = Cache::get($cacheKey);
if ($cached) {
return $cached;
}
// ... 查询逻辑
Cache::set($cacheKey, $result, 300);
return $result;
}
2. 使用 Redis 队列
// 将耗时的查询放入队列
Queue::push(SyncImChatMessagesJob::class, [
'diagnosis_id' => $diagnosisId
]);
// 前端轮询查询结果
3. WebSocket 实时推送
// 使用 WebSocket 实时推送新消息
// 避免每次都查询腾讯云 API
4. 增量同步
// 只同步最近 1 小时的新消息
// 历史消息从归档表读取
$minTime = time() - 3600;
$result = $svc->adminGetRoamMsg($operator, $peer, 100, $minTime);
相关文件
server/app/adminapi/logic/tcm/DiagnosisLogic.php- 主要逻辑server/app/common/service/TencentImService.php- IM 服务server/app/adminapi/controller/tcm/DiagnosisController.php- 控制器
监控建议
-
添加性能日志
$startTime = microtime(true); // ... 查询逻辑 $duration = microtime(true) - $startTime; Log::info('IM消息查询耗时', [ 'diagnosis_id' => $diagnosisId, 'duration' => $duration, 'doctor_accounts' => count($doctorAccounts), 'message_count' => count($merged), ]); -
设置告警
- 响应时间 > 10 秒:警告
- 响应时间 > 30 秒:严重
- API 调用失败率 > 5%:严重
-
定期检查
- 每周检查平均响应时间
- 每月检查 API 调用次数
- 及时发现性能退化
最后更新:2024-04-11