添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

以下是我从项目中摘抄的一段代码:

// 查看该用户是否已经有该道具的记录
BackPackData backPackData = backPackDataDao4Mongo.getUserBackPackPropData(uid, propId);
if (Objects.isNull(backPackData)) {
    // 如果不存在插入新的记录
    backPackData = new BackPackData()
            .setId(redisIdService.generate(BackPackData.class))
            .setUid(uid)
            .setDataId(propId)
            .setAmount(sourceEvent.getAmount())
            .setCreateTime(System.currentTimeMillis());
    backPackDataDao4Mongo.save(backPackData);
} else {
    // 如果存在,那么执行incr操作
    backPackDataDao4Mongo.incrPropAmount(uid, propId, sourceEvent.getAmount());

很显然,如果在高并发的场景下,多个线程同时查询不存在,那么会出现同时插入多条相同的记录,造成计数错误;

那么如果加上分布式锁,或者使用消息队列,使其串行执行,是否能保证正确性呢?

答案是否定的,原因就是mongo的主从复制存在延迟性,当第一条写操作在主节点执行完成后,第二次的从节点读操作一旦发生在主从同步之前,那么程序会认为不存在,再次执行写操作。

三、解决方法

原子操作实现:如果存在,更新,如果不存在,添加。

upsert

是一种特殊的更新,如果没有找到符合条件的更新条件的文档,就会以这个条件和更新文档为基础创建一个新的文档;如果找到了匹配的文档,就正常更新

执行命令:
db.getCollection('test').update({"_id": 3}, {"$inc": {"amount": 1}}, true);
查询结果(插入):
    "_id" : 3.0,
    "amount" : 1.0
执行命令:
db.getCollection('test').update({"_id": 3}, {"$inc": {"amount": 1}}, true);
查询结果(更新):
    "_id" : 3.0,
    "amount" : 2.0
  • 所有需要的属性都需要存在query或update条件中;
  • 当无法使用id作为query条件时,上述命令会插入一条id为uuid的数据。
执行命令:
db.getCollection('test').update({"uid": 3}, {"$inc": {"amount": 1}}, true)
查询结果(插入):
    "_id" : ObjectId("63512ae115160a87df5d0818"),
    "uid" : 3.0,
    "amount" : 1.0

$setOnInsert

db.getCollection('test').update( {"uid": 1}, {"$setOnInsert": { "_id" : 1 }, "$inc": {"amount": 1}}, true) 查询结果(插入): "_id" : 1.0, "uid" : 1.0, "amount" : 1.0 db.getCollection('test').update( {"uid": 1}, {"$setOnInsert": { "_id" : 1 }, "$inc": {"amount": 1}}, true) 查询结果(更新): "_id" : 1.0, "uid" : 1.0, "amount" : 2.0

当update条件中只存在$setOnInsert,可实现不存在则插入,存在则跳过语义。

通过upsert和$setOnInsert相结合,可以完美的解决并发下账户操作原子性问题。

from pymongo import MongoClient from datetime import datetime tzinfo = pytz.timezone('Asia/Shanghai') client = MongoClient( host=127.0.0.1, port=27017, username=root, password=123456, authSource=admi 在用C++对MongoDB执行update操作的时候,如果设置了upsert参数为true,则会自动插入不存在的数据。在高并发环境下,会导致数据重复。解决方法是为查询条件添加unique index, 参考官方文档:http://docs.mongodb.org/manual/core/write-operations-atomicity/http://docs.mongodb.org/manua MongoDB是一个开源的文档型数据库管理系统,采用BSON(Binary JSON)格式存储数据。它使用灵活的文档模型代替传统的表格模型,可以方便地存储和查询半结构化的数据。MongoDB的设计目标是轻松扩展和高性能,适用于需要处理大量数据和高并发请求的场景。通过总结实际项目中的经验和教训,可以得出一些最佳实践和优化建议。例如,选择合适的硬件配置和部署方案,规划好数据模型和索引策略,定期维护和监控数据库性能等。 一、MongoDB简介 1、集成简介 spring-data-mongodb提供了MongoTemplate与MongoRepository两种方式访问mongodbMongoRepository操作简单,MongoTemplate操作灵活,我们在项目中可以灵活适用这两种方式操作mongodbMongoRepository的缺点是不够灵活,MongoTemplate正好可以弥补不足。 2、搭建开发环境 2.1 初始化工程 使用 Spring Initializr 快速初始化一个 Spring Boot // 查询条件,如果数据存在更新 Query query = new Query(); query.addCriteria(Criteria.where("statisticsDatetime").is(e.getStatisticsDatetime())); query.addCriteria(Criteria.where("storeId").is(e.getStoreId())); //... <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency> ​ MongoShake是一个以golang语言进行编写的通用的平台型服务,通过读取MongoDB集群的Oplog操作日志,对MongoDB的数据进行复制,后续通过操作日志实现特定需求。日志可以提供很多场景化的应用,为此,我们在设计时就考虑了把MongoShake做成通用的平台型服务。通过操作日志,我们提供日志数据订阅消费PUB/SUB功能,可通过SDK、Kafka、MetaQ等方式灵活对接以适应不同场景(如日志订阅、数据中心同步、Cache异步淘汰等)。 在MongoDB数据库中$inc的作用大致可以理解为自增和自减,类似于其C语言中count+=1或者count-=1。但是两者之间还是有很大的区别,这里我们不仔细探究。 使用格式: { $inc: { : , : , … } } 在一个数组或者内嵌文档中指定一个的时候可以使用点号 $inc可以接收正的和负的值 如果指定的字段不存在则$inc操作符创建这个字段并且设置这个字段的值为指定的在值...