以下是我从项目中摘抄的一段代码:
// 查看该用户是否已经有该道具的记录
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的主从复制存在延迟性,当第一条写操作在主节点执行完成后,第二次的从节点读操作一旦发生在主从同步之前,那么程序会认为不存在,再次执行写操作。
原子操作实现:如果存在,更新,如果不存在,添加。
是一种特殊的更新,如果没有找到符合条件的更新条件的文档,就会以这个条件和更新文档为基础创建一个新的文档;如果找到了匹配的文档,就正常更新
执行命令:
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
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两种方式访问mongodb,MongoRepository操作简单,MongoTemplate操作灵活,我们在项目中可以灵活适用这两种方式操作mongodb,MongoRepository的缺点是不够灵活,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操作符创建这个字段并且设置这个字段的值为指定的在值...