Files
zyt/IM_CHAT_MESSAGES_PERFORMANCE_FIX.md
T
2026-04-11 18:06:02 +08:00

6.3 KiB
Raw Blame History

IM 聊天记录接口性能优化

问题描述

接口 tcm.diagnosis/getImChatMessages?diagnosis_id=377 响应超时(30秒),错误信息:

Maximum execution time of 30 seconds exceeded

错误发生在 TencentImService.php 第 346 行的 curl_exec($ch) 调用。

根本原因

  1. 查询范围过大

    • 原代码调用 collectAllDoctorImPeerAccounts() 获取所有医生和医助账号
    • 如果系统有 50+ 个医生/医助,就需要对每个账号调用腾讯云 IM API
    • 每个账号可能需要多次分页请求(每次最多 100 条消息)
  2. API 调用次数过多

    • 假设有 50 个医生,每个医生有 300 条消息
    • 总 API 调用次数 = 50 × (300/100) = 150 次
    • 每次调用耗时 0.5-1 秒,总耗时 75-150 秒
  3. 超时设置不合理

    • 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% ↓
超时风险 -

业务逻辑说明

为什么只查询指派的医助?

  1. 业务场景

    • 每个诊单都会指派一个医助(assistant_id
    • 患者主要与指派的医助沟通
    • 其他医生/医助不会与该患者聊天
  2. 数据准确性

    • 只显示与该诊单相关的聊天记录
    • 避免显示无关的聊天记录
    • 提高数据查询的精准度
  3. 归档机制

    • 系统有定时任务将 IM 消息归档到本地数据库
    • 归档数据突破腾讯云 7 天限制
    • 即使不查询所有医生,历史记录也不会丢失

如果需要查询所有医生怎么办?

如果确实需要查询所有医生的聊天记录(例如管理员查看),可以:

  1. 使用定时任务归档

    php think tcm:sync-im-chat-archive --days=7 --limit=100
    
  2. 异步查询

    • 将查询任务放入队列
    • 后台慢慢处理
    • 完成后通知用户
  3. 分页查询

    • 前端分批请求不同医生的记录
    • 每次只查询 1-2 个医生
    • 避免单次请求超时

测试建议

  1. 测试正常场景

    GET /adminapi/tcm.diagnosis/getImChatMessages?diagnosis_id=377
    
    • 应该在 5 秒内返回
    • 返回指派医助的聊天记录
  2. 测试无指派医助场景

    • 创建一个未指派医助的诊单
    • 应该返回归档记录(如果有)
    • 不应该报错
  3. 测试大量消息场景

    • 找一个有 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 - 控制器

监控建议

  1. 添加性能日志

    $startTime = microtime(true);
    // ... 查询逻辑
    $duration = microtime(true) - $startTime;
    Log::info('IM消息查询耗时', [
        'diagnosis_id' => $diagnosisId,
        'duration' => $duration,
        'doctor_accounts' => count($doctorAccounts),
        'message_count' => count($merged),
    ]);
    
  2. 设置告警

    • 响应时间 > 10 秒:警告
    • 响应时间 > 30 秒:严重
    • API 调用失败率 > 5%:严重
  3. 定期检查

    • 每周检查平均响应时间
    • 每月检查 API 调用次数
    • 及时发现性能退化

最后更新:2024-04-11