You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何同步持续更新Map的路由请求与每小时写入Map数据到数据库的定时任务

解决方案

首先删除路由逻辑中多余的update_count()调用,避免频繁触发数据库更新操作,抵消Map缓存计数的性能优势。以下是两种无数据异常的同步实现方案:

方案1:双Map切换法(推荐,性能无损耗)

利用JS单线程赋值的原子性,通过切换计数存储的Map完全避免读写冲突,实现最简单,性能损耗为0:

  • 维护两个Map:请求侧仅写入temp_count,定时任务仅读取独立的persist_count
  • 定时任务触发时,先原子交换两个Map的引用:将当前temp_count赋值给persist_count,同时直接初始化新的空Map给temp_count承接后续新请求的计数,两个操作不会被任何请求逻辑打断
  • 定时任务遍历persist_count的所有键值对批量更新到MongoDB,完成后直接释放persist_count即可
  • 新增执行状态标识避免定时任务重入,新增异常兜底逻辑避免持久化失败丢数

参考实现代码:

const Link_URL = require('./你的模型路径');
const cron = require('node-cron');

// 替换原单Map声明
let temp_count = new Map();
let isUpdating = false;

async function update_count(){
    // 上个定时任务未执行完成直接跳过,避免重入
    if (isUpdating) return;
    isUpdating = true;

    // 原子交换Map,JS单线程保证该操作不会被中断
    const persist_count = temp_count;
    temp_count = new Map();
    
    try {
        // 批量生成Mongo增量更新操作,用$inc避免覆盖数据库侧的其他修改
        const bulkOps = Array.from(persist_count.entries()).map(([code, count]) => ({
            updateOne: {
                filter: { _id: code },
                update: { $inc: { visit_count: count } }
            }
        }));

        if (bulkOps.length > 0) {
            await Link_URL.bulkWrite(bulkOps);
        }
    } catch (err) {
        console.error('计数持久化失败:', err);
        // 持久化失败兜底:将未同步的计数合并回当前temp_count,避免丢数
        for (const [code, count] of persist_count.entries()) {
            temp_count.set(code, (temp_count.get(code) || 0) + count);
        }
    } finally {
        isUpdating = false;
    }
}

app.get('/teeny/:code', async (req, res) => {
    try {
        const url = await Link_URL.findOne({
            _id : req.params.code
        })
        if (url) {
            const code = req.params.code;
            // 仅写入temp_count,无需触发更新逻辑
            temp_count.set(code, (temp_count.get(code) || 0) + 1);
            return res.redirect(url.URL);
        } else {
            return res.status(404).json('No URL Found')
        }
    }
    catch (err) {
        console.error(err);
        res.status(500).json('Server Error');
    }
})

// 修正cron表达式:0 0 * * * 表示每小时整点执行,原表达式* * 1 * *为每月1号每分钟执行
cron.schedule('0 0 * * *', update_count);

方案2:读写锁方案(适合复杂并发场景)

如果存在多段业务逻辑同时读写计数Map的场景,可以引入读写锁保证同步:

  • 引入async-rwlock之类的读写锁库
  • 路由侧更新Map时加读锁,允许多个请求同时读取修改Map
  • 定时任务执行持久化时加写锁,阻塞所有读操作,持久化完成后释放锁
    该方案会对请求响应性能有轻微影响,纯计数缓存场景无需使用。

注意事项

  • 数据库更新必须用$inc增量操作符,不要直接用Map内的数值覆盖数据库字段,避免数据库侧有其他业务修改计数时出现数据丢失
  • 服务进程重启前需要主动调用一次update_count,将内存中未同步的计数持久化到数据库,避免丢数
  • 若部署多实例服务,需要改用Redis集中存储计数,避免多实例内存Map数据不一致

内容的提问来源于stack exchange,提问作者Anirudh Singh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 02:24:03