04 特色及优势
对象模型,快速响应业务变化:
- 多形性:同一个集合中可以包含不同字段(类型)的文档对象。
- 动态性:线上修改数据模式,修改是应用与数据库均无须下线。
- 数据治理:支持使用JSONSchema 来规范数据模式。在保证模式灵活动态的前提下,提供数据治理能力。
快速的开发:
- 只存储在一个存储区读写。
- 反范式、无关联的组织极大优化查询速度。
- 程序 API 自然,开发速度快。
原生的高可用:
- Replica Set - 2 to 50 个成员,建议单数。
- 自恢复。
- 多中心容灾能力。
- 滚动服务,最小化服务终端。
横向扩展能力:
- 需要的时候无缝扩展。
- 应用全透明。
- 多种数据分布策略。
- 轻松支持 TB-PB 数量级。
06 基本操作
- cloud.mongodb.com 云服务
- 样本数据 | geektime-mongodb-course
tar -xvf dump.tar.gz
mongorestore --uri="mongodb://root:root@10.130.0.12/?&authMechanism=SCRAM-SHA-1"
// 插入
db.fruit.insertOne({name: "apple"})
db.fruit.insertMany([
{name: "apple"},
{name: "pear"},
{name: "orange"},
])
// 查询
db.customers.find({username: "fmiller", name: "Elizabeth Ray"})
db.customers.find({username: /^f/})
db.customers.find({$or: [{username: /^f/}, {name: /^E/}]})
a <> 1
{a: {$ne: 1}}
a > 1
{a: {$gt: 1}}
a >= 1
{a: {$gte: 1}}
a < 1
{a: {$lt: 1}}
a <= 1
{a: {$lte: 1}}
a=1 OR b=1
{$or: [{a: 1}, {b: 1}]}
a IS NULL
{a: {$exists: false}}
a IS (1,2,3)
{a: {$in: [1, 2, 3]}}
// 同时满足
{$eleMatch: {"city": "Rome", "country": "USA"}}
// 投影 projection
// like select
db.customers.find({username: /^f/}, {name: 0, email: 0})
db.customers.find({username: /^f/}, {_id: 1, name: 1})
// remove db.customers.remove({username: "abrown"})
// update
db.customers.updateOne({username: "fmiller"}, {$set: {from: "China"}})
db.customers.updateMany({username: "fmiller"}, {$set: {from: "China"}})
// set unset
// push pushAll $pop 数组操作
// pull pullAll 如果匹配,从数组中删除相应的对象
// $addToSet 如果不存在则增加一个值到数组// drop
db.fruit.drop()
show collections
db
db.dropDatabase()
show dbs
08 聚合查询
Aggregation Framework
- Pipeline
- Stage
pipeline = [stage1, stage2, ...]
db.<collection>.aggregate(
pipeline,
{ option }
)
- match 过滤</li><li>project 投影
- sort 排序</li><li>group 分组
- skip limit 结果限制
- $lookup 左外连接
09 聚合查询实验
// 计算总合计
db.orders.aggregate([
{
$group: {
_id: null,
total: {sum: "total"}
}
}
])
// {
// "_id": null,
// "total": NumberDecimal("44019609")
// }
// 查询 2019 第一季度,已完成订单(completed)总金额(金额+运费)和订单总数
db.orders.aggregate([
{
$match: {
status: "completed",
orderDate: { gte: ISODate("2019-01-01"), lt: ISODate("2019-04-01") }
}
},
{
$group: {
_id: null,
total: { sum: "total" },
shippingFee: { sum: "shippingFee" },
count: { $sum: 1 }
}
},
{
$project: {
grandTotal: { add: ["total", "$shippingFee"] },
count: 1,
_id: 0
}
}
])
// {
// "count": 5875,
// "grandTotal": NumberDecimal("2636376")
// }
10 复制集机制及原理
由3个以上具有投票权的节点组成:
- 1个主节点 PRIMARY:接收写入操作和选举时投票。
- 两个或多个从节点 SECONDARY:复制主节点上的新数据和选举时投票。
数据是如何复制的:
- 当一个修改操作,无论是插入、更新或删除,到达主节点时它对数据的操作将被记录下来(经过些必要的转换),这些记录称为 oplog。
- 从节点通过在主节点上打开一个 tailable 游标不断获取新进入主节点的 oplog,并在自己的数据上回放,以此保持跟主节点的数据一致。
通过选举完成故障恢复:
- 具有投票权的节点之间两两互相发送心跳。
- 当5次心跳未收到时判断为节点失联。
- 如果失联的是主节点,从节点会发起选举,选出新的主节点。
- 如果失联的是从节点则不会产生新的选举。
- 选举基于RAFT一致性算法实现,选举成功的必要条件是大多数投票节点存活。
- 复制集中最多可以有50个节点,但具有投票权的节点最多7个。
影响选举的因素:
- 整个集群必须有大多数节点存活。
- 被选举为主节点的节点必须:
- 能够与多数节点建立连接
- 具有较新的 oplog
- 具有较高的优先级(如果有配置)
复制集节点有以下常见的选配项:
- 是否具有投票权(v 参数):有则参与投票。
- 优先级(priority 参数):优先级越高的节点越优先成为主节点。优先级为0的节点无法成为主节点。
- 隐藏(hidden 参数):复制数据,但对应用不可见。隐藏节点可以具有投票仅,但优先级必须为0。
- 延迟(slaveDelay 参数):复制 n 秒之前的数据,保持与主节点的时间差。
复制集注意事项:
- 关于硬件:
- 因为正常的复制集节点都有可能成为主节点,它们的地位是一样的,因此硬件配置上必须致;
- 为了保证节点不会同时岩机,各节点使用的硬件必须具有独立性。
- 关于软件:
- 复制集各节点软件版本必须一致,以避免出现不可预知的问题。
- 增加节点不会增加系统写性能!
11 搭建 MongoDB 复制集
mkdir -p runtime/data_db{1,2,3} &&
mkdir -p runtime/data_configdb{1,2,3}hostname -f
rs.status()
12 全家桶
- launch-manage | mongodb
- database-tools | mongodb
13 模型设计基础
- Entity
- Attribute
- Relationship
概念模型 CDM -> 逻辑模型 LDM -> 物理模型 PDM
对象 -> 实体、属性、关系 -> 表结构、字段列表、主外建
14 JSON 文档模型设计
无模式的由来:可以省略无论建模的具体过程,物理模型可省。
设计原则:
- 性能 Performance
- 开发易用 Ease of Development
15 基础设计
集合、字段、基础形状 -> 引用及关联 -> 最终模式
业务需求及逻辑模型 –逻辑导向-> 基础建模 —> 集合、字段、基础形状
一个文档 16MB max.
内嵌为主。
16 工况细化
技术需求、读写比例、方式及数据 –技术导向-> 工况细化 —> 引用及关联
引用模式 $lookup:
db.contacts.aggregate([
$lookup: {
form: "groups",
localField: "groups_ids",
foreignField: "groups_id",
as: "groups",
}
])
使用引用方式:
- 内嵌文档太大
- 内嵌文档或数组元素频繁修改
- 内嵌文档数组元素持续增长且没有封顶
使用引用的设计:
- 没有主外键的检查
- lookup 只支持 left outer join</li><li>lookup 的关系目标(from)不能是分片表
17 模式套用
经验和学习 –模式导向-> 套用设计模式 -> 优化的模型
时序数据,分桶设计:利用文档内嵌组,将一个时间段的数据聚合到一个文档里。大量减少文档数据量,大量减少索引占用空间。
18 设计模式集锦
大文档,很多字段,很多索引。列转行。列数据变化为数组。多语言多国家属性,类似字段需要建立很多索引。转化为数组,一个索引解决所有查询问题。
模型灵活了,如何管理文档不同版本?增加一个版本字段。schema_version。
统计网页点击流量。近似计算。if random(0,9)==0 increment by 10。
业绩排名、游戏排名等精确统计。消耗资源多,聚合计算时间长。用预聚合字段。模型中直接增加统计字段,每次更新数据时同时更新统计值。
19 写操作事务 writeConcern
write-concern | mongodb
{ w: <value>, j: <boolean>, wtimeout: <number> }
rs.status();
db.test.drop()
db.test.insert({count:2}, {writeConcern: {w: "majority"}})
db.test.insert({count:2}, {writeConcern: {w: 3}})
db.test.insert({count:2}, {writeConcern: {w: 1}})
// WriteResult({ "nInserted" : 1, "writeConcernError" : [ ] })db.test.insert({count:2}, {writeConcern: {w: 4}})
// [Error] 100 - Not enough data-bearing nodes
db.test.find()
// 配置延迟节点,模拟网络延迟
conf=rs.conf()
// {
// "_id": 3,
// "host": "mongo3:27017",
// "arbiterOnly": false,
// "buildIndexes": true,
// "hidden": false,
// "priority": 1,
// "tags": { },
// "slaveDelay": NumberLong("0"),
// "votes": 1
// }
conf.members[2].slaveDelay=5
// 没有选举权
conf.members[2].priority=0
rs.reconfig(conf)
// {
// "_id": 3,
// "host": "mongo3:27017",
// "arbiterOnly": false,
// "buildIndexes": true,
// "hidden": false,
// "priority": 0,
// "tags": { },
// "slaveDelay": NumberLong("5"),
// "votes": 1
// }db.test.insert({count:2}, {writeConcern: {w: 3}})
// Result Time 5s
// 3s 超时
db.test.insert({count:5}, {writeConcern: {w: 3, wtimeout:3000}})
// writeResult({
// "nInserted" : 1,
// "writeConcernError" : {
// "code" : 64,
// "codeName" : "WriteConcernFailed",
// "errmsg" : "waiting for replication timed out",
// "errInfo" : {
// "wtimeout" : true,
// "writeConcern" : {
// "w" : 3,
// "wtimeout" : 3000,
// "provenance" : "clientSupplied"
// }
// }
// }
// })
20 读操作事务 readPreference
- Read Preference | mongodb
- Read Preference Tags | mongodb
配置:
- mongodb://…mongodb://…/?replicaSet=rs&readPrefence=secondary
- 通过驱动API,MongoCollection.withReadPrefence(ReadPrefence readPref)
- Mongo Shell: db.collection.find({}).readPref(“secondary”)
实验:
// 主节点
db.test.drop()
db.test.insert({count:2}, {writeConcern: {w: 3}})
db.test.find({})
// 1 line
db.test.find({}).readPref("secondary")
// 1 line// 从节点 1、2
db.test.find({})
// 1 line
db.fsyncLock()
// now locked against writes, use db.fsyncUnlock() to unlock 锁住写入// 主节点
db.test.insert({count:3})
db.test.find({})
// 2 line
db.test.find().readPref("secondary")
// 1 line
// 从节点 1、2
db.fsyncUnlock()
// 主节点
db.test.find().readPref("secondary")
21 读操作事务 readConcern
- read-concern | mongodb
enableMajorityReadConcern: true
// 从节点 1、2
db.fsyncLock()
// 主节点
db.test.insert({count:3})
db.test.find().readConcern("local")
// 如果在一个写操作到达大多数节点前读取了这个写操作,然后因为系统故障该操作回滚了,则发生了脏读
// {readConcern: "majority"} 可以避免脏读
db.test.find().readConcern("majority")
majority 约为:Read Committed。
22 多文档事务
- Atomocity 原子性
- Consistency 一致性 writeConcern,readConcern
- Isolation 隔离性 readConcern
- Durability 持久性 Journal and Replication
实验启用事务后的隔离性:
db.tx.drop();
db.tx.insertMany([{x:1},{x:2}]);
var session = db.getMongo().startSession();
session.startTransaction();
var coll = session.getDatabase('test').getCollection('tx');
coll.updateOne({x:2}, {$set: {y:1}});
// {
// acknowledged: true,
// insertedId: null,
// matchedCount: 1,
// modifiedCount: 1,
// upsertedCount: 0
// }
coll.find();
// [
// { _id: ObjectId("64478074fcfac90fb90f1a65"), x: 1 },
// { _id: ObjectId("64478074fcfac90fb90f1a66"), x: 2, y: 1 }
// ]
db.tx.find();
// [
// { _id: ObjectId("64478074fcfac90fb90f1a65"), x: 1 },
// { _id: ObjectId("64478074fcfac90fb90f1a66"), x: 2 }
// ]
session.commitTransaction();
db.tx.find();
实验可重复读 Repeatable Read:
db.tx.drop();
db.tx.insertMany([{x:1},{x:2}]);
var session = db.getMongo().startSession();
session.startTransaction({
readConcern: {"level": "snapshot"},
writeConcern: {"w": "majority"}
});
var coll = session.getDatabase('test').getCollection('tx');
coll.findOne({x: 1});
// 模拟事务之外的操作
db.tx.updateOne({x:1}, {$set: {y:1}});
db.tx.find();
// [
// { _id: ObjectId("644782c3fcfac90fb90f1a69"), x: 1, y: 1 },
// { _id: ObjectId("644782c3fcfac90fb90f1a6a"), x: 2 }
// ]
coll.findOne({x: 1});
// { _id: ObjectId("644782c3fcfac90fb90f1a69"), x: 1 }
session.abortTransaction();
实验写冲突:
db.tx.drop();
db.tx.insertMany([{x:1},{x:2}]);
// shell 1、2
var session = db.getMongo().startSession();
session.startTransaction({
readConcern: {"level": "snapshot"},
writeConcern: {"w": "majority"}
});
var coll = session.getDatabase('test').getCollection('tx');
// shell 1
coll.updateOne({x:1}, {$set: {y:1}});
// ok
// shell 2
coll.updateOne({x:1}, {$set: {y:1}});
// MongoServerError: WriteConflict error: this operation conflicted with another operation. Please retry your operation or multi-document transaction.
// session.abortTransaction();
- 事务默认 60s 内完成。
- 多文档事务中的读操作必须使用主节点读。
23 Change Stream
类似触发器。
- 触发方式:异步 | 同步(事务保证)
- 触发位置:回调事件 | 数据库触发器
- 触发次数:每个订阅事件的客户端 | 1次
- 故障恢复:从上此断点重新触发 | 事务回滚
基于 oplog 实现。被追踪的变更事件主要包括:
- insert/update/delete
- drop
- rename
- dropDatabase
- invalidate:drop/rename/dropDatabase 将导致 invalidate 被触发,并关闭 chage stream
可重复读。未开启 majority readConcern 的集群无法使用 Change Stream。当集群无法满足 {w: “majority”} 时,不会触发 Change Stream。
可以使用集合管道的过滤步骤过滤事件。
25 分片集群机制及原理
- 路由节点 mongos
- 配置节点 mongod
- 数据节点 mongod
分片集群数据发布方式
- 基于范围 查询性能好,数据分布可能不均匀
- 基于哈希 发布均匀,范围查询效率低;日志、物联网
- Zone 数据天然分区
25 分片集群设计
- 片键 shard key 文档中的一个字段
- 文档 doc
- 块 chunk
- 分片 shard
- 集群 cluster
片键:
- 取值基数范围要大
- 取值范围应尽可能均匀
- 对主要查询要具有定向能力
组合片键。{user_id:1, time:1}
27 分片集群搭建及扩容
# 1 配置域名解析
2 准备分片目录
3 创建第1个分片复制集 member1:27010 member3:27010 member5:27010
mongod --bind_ip_all --replSet shard1 --shardsvr --wiredTigerCacheSizeGB 1
--shardsvr 标注为分片节点
--wiredTigerCacheSizeGB 1 缓存大小
4 初始化分片复制集
5 创建 config server 复制集 member1:27019 member3:27019 member5:27019
mongod --bind_ip_all --replSet config --configsvr --wiredTigerCacheSizeGB 1
--configsvr 标注为配置节点
6 创建 config server 复制集
7 搭建 mongos member1:27017
mongos --bind_ip_all --configdb config/member1:27019,member3:27019,member5:27019,
连接到 mongos 添加分片
sh.addShard("shard1/member1:27010,member3:27010,member5:27010")
8 创建分片表
连接到 mongos member1:27017
sh.enableSharding("foo");
sh.shardCollection("foo.bar", {_id: "hashed"});
sh.status();插入测试数据
use foo
for(var i=0;1<10000;i++) {
db.bar.insert({i:i))
}9 创建第2个分片复制集 member2:27011 member4:27011 member6:27011
10 初始化第2个分片复制集
11 加入第2个分片
连接到 mongos 添加分片
sh.addShard("shard2/member2:27011,member4:27011,member6:27011")
28 监控最佳实践
- MongoDB Ops Manager
- Percona
- 程序脚本
如何获取监控数据:
- db.serverStatus() 从上次开机到现在为止
- db.isMaster()
- monogostats
- db.serverStatus().opcounters
29 备份与恢复
- 延迟节点备份
- 全量备份+Oplog增量,Oplog 幂等性 支持重放
- mongodump –oplog;不遗漏 dump 时的数据
- mongorestore –oplogReplay
31 安全架构
db.createRole({
role: "readWriteRole",
privileges: [
{
resource: { db: "myDatabase", collection: "sample" },
actions: [ "find", "insert", "update", "remove" ]
}
],
roles: [{
role: 'read',
db: 'sampledb',
}]
})
db.createUser({
user: 'sampleUser',
pwd: 'pwd',
roles: [{
role: 'readWriteRole',
db: 'admin',
}]
})
32 安全加固实践
# 禁止脚本执行
--noscripting必须鉴权
--auth
33 索引机制(一)
- Index、Key、DataPage
- Covered Query/FETCH
- IXSCAN/COLLSCAN 索引扫描/集合扫描
- Big O Notation 时间复杂度
- Query Shape 查询的形状
- Index Prefix 索引前缀
- Selectivity 过滤器 选择区别度大的
- B-数结构
B-树和B+树都是常见的多路搜索树结构,常用于数据库索引和文件系统中。它们的主要区别在于如何存储和检索数据。
B-树是一种自平衡的搜索树,其中每个节点可以存储多个键和对应的值,并支持在O(log n)时间内进行搜索、插入和删除操作。B-树的每个节点都包含了一个子节点数组,可以用来搜索和遍历树。在B-树中,所有节点都可以存储键和值,而非仅仅是叶子节点。
B+树与B-树非常相似,但是只有叶节点包含了所有的键和值,而且所有叶节点都通过指针链接在一起。这意味着在B+树上进行查找只需要搜索一条从根节点到叶节点的路径,而在B-树中可能需要搜索多个节点。B+树的非叶子节点只包含键,而不包含值,这使得B+树在维护索引时更加高效。
因此,B+树比B-树更适用于存储和检索大量数据,尤其是数据库和文件系统中的索引。B+树的叶子节点形成了一个有序链表,可以方便地进行区间查找和遍历。而B-树则更适合内存较小的情况下,例如缓存。
34 索引机制(二)
.explain(true) 查看:
executionTimeMillis
totalDocsExamined
executionStages.docsExamined
executionStages.inputStage.stage
组合索引 ESR 原则,Equal(最前) Sort(中间) Range(最后)
db.member.createIndex({city:1}, {background: true})
36 性能诊断工具
mongostat 了解 MongoDB 运行状态的工具。
- dirty 没有刷盘的比例 <5%
- used
- qrw 排队的请求
- con 连接数量
mongotop 了解集合压力状态
mtools
41 应用场景及选型
优点:
- 横向扩展能力,数据量或并发量增加时候架构可以自动扩容
- 灵活模型,适合迭代开发,数据模型多变场景
- JSON 数据结构,适合微服务/REST API
44 关系型数据库迁移
从基于关系型数据库应用迁移到 MongoDB 的理由:
- 高并发需求(数千 - 数十万 ops),关系型数据库不容易扩展
- 快速迭代 - 关系型模式太严谨
- 灵活的 JSON 模式
- 大数据量需求
- 地理位置查询
- 多数据中心跨地域部署
References
- MongoDB 高手课
- mongodb-cluster-docker-compose
– EOF –
- # mongodb