进行原子操作
目前数据存储服务使用 MongoDB 3.4,不具备处理事务的能力,但这不代表开发者无法进行并发操作。阅读本文,了解 MongoDB 提供的原子操作符以及使用方法。
原子插入
要保证同一条信息不可插入两次,在集合上新增一个字段并且设置 unique index
,如下所示:
// 创建唯一索引
basement.db.collection('order').createIndex(
{ 'orderId': 1 },
{ 'unique': true }
);
// 插入时验证
function placeOrder(items) {
const orderId = uuid();
basement.db.collection('order').insertOne({
orderId,
items,
}).then(res => {
console.log('order placed successful');
}).catch(err => {
console.error('insert failed');
});
}
原子更新
原子更新可以通过判断当前的最后更新时间来匹配是否可以更新。例如,在发送一个礼物时,A 和 B 同时领取,在不支持原子更新的情况下,可能导致两个用户都抢到的情况。
basement.db.collection('gifts').createIndex('inventory.itemId');
function giveOutGift(owner, item) {
// 发礼物
return basement.db.collection('inventory').updateOne({
user: owner,
}, {
$addToSet: { // 保证唯一性,subdocument 不能建立唯一索引
inventory: {
item,
itemId: item.id,
status: 'unclaimed',
},
},
}, { upsert: true });
}
function freezeGift(item) {
return basement.db.collection('gifts').updateOne({
inventory: {
$elemMatch: { // 找到数组里对应的礼物
itemId: item.id,
status: 'unclaimed',
},
},
}, {
$set: {
'inventory.$.status': { status: 'transferring' }, // 设置成转移中
},
}).then(res => {
if (res.affectedDocs !== 1) {
return Promise.reject();
}
});
}
function claimGift(to, item) {
return basement.db.collection('gifts').updateOne({
user: to,
}, {
$addToSet: {
inventory: {
item,
itemId: item.id,
status: 'owned'
},
},
}, {
upsert: true,
}).then(res => {
if (res.affectedDocs !== 1) {
return Promise.reject();
}
});
}
function claimUserGift(from, to, item) {
return freezeGift().then(claimGift).then(() => basement.db.collection('gifts').updateOne({
user: from,
inventory: {
$elemMatch: {
itemId: item.id,
status: 'transferring',
},
},
}, {
$set: {
'inventory.$.status': 'claimed',
},
});
}
giveOutGift('alice', {
name: 'umbrella',
id: '5b9642e109d54b4c12d68c7e',
}).then(res => claimUserGift('alice', 'bob', {
name: 'umbrella',
id: '5b9642e109d54b4c12d68c7e',
});