关于 MongoDB 实现计数器时并发计数错误的问题

2019-04-22 22:00:55 +08:00
 DovaKeen

我用 MongoDB 的 upsert 和$inc 实现了一个简单的自增计数器,但是却发现当对一个键并发自增计数时,会丢失数据,让人百思不得其解啊

我的 MongoDB 进程是在本机运行的,计数器主要代码如下: 计数器的 incr 实现:

public boolean incr(String key) {

    checkKey(key);
    
    UpdateResult result = mongoTemplate.upsert(query(Criteria.where("key").is(key)),
            new Update().inc("value", 1),
            MongoCounter.class, COLLECTION_NAME);
    return result.wasAcknowledged();

}

测试代码:

    int count = 200;
    ExecutorService executor = Executors.newCachedThreadPool();
    String key = "incrkey";
    for (int i = 0; i < count; i++) {
        executor.submit(() -> counter.incr(key));
    }
    assertTrue(counter.getCounter(key) == count);
    executor.shutdown();
    counter.delCounter(key);

在测试时,开了一个线程池,用多个线程同时向一个初始没有值的键连续调用 n 次 incr,但是结果发现总是会丢失几次更新,难道原子性更新的前提是并发量不能太大吗?

请懂 MongoDB 的人指点一二啊

2545 次点击
所在节点    问与答
4 条回复
xmh51
2019-04-22 22:20:04 +08:00
不应该立马 executor.shutdown();
你等等 java 执行完异步线程再关闭.........
DovaKeen
2019-04-23 08:51:31 +08:00
@xmh51 executor.shutdown();执行之后,已经提交过的任务还是会跑完才关闭的,不会直接关闭线程池呀,而且我注释掉了这一句后,还是会丢失更新,,
xmh51
2019-04-23 10:22:39 +08:00
@DovaKeen 用错了方法你 This method does not wait for previously submitted tasks to
* complete execution 该方法会立即返回, 但不一定代表之前提交的任务已经全部完成了. 如果需要一个阻塞的方法, 可以调用 awaitTermination 方法
DovaKeen
2019-04-23 15:46:54 +08:00
@xmh51 确实是这里的问题,并且还发现当多个线程同时对一个没有值的键调用 $inc 时,可能会产生多个值为 1 而键名相同的文档,这里也是会导致计数器结果错误的地方,现在给键名加上 unique 约束,并且在调用后捕获异常重设值,就可以得到正确的结果了。谢谢你呀

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/557692

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX