面试题答案
一键面试1. 使用分组与投射操作实现实时统计每个产品每小时的销售总额
在MongoDB中,可以使用聚合框架来实现此需求。以下是具体的聚合管道:
db.sales.aggregate([
// 投射操作,提取需要的字段并格式化时间为小时粒度
{
$project: {
productId: 1,
saleAmount: 1,
saleHour: {
$dateToString: {
format: "%Y-%m-%dT%H:00:00Z",
date: "$saleTime"
}
}
}
},
// 分组操作,按productId和saleHour分组并计算销售总额
{
$group: {
_id: {
productId: "$productId",
saleHour: "$saleHour"
},
totalSaleAmount: {
$sum: "$saleAmount"
}
}
},
// 可选投射操作,使输出格式更友好
{
$project: {
productId: "$_id.productId",
saleHour: "$_id.saleHour",
totalSaleAmount: 1,
_id: 0
}
}
]);
2. 保证在高并发写入时聚合结果的一致性
- 多版本并发控制(MVCC):MongoDB从4.0版本开始支持多文档事务,利用MVCC机制,在事务内进行写入操作时,其他并发的读取操作会读取到事务开始前的版本,从而保证读取的一致性。在统计聚合时,可以在事务边界内进行操作,确保聚合结果是基于一个一致性的数据集。
- 写锁策略:在高并发写入场景下,可以适当调整写锁的粒度和持有时间。例如,尽量使用细粒度的锁,减少锁冲突。对于统计聚合操作,可以考虑在读取数据时获取共享锁,而写入操作获取排他锁,通过锁机制来保障数据的一致性。
3. 性能优化策略
- 索引优化:
- 对
productId
和saleTime
字段建立复合索引,例如db.sales.createIndex({ productId: 1, saleTime: 1 })
。这样在聚合操作时,能够快速定位到需要的数据,提高聚合性能。 - 如果经常按某个特定条件过滤数据后再进行聚合,也可以针对过滤条件字段建立索引。
- 对
- 批量写入:客户端采用批量写入的方式,减少与数据库的交互次数,降低网络开销。在Node.js中,可以使用
bulkWrite
方法,例如:
const salesToInsert = [
{ "productId": ObjectId("640000000000000000000001"), "saleAmount": 200, "saleTime": ISODate("2023 - 11 - 15T14:30:00Z") },
// 更多销售记录
];
db.sales.bulkWrite(salesToInsert.map(sale => ({ insertOne: { document: sale } })));
- 缓存机制:使用缓存(如Redis)来暂存部分聚合结果。对于变化频率较低的聚合数据,可以从缓存中读取,减少直接查询MongoDB的次数。定期更新缓存,以保证数据的实时性。
- 分布式计算:对于大规模数据的聚合操作,可以考虑使用MongoDB的分片集群,将数据分布在多个节点上进行并行计算,提高聚合性能。同时,利用MongoDB的副本集机制,提供高可用性和读扩展性。