基于NodeJS的联盟链接点击追踪服务:高并发适配与优化问询
联盟链接点击追踪服务的高并发适配与优化方案
我想要创建一个联盟链接点击追踪服务,逻辑很简单:用户点击服务提供的链接时,会被重定向至原始联盟链接。我用MongoDB存储数据,需要通过请求里的offerID获取对应的原始联盟链接。我写了以下代码:
app.get('/click', async (req, res) => { //Tracking link : https://myservice.com/click?a=500&o=15112 const url =req.protocol + '://' + req.get('host') + req.originalUrl const affiliateId = new URL(url).searchParams.get('a') const offerID = new URL(url).searchParams.get('o') //Getting affiliateID and offerID from URL const link = await Links.findOne({oID: offerID}) //Getting the affiliate link from the MongoDB by passing offerID if (link == null) return res.sendStatus(404) const userAgent = navigator.userAgent //Getting the userAgent const clickID = shortId.generate //Generating a unique clickID const finalLink = link.affiliateLink.concat(`&clickID=${clickID}&sub1=${affiliateId}`) //Creating the link from the affiliates //Link will be like https://offer.com/clicks?affiliateoffer=500&affiliateid=1000&clickID=12151212121&sub1=500 const click = new Click({ affID: affiliateId, oID: offerID, clickID: clickID, userAgent: userAgent, }); Click.save(click) // Saving the click on the database(mongodb) res.redirect(finalLink) //Redirecting to affiliate link })
一、数千次并发点击下的运行情况
当前代码存在多个致命问题,直接导致高并发场景下服务无法正常运行:
- 异步操作未正确处理:
Click.save(click)未使用await,数据库保存操作会在后台异步执行,高并发下会堆积大量未完成的数据库请求,耗尽连接池资源,同时可能丢失大量点击数据。 - 浏览器API误用:
navigator.userAgent是浏览器端专属API,Node.js环境下调用会直接抛出错误,导致请求失败。 - 函数未执行:
shortId.generate仅引用了函数,未调用执行,clickID实际是函数对象而非生成的唯一ID,会导致最终跳转链接无效。 - 无索引的数据库查询:
Links.findOne({oID: offerID})中oID字段未建立索引,高并发下大量查询会触发全表扫描,拖慢数据库响应,甚至导致数据库过载。 - 冗余的URL解析:手动拼接URL再重新解析的操作完全多余,增加了不必要的CPU开销。
如果不修复这些问题,数千次并发点击时服务会频繁报错、丢失数据,甚至直接崩溃。
二、缩短重定向时间的优化方案
1. 修复基础逻辑错误
- 替换用户代理获取方式:
const userAgent = req.get('User-Agent') - 正确生成唯一ID:
const clickID = shortId.generate() - 处理数据库保存操作:要么用
await click.save()确保数据入库(会增加一点延迟),要么改为异步非阻塞执行(优先重定向,后台处理数据):click.save().catch(err => console.error('点击数据保存失败:', err))
2. 数据库层优化
- 给
Links集合的oID字段创建唯一索引,将查询效率从O(n)提升至O(1):db.Links.createIndex({oID: 1}, {unique: true}) - 调整MongoDB连接池配置,根据并发量设置合理的
maxPoolSize,避免连接耗尽。
3. 减少请求阻塞
将点击数据保存改为异步非阻塞执行,不等待数据库操作完成就返回重定向响应,用户感知的跳转延迟会大幅降低,同时通过错误捕获确保异常情况可追溯。
4. 简化参数获取逻辑
直接从req.query中提取参数,省去冗余的URL拼接与解析:
const affiliateId = req.query.a; const offerID = req.query.o;
5. 缓存热门链接
使用Redis或内存缓存(如lru-cache)存储访问频率高的offer链接,避免每次请求都查询MongoDB。缓存失效时间可根据链接更新频率灵活设置,比如设置为1小时或更久。
6. 优化链接拼接方式
使用URLSearchParams处理链接参数,避免手动拼接的错误,同时确保参数编码正确:
const urlObj = new URL(link.affiliateLink); urlObj.searchParams.set('clickID', clickID); urlObj.searchParams.set('sub1', affiliateId); const finalLink = urlObj.toString();
7. 服务器与网络优化
- 启用HTTP/2或HTTP/3协议,提升并发请求的处理效率
- 将服务部署在靠近目标用户的区域,降低网络延迟
内容的提问来源于stack exchange,提问作者Schola LMS
相关产品推荐
相关产品推荐

