Teams Bot通知:存储600+对话后FindMember API响应极慢
问题分析与解决方案
问题根源
当前使用findMember方法时,会遍历存储中所有对话记录,对每条记录都调用Microsoft Teams API获取成员信息后再匹配邮箱。当对话量达到600条时,串行的API请求堆积加上Blob存储无索引导致的全量遍历IO开销,最终让耗时从7秒飙升至15分钟。
优化方案
1. 为对话存储添加邮箱索引(最优解)
针对兼容Azure Blob的存储,修改对话记录的存储规则,将用户邮箱作为Blob的命名标识或元数据:
- 存储阶段:保存对话记录时,以用户邮箱作为Blob文件名(例如
user@domain.com.json),或给Blob添加email元字段存储邮箱地址。 - 查找阶段:直接通过邮箱定位对应的Blob,无需遍历所有对话,也不用重复调用Teams API匹配邮箱。
示例修改存储与查找逻辑:
// 保存对话时按邮箱命名Blob await blobStore.save(`conv_${member.account.email}.json`, conversationData); // 自定义邮箱查找方法 async function findMemberByEmail(email: string) { const targetBlobName = `conv_${email}.json`; const conversationData = await blobStore.read(targetBlobName); if (conversationData) { // 构造符合原有调用逻辑的Member对象 return { sendAdaptiveCard: async (card) => { await notificationApp.notification.sendAdaptiveCard(conversationData, card); } } as any; } return null; }
2. 缓存成员信息,减少重复API调用
将已获取的“邮箱-对话关联”数据缓存(可用Redis或内存缓存),避免每次查找都调用Teams API:
import Redis from 'ioredis'; const redisClient = new Redis(); async function findMemberByEmail(email: string) { // 先查缓存 const cachedConvId = await redisClient.get(`email_conv_map:${email}`); if (cachedConvId) { const conversationData = await blobStore.read(cachedConvId); return conversationData ? { /* 构造Member对象 */ } : null; } // 缓存未命中时遍历查找,找到后更新缓存 const allConvIds = await blobStore.list(); for (const convId of allConvIds) { const member = await notificationApp.notification.getMember(convId); if (member.account.email === email) { // 缓存1天 await redisClient.set(`email_conv_map:${email}`, convId, 'EX', 86400); return member; } } return null; }
3. 并行处理遍历,压缩串行耗时
如果暂时无法修改存储结构,将串行遍历改为有限并发的并行处理,同时控制并发数避免触发Teams API速率限制:
async function findMemberByEmail(email: string) { const allConvIds = await blobStore.list(); const batchSize = 10; // 每次并行处理10条 let targetMember = null; for (let i = 0; i < allConvIds.length; i += batchSize) { const batch = allConvIds.slice(i, i + batchSize); const promises = batch.map(async (convId) => { const member = await notificationApp.notification.getMember(convId); if (member.account.email === email) { targetMember = member; } }); await Promise.all(promises); if (targetMember) break; // 找到目标后提前终止遍历 } return targetMember; }
4. 切换到带索引的存储服务
若Blob存储的索引能力不足,可将对话记录迁移到Azure Cosmos DB或SQL数据库,利用数据库的索引功能快速定位匹配邮箱的对话,彻底避免全量遍历。
总结
优先选择添加邮箱索引的方案,从根源解决遍历和重复API调用问题;若暂时无法修改存储结构,用并行处理或缓存作为临时优化,可大幅降低耗时。
内容的提问来源于stack exchange,提问作者user2872741
相关产品推荐
相关产品推荐

