|
|
|
|
## 数据模型设计
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 1. 模型设计基础
|
|
|
|
|
|
|
|
|
|
- 数据模型设计的元素
|
|
|
|
|
- 实体
|
|
|
|
|
- 描述业务的主要数据集合
|
|
|
|
|
- 谁, 什么, 何时, 何地, 为何, 如何
|
|
|
|
|
- 属性
|
|
|
|
|
- 描述实体里面的单个信息
|
|
|
|
|
- 关系
|
|
|
|
|
- 描述实体与实体之间的数据规则
|
|
|
|
|
- 结构规则: 1-N, N-1, N-N
|
|
|
|
|
- 引用规则: 电话号码不能单独存在
|
|
|
|
|
|
|
|
|
|
- 传统模型设计
|
|
|
|
|
- ![传统模型设计.png](pic/传统模型设计.png)
|
|
|
|
|
- 架构师要产出 LDM 给开发者
|
|
|
|
|
- 逻辑模型
|
|
|
|
|
- 实体, 属性, 函数名称, 实体间关系
|
|
|
|
|
- 开发者: 第三范式下的物理模型
|
|
|
|
|
- 数据在库里尽量不可能存在冗余
|
|
|
|
|
|
|
|
|
|
### 2. JSON 文档模型的设计特点
|
|
|
|
|
|
|
|
|
|
- MongoDB 文档模型设计的三个误区
|
|
|
|
|
- 不需要模型设计
|
|
|
|
|
- MongoDB 应用用一个超级大文档来组织所有数据
|
|
|
|
|
- MongoDB 不支持关联或者事务
|
|
|
|
|
|
|
|
|
|
- 关于 JSON 文档模型设计
|
|
|
|
|
- 文档模型设计处于是物理模型设计阶段 (PDM)
|
|
|
|
|
- JSON 文档模型通过内嵌数组或引用字段来表示关系
|
|
|
|
|
- 文档模型设计不遵从第三范式, 允许冗余
|
|
|
|
|
- 流程: 概念建模 -> 逻辑建模 -> 文档模型 / 关系模型
|
|
|
|
|
|
|
|
|
|
- 为什么人们都说 MongoDB 是无模式
|
|
|
|
|
- 严格来说, MongoDB 同样需要概念/逻辑建模
|
|
|
|
|
- 文档建模设计的物理层结构可以和逻辑层类似
|
|
|
|
|
- MongoDB 无模式由来:
|
|
|
|
|
- **可以省略物理建模的具体过程**
|
|
|
|
|
- 概念模型 -> 逻辑模型 -(复用)-> 物理模型
|
|
|
|
|
|
|
|
|
|
- JSON 模型和逻辑模型对比
|
|
|
|
|
- ![JSON 模型和逻辑模型对比.png](pic/JSON%20模型和逻辑模型对比.png)
|
|
|
|
|
|
|
|
|
|
- **文档模型的设计原则: 性能和易用**
|
|
|
|
|
|
|
|
|
|
- 关系模型 vs 文档模型
|
|
|
|
|
- ![关系模型 vs 文档模型.png](pic/关系模型%20vs%20文档模型.png)
|
|
|
|
|
|
|
|
|
|
### 3. 文档模型三步走
|
|
|
|
|
|
|
|
|
|
- MongoDB 文档模型设计三步曲 - 总览
|
|
|
|
|
- ![MongoDB文档模型设计三步曲.png](pic/MongoDB文档模型设计三步曲.png)
|
|
|
|
|
|
|
|
|
|
- **第一步: 建立基础文档模型**
|
|
|
|
|
- 根据概念模型或者业务需求推导出逻辑模型 - 找到对象
|
|
|
|
|
- 列出实体之间的关系(及基数) - 明确关系
|
|
|
|
|
- 套用逻辑设计原则来决定内嵌方式 - 进行建模
|
|
|
|
|
- 完成基础模型构建
|
|
|
|
|
- 业务需求及逻辑模型 -(逻辑导向)-> 基础建模 -> 集合/字段/基础形状
|
|
|
|
|
|
|
|
|
|
- 举例
|
|
|
|
|
- 找到对象:
|
|
|
|
|
- Contacts
|
|
|
|
|
- Groups
|
|
|
|
|
- Address
|
|
|
|
|
- Portraits
|
|
|
|
|
- 明确关系
|
|
|
|
|
- 一个联系人有一个头像(1-1)
|
|
|
|
|
- 一个联系人可以有多个地址(1-N)
|
|
|
|
|
- 一个联系人可以属于多个组, 一个组可以有多个联系人(N-N)
|
|
|
|
|
- 表示:
|
|
|
|
|
- Groups(name) <-[N-N]-> Contacts (name,company,title) <-[1-N]-> Addresses(type,street,city,state,zip_code)
|
|
|
|
|
- Contacts(...) <-[1-1]-> Portraits(mine_type,date)
|
|
|
|
|
|
|
|
|
|
- 1-1 关系建模: portraits
|
|
|
|
|
- 基本原则: **一对一关系以内嵌为主, 作为子文档形式或者直接在顶级不涉及到数据冗余**
|
|
|
|
|
- 例外情况: **如果内嵌后导致文档大小超过16MB**
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
name: "TJ Tang",
|
|
|
|
|
company: "TAPDATA",
|
|
|
|
|
title: "CTO",
|
|
|
|
|
portraits: {
|
|
|
|
|
mimetype: xxx,
|
|
|
|
|
date: xxxx
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
- 1-N 关系建模: Addresses
|
|
|
|
|
- 基本原则: **一对多关系同样以内嵌为主, 用数组来表示一对多不涉及到数据冗余**
|
|
|
|
|
- 例外情况: **内嵌后导致文档大小超过 16 MB, 数组长度太大(数万或更多) 数组长度不确定**
|
|
|
|
|
- Groups(name) <-[N-N]->
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
name: "TJ Tang",
|
|
|
|
|
company: "TAPDATA",
|
|
|
|
|
title: "CTO",
|
|
|
|
|
portraits: {
|
|
|
|
|
mimetype: xxx,
|
|
|
|
|
date: xxxx
|
|
|
|
|
},
|
|
|
|
|
addresses: {
|
|
|
|
|
{type: home, ...},
|
|
|
|
|
{type: work, ...},
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
- N-N 关系建模: 内嵌数组模式
|
|
|
|
|
- 基本原则: **不需要映射, 一般用内嵌数组来表示一对多, 通过冗余来实现 N-N**
|
|
|
|
|
- 例外情况: **内嵌后导致文档大小超过 16 MB, 数组长度太大(数万或更多) 数组长度不确定**
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
name: "TJ Tang",
|
|
|
|
|
company: "TAPDATA",
|
|
|
|
|
title: "CTO",
|
|
|
|
|
portraits: {
|
|
|
|
|
mimetype: xxx,
|
|
|
|
|
date: xxxx
|
|
|
|
|
},
|
|
|
|
|
addresses: {
|
|
|
|
|
{type: home, ...},
|
|
|
|
|
{type: work, ...},
|
|
|
|
|
},
|
|
|
|
|
groups: {
|
|
|
|
|
{name: :"Friends"}
|
|
|
|
|
{name: :"Surfers"}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
- **第二步: 根据读写工况细化**
|
|
|
|
|
- 技术需求, 读写比例, 方式及数量 -(技术导向)-> 工况细化 -> [引用及关联]
|
|
|
|
|
- 考虑的问题
|
|
|
|
|
- 最频繁的数据查询模式
|
|
|
|
|
- 最常用的查询参数
|
|
|
|
|
- 最频繁的数据写入模式
|
|
|
|
|
- 读写操作的比例
|
|
|
|
|
- 数据量的大小
|
|
|
|
|
- 基于内嵌的文档模型
|
|
|
|
|
- 根据业务需求
|
|
|
|
|
- 使用引用来避免性能瓶颈
|
|
|
|
|
- 使用冗余来优化访问性能
|
|
|
|
|
|
|
|
|
|
- 举例
|
|
|
|
|
- 联系人管理应用分组需求
|
|
|
|
|
- 用于客户营销
|
|
|
|
|
- 有千万级联系人
|
|
|
|
|
- 需要频繁变动分组的信息, 如增加分组及修改名称及描述以及营销状态
|
|
|
|
|
- 一个分组可以有百万级联系人
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
name: "TJ Tang",
|
|
|
|
|
company: "TAPDATA",
|
|
|
|
|
title: "CTO",
|
|
|
|
|
portraits: {
|
|
|
|
|
mimetype: xxx,
|
|
|
|
|
date: xxxx
|
|
|
|
|
},
|
|
|
|
|
addresses: {
|
|
|
|
|
{type: home, ...},
|
|
|
|
|
{type: work, ...},
|
|
|
|
|
},
|
|
|
|
|
groups: {
|
|
|
|
|
{name: :"Friends"}
|
|
|
|
|
{name: :"Surfers"}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
```json
|
|
|
|
|
name: "Mona Jang",
|
|
|
|
|
company: "HUAXIA",
|
|
|
|
|
title: "DIRECTOR",
|
|
|
|
|
portraits: {
|
|
|
|
|
mimetype: xxx,
|
|
|
|
|
date: xxxx
|
|
|
|
|
},
|
|
|
|
|
addresses: {
|
|
|
|
|
{type: home, ...},
|
|
|
|
|
{type: work, ...},
|
|
|
|
|
},
|
|
|
|
|
groups: {
|
|
|
|
|
{name: :"Friends"}
|
|
|
|
|
{name: :"Surfers"}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
- 一个分组(groups)的改动意味着百万级的DB操作
|
|
|
|
|
- 解决方案: Group 使用单独的集合
|
|
|
|
|
- 类似于关系型设计
|
|
|
|
|
- 用 id 或者唯一键关联
|
|
|
|
|
- 使用 $lookup 来提供一次查询多表的能力(类似关联)
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
name: "Mona Jang",
|
|
|
|
|
company: "HUAXIA",
|
|
|
|
|
title: "DIRECTOR",
|
|
|
|
|
portraits: {
|
|
|
|
|
mimetype: xxx,
|
|
|
|
|
date: xxxx
|
|
|
|
|
},
|
|
|
|
|
addresses: {
|
|
|
|
|
{type: home, ...},
|
|
|
|
|
{type: work, ...},
|
|
|
|
|
},
|
|
|
|
|
group_ids: [1,2,3]
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
Groups
|
|
|
|
|
group_id
|
|
|
|
|
name
|
|
|
|
|
```
|
|
|
|
|
- 使用 group_id 进行关联
|
|
|
|
|
- 此时查询语句
|
|
|
|
|
```spring-mongodb-json
|
|
|
|
|
db.contacts.aggregate([
|
|
|
|
|
{
|
|
|
|
|
$lookup:{
|
|
|
|
|
from: "groups",
|
|
|
|
|
localField: "group_ids",
|
|
|
|
|
foreignField: "group_id",
|
|
|
|
|
as: "groups"
|
|
|
|
|
}
|
|
|
|
|
}])
|
|
|
|
|
```
|
|
|
|
|
- aggregate 的 $lookup 来实现关联查询
|
|
|
|
|
|
|
|
|
|
- 例子2: 联系人的头像: 引用模式
|
|
|
|
|
- 头像使用高保真, 大小在 5MB - 10MB
|
|
|
|
|
- 头像一旦上传, 一个月不可变更
|
|
|
|
|
- 基础信息查询(不含头像) 和 头像查询的比例为 9 : 1
|
|
|
|
|
- 建议: 使用引用方式, 把头像数据放到另外一个集合, 可以显著提升 90% 的查询效率
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
name: "Mona Jang",
|
|
|
|
|
company: "HUAXIA",
|
|
|
|
|
title: "DIRECTOR",
|
|
|
|
|
addresses: {
|
|
|
|
|
{type: home, ...},
|
|
|
|
|
{type: work, ...},
|
|
|
|
|
},
|
|
|
|
|
group_ids: [1,2,3]
|
|
|
|
|
```
|
|
|
|
|
```json
|
|
|
|
|
Contact_Portrait: {
|
|
|
|
|
_id: 123,
|
|
|
|
|
mimetype: xxx,
|
|
|
|
|
data: xxxx
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
- 什么时候该使用引用方式?
|
|
|
|
|
- 内嵌文档太大, 数 MB 或者超过 16MB
|
|
|
|
|
- 内嵌文档或数组元素会频繁修改
|
|
|
|
|
- 内嵌数组元素会持续增长并且没有封顶
|
|
|
|
|
|
|
|
|
|
- MongoDB 引用设计的限制
|
|
|
|
|
- MongoDB 对使用引用的集合之间并无主外键检查
|
|
|
|
|
- MongoDB 使用聚合框架的 $lookup 来模仿关联查询
|
|
|
|
|
- $lookup 只支持 left outer join
|
|
|
|
|
- $lookup 的关联目标(from) 不能是分片表
|
|
|
|
|
|
|
|
|
|
- **第三步: 套用设计模式**
|
|
|
|
|
- 文档模式: 无范式, 无思维定式, 充分发挥想象力
|
|
|
|
|
- 设计模式: 实战过屡试不爽的设计技巧, 快速应用
|
|
|
|
|
- 举例: 一个 loT 场景的分桶设计模式, 可以帮助把存储空间降低10倍并且查询效率提升数十倍
|
|
|
|
|
- 经验和学习 -(模式导向)-> 套用设计模式 -> 优化的模型
|
|
|
|
|
|
|
|
|
|
- 问题: 物联网场景下的海量数据处理 - 飞机监控数据
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
{
|
|
|
|
|
"_id" : "20160101050000:CA2790",
|
|
|
|
|
"icao": "CA2790",
|
|
|
|
|
"callsign": "CA2790",
|
|
|
|
|
"ts": ISODate("2016-01-01T05:00:00.000+0000"),
|
|
|
|
|
"events": {
|
|
|
|
|
"a": 31418,
|
|
|
|
|
"b" : 173,
|
|
|
|
|
"s" :91,
|
|
|
|
|
"v" : 80
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
- 实际问题: 520亿条, 10TB - 海量数据
|
|
|
|
|
- 10万架飞机
|
|
|
|
|
- 1年的数据
|
|
|
|
|
- 每分钟1条
|
|
|
|
|
- ![飞机-海量数据.png](pic/飞机-海量数据.png)
|
|
|
|
|
- 52.6B = 100000 * 365 * 24 * 60
|
|
|
|
|
- 6364GB = 100000 * 365 * 24 * 60 * 130
|
|
|
|
|
- 4503GB = 100000 * 365 * 24 * 60 * 92
|
|
|
|
|
|
|
|
|
|
- 解决方案: 分桶设计
|
|
|
|
|
```json
|
|
|
|
|
{
|
|
|
|
|
" id" :"20160101050000:WG9943",
|
|
|
|
|
"icao":"WG9943",
|
|
|
|
|
"ts":ISODate("2016-01-01T05:00:00.000+0000"),
|
|
|
|
|
"events":[
|
|
|
|
|
{
|
|
|
|
|
"a":24293, "b":319, "p":[41,70], "s":56,
|
|
|
|
|
"t": ISODate("2016-01-01T05:00:00.000+0000")
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"a":33663, "b":134, "p":[-38, -30], "s":385,
|
|
|
|
|
"t": ISODate("2016-01-01T05:00:01.000+0000")
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
- 60 events == 1小时的数据
|
|
|
|
|
- 一个文档: 一架飞机一个小时的数据
|
|
|
|
|
- ![飞机-海量数据-分桶.png](pic/飞机-海量数据-分桶.png)
|
|
|
|
|
- 可视化表现24小时的飞行数据
|
|
|
|
|
- 1440次读
|
|
|
|
|
|
|
|
|
|
- 模式小结:
|
|
|
|
|
- 场景
|
|
|
|
|
- 时序数据
|
|
|
|
|
- 物联网
|
|
|
|
|
- 智慧城市
|
|
|
|
|
- 智慧交通
|
|
|
|
|
- 痛点
|
|
|
|
|
- 数据点采频繁, 数据量太多
|
|
|
|
|
- 设计模式的方案及优点
|
|
|
|
|
- 利用文档内嵌数组, 将一个时间段的数据聚合到一个文档里
|
|
|
|
|
- 大量减少文档数量
|
|
|
|
|
- 大量减少索引占用空间
|
|
|
|
|
|
|
|
|
|
### 4. 设计模式集锦
|
|
|
|
|
|
|
|
|
|
- 问题: 大文档, 很多字段, 很多索引
|
|
|
|
|
```json
|
|
|
|
|
{
|
|
|
|
|
title: "Dunkirk",
|
|
|
|
|
release USA:"2017/07/23",
|
|
|
|
|
release UK:"2017/08/01",
|
|
|
|
|
release France:"2017/08/01",
|
|
|
|
|
release Festival San Jose:"2017/07/22"
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
- 需要很多索引
|
|
|
|
|
```json
|
|
|
|
|
{release_UsA:1}
|
|
|
|
|
{release_UK:1}
|
|
|
|
|
{release_France:1}
|
|
|
|
|
{release_Festival_San_Jose:1 }
|
|
|
|
|
```
|
|
|
|
|
- 电影的上映时间
|
|
|
|
|
|
|
|
|
|
- 解决方法: 列转行
|
|
|
|
|
```json
|
|
|
|
|
{
|
|
|
|
|
title: "Dunkirk",
|
|
|
|
|
releases:[
|
|
|
|
|
{country:"USA",date:"2017/7/23"},
|
|
|
|
|
{country:"Uk",date:"2017/08/01”}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
- 转换后: 字段数变少了
|
|
|
|
|
- 创建索引: db.movies.createIndex({"releases.country":1, "releases.date":1})
|
|
|
|
|
|
|
|
|
|
- 模式小结:
|
|
|
|
|
- 场景: 产品属性多, 多语言(多国家属性)
|
|
|
|
|
- 痛点: 文档中有很多类似的字段, 会用于组合查询搜索, 需要创建很多索引
|
|
|
|
|
- 设计模式方案及优点: 转化为数组, 一个索引解决所有查询问题
|
|
|
|
|
|
|
|
|
|
- 问题: 模型灵活了, 如何管理文档的不同版本
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
"id":ObiectId("5de26f197edd62c5d388babb"),
|
|
|
|
|
"name":"TJ",
|
|
|
|
|
"Tapdata""company":
|
|
|
|
|
```
|
|
|
|
|
- v1.0
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
"id":obiectId("5de26f197edd62c5d388babb")
|
|
|
|
|
"name":"TJ"
|
|
|
|
|
"company":"Tapdata"
|
|
|
|
|
"wechat":"titang826"
|
|
|
|
|
"schema_version": "2.0"
|
|
|
|
|
```
|
|
|
|
|
- v2.0
|
|
|
|
|
- 解决方案:schema_version 使用这个字段进行区分标识
|
|
|
|
|
|
|
|
|
|
- 模式小结:版本字段
|
|
|
|
|
- 场景: 任何有版本衍生的数据库
|
|
|
|
|
- 痛点: 文档模型格式多, 无法知道其合理性, 升级时候需要更新太多文档
|
|
|
|
|
- 设计模式及优点: 增加一个版本号字段, 快速过滤掉不需要升级的文档, 升级时候对不同的文档做不同的升级
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- 问题: 统计网页点击流量
|
|
|
|
|
- 每访问一个页面就会产生一次数据库计数更新操作
|
|
|
|
|
- 统计数字准确性并不十分重要
|
|
|
|
|
|
|
|
|
|
- 解决方案: 用近似计算
|
|
|
|
|
- 每隔 10(X)次写一次
|
|
|
|
|
- Increment by 10(X)
|
|
|
|
|
- {$inc:{views:1}}
|
|
|
|
|
- if random(0,9) == 0 increment by 10
|
|
|
|
|
|
|
|
|
|
- 模式小结: 近似计算
|
|
|
|
|
- 场景:
|
|
|
|
|
- 网页计数
|
|
|
|
|
- 各种结果不需要准确的排名
|
|
|
|
|
- 痛点:
|
|
|
|
|
- 写入太频繁, 消耗系统资源
|
|
|
|
|
- 设计模式方案及优点
|
|
|
|
|
- 间隔写入, 每隔10次或者100次
|
|
|
|
|
- 大量减少写入需求
|
|
|
|
|
|
|
|
|
|
- 问题: 业绩排名, 游戏排名, 商品统计等精确统计
|
|
|
|
|
- 热销榜: 某个商品今天卖了多少, 这个星期卖了多少, 这个月卖了多少?
|
|
|
|
|
- 电影排行: 观影者, 场次统计
|
|
|
|
|
- 传统解决方案: 通过聚合计算
|
|
|
|
|
- 痛点: 消耗资源多, 聚合计算时间长
|
|
|
|
|
|
|
|
|
|
- 解决方案: 用预聚合字段
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
{
|
|
|
|
|
product:"Bike"
|
|
|
|
|
sku:“abc123456”
|
|
|
|
|
quantitiy:20394,
|
|
|
|
|
daily_sales: 40,
|
|
|
|
|
weekly sales:302,
|
|
|
|
|
monthly_sales:1419
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
```spring-mongodb-json
|
|
|
|
|
db.inventory.update({_id:123},{
|
|
|
|
|
$inc:{
|
|
|
|
|
quantity:-1,
|
|
|
|
|
daily_sales: 1,
|
|
|
|
|
weekly_sales:1,
|
|
|
|
|
monthly_sales: 1
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
- 模式小结: 预聚合
|
|
|
|
|
- 场景: 准确排名, 排行榜
|
|
|
|
|
- 痛点: 统计计算耗时, 计算时间长
|
|
|
|
|
- 设计模式方案及优点: 模型中直接增加统计字段, 每次更新数据时候同时更新统计值
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 分片和集群 - [运维]
|
|
|
|
|
|
|
|
|
|
### 1. 分片集群机制及原理
|
|
|
|
|
|
|
|
|
|
- MongoDB 常见的部署架构
|
|
|
|
|
- ![MongoDB常见的部署架构.png](pic/MongoDB常见的部署架构.png)
|
|
|
|
|
|
|
|
|
|
- 为什么要使用分片集群?
|
|
|
|
|
- 数据容量日益增大, 访问性能日渐降低, 怎么破?
|
|
|
|
|
- 新品上线异常火爆, 如何支撑更多的并发用户?
|
|
|
|
|
- 单库已有 10TB 数据, 恢复需要 1-2 天, 如何加速?
|
|
|
|
|
- 地理分布数据
|
|
|
|
|
|
|
|
|
|
- 分片如何解决?
|
|
|
|
|
- 银行交易单表内10亿笔资料超负荷运转
|
|
|
|
|
- 交易号 0 - 1,000,000,000
|
|
|
|
|
|
|
|
|
|
- 思路:
|
|
|
|
|
- 交易号: 0 - 500,000,000 -> mongod[分片1]
|
|
|
|
|
- 交易号: 500,000,000 - 1,000,000,000 -> mongod[分片2]
|
|
|
|
|
- 还可以再细分[最多1024片]
|
|
|
|
|
|
|
|
|
|
- 分片集群剖析 - 路由节点 mongos
|
|
|
|
|
- ![分片节点剖析.png](pic/分片节点剖析.png)
|
|
|
|
|
|
|
|
|
|
- 分片集群剖析 - 配置节点 mongod
|
|
|
|
|
- ![配置节点mongod.png](pic/配置节点mongod.png)
|
|
|
|
|
|
|
|
|
|
- 分片集群剖析 - 数据节点 mongod
|
|
|
|
|
- ![数据节点mongod.png](pic/数据节点mongod.png)
|
|
|
|
|
|
|
|
|
|
- MongoDB 分片集群特点
|
|
|
|
|
- 应用全透明, 无特殊处理
|
|
|
|
|
- 数据自动均衡
|
|
|
|
|
- 动态扩容, 无须下线
|
|
|
|
|
- 提供三种分片方式
|
|
|
|
|
|
|
|
|
|
- 分片集群数据分布方式
|
|
|
|
|
- 基于范围
|
|
|
|
|
- ![分片集群数据分布方式-基于范围.png](pic/分片集群数据分布方式-基于范围.png)
|
|
|
|
|
- 基于 Hash
|
|
|
|
|
- ![分片集群数据分布方式-基于Hash.png](pic/分片集群数据分布方式-基于Hash.png)
|
|
|
|
|
- 基于 zone/tag
|
|
|
|
|
- ![分片集群数据分布方式-基于zone.png](pic/分片集群数据分布方式-基于zone.png)
|
|
|
|
|
|
|
|
|
|
- 小结:
|
|
|
|
|
- 分片集群可以有效解决性能瓶颈及系统扩容问题
|
|
|
|
|
- 分片消耗较多, 管理复杂, 尽量不要分片
|
|
|
|
|
- 详细了解学习后再进行分片
|
|
|
|
|
|
|
|
|
|
### 2. 分片集群的设计
|
|
|
|
|
|
|
|
|
|
- 如何用好分片集群
|
|
|
|
|
- 合理的架构
|
|
|
|
|
- 是否需要分片?
|
|
|
|
|
- 需要多少分片?
|
|
|
|
|
- 数据的分布规则
|
|
|
|
|
- 正确的姿势
|
|
|
|
|
- 选择需要分片的表
|
|
|
|
|
- 选择正确的片键
|
|
|
|
|
- 使用合适的均衡策略
|
|
|
|
|
- 足够的资源
|
|
|
|
|
- CPU
|
|
|
|
|
- RAM
|
|
|
|
|
- 存储
|
|
|
|
|
|
|
|
|
|
- 合理的架构 - 分片大小
|
|
|
|
|
- 分片的基本标准
|
|
|
|
|
- 关于数据: 数据量不超过 3TB, 尽可能保持在 2TB一个片;
|
|
|
|
|
- 关于索引: 常用索引必须容纳进内存
|
|
|
|
|
- 按照以上标准初步确定分片后, 还需要考虑业务压力, 随着压力增大, CPU, RAM, 磁盘中的任何一项出现瓶颈时, 都可以通过添加更多分片来解决。
|
|
|
|
|
|
|
|
|
|
- 合理的架构 - 需要多少个分片
|
|
|
|
|
- A = 所需存储总量/单服务器可挂载容量 8TB/2TB = 4
|
|
|
|
|
- B = 工作集大小/单服务器内存容量 400GB/ (256G*0.6) = 3
|
|
|
|
|
- C = 并发量总数/ (单服务器并发量*0.7) 30000/(9000*0.7) = 6 [额外开销]
|
|
|
|
|
- 分片数量 = max(A, B, C)=6
|
|
|
|
|
|
|
|
|
|
- 合理的架构 - 其他需求
|
|
|
|
|
- 考虑分片的分布:
|
|
|
|
|
- 是否需要跨机房分布分片?
|
|
|
|
|
- 是否需要容灾?
|
|
|
|
|
- 高可用的要求如何?
|
|
|
|
|
|
|
|
|
|
- 正确的姿势
|
|
|
|
|
- ![正确的姿势-概念图.png](pic/正确的姿势-概念图.png)
|
|
|
|
|
- 各种概念由小到大
|
|
|
|
|
- 片键 shard key: 文档中的一个字段
|
|
|
|
|
- 文档 doc: 包含 shard key 的一行数据
|
|
|
|
|
- 块 Chunk: 包含 n个文档
|
|
|
|
|
- 分片 Shard: 包含 n个 chunk
|
|
|
|
|
- 集群 Cluster: 包含 n个 分片
|
|
|
|
|
|
|
|
|
|
- 正确的姿势 - 选择合适的片键
|
|
|
|
|
- 影响片键效率的主要因素
|
|
|
|
|
- 取值基数(Cardinality)
|
|
|
|
|
- 取值分布
|
|
|
|
|
- 分散写, 集中读
|
|
|
|
|
- 被尽可能的业务场景用到
|
|
|
|
|
- 避免单调递增或递减的片键
|
|
|
|
|
|
|
|
|
|
- 正确的姿势 - 选择基数大的片键
|
|
|
|
|
- 对于小基数的片键:
|
|
|
|
|
- 因为备选值有限, 那么块的总数量就有限
|
|
|
|
|
- 随着数据增多, 块的大小会越来越大
|
|
|
|
|
- 太大的块, 会导致水平扩展时移动块会非常困难
|
|
|
|
|
- 例如: 存储一个高中的师生数据, 以年龄(假设年龄范围为15-65岁)作为片键, 那么:
|
|
|
|
|
- 15 <= 年龄 <= 65, 且只为整数
|
|
|
|
|
- 最多只会有 51个 chunk
|
|
|
|
|
- 结论: 取值基数要大
|
|
|
|
|
|
|
|
|
|
- 正确的姿势 - 选择分布均匀的片键
|
|
|
|
|
- 对于分布不均匀的片键
|
|
|
|
|
- 造成某些块的数据量急剧增大
|
|
|
|
|
- 这些块压力随之增大
|
|
|
|
|
- 数据均衡以chunk为单位, 所以系统无能为力
|
|
|
|
|
- 例如: 存储一个学校的师生数据, 以年龄(假设年龄范围为15-65岁)作为片键,那么:
|
|
|
|
|
- 15 <= 年龄 <= 65, 且只为整数
|
|
|
|
|
- 大部分的年龄范围为 15-18岁(学生)
|
|
|
|
|
- 15, 16, 17, 18 四个chunk的数据量, 访问压力远大于其他chunk
|
|
|
|
|
- 结论: 取值分布应尽可能均匀
|
|
|
|
|
|
|
|
|
|
- 正确的姿势 - 定向性好
|
|
|
|
|
- 考虑:
|
|
|
|
|
- 4个分片的集群, 你希望读某条特定的数据
|
|
|
|
|
- 如果你用片键作为条件查询, mongos 可以直接定位到具体的分片
|
|
|
|
|
- 如果你不用片键, mongos 需要把查询发到4个分片
|
|
|
|
|
- 等最后一个分片响应, mongos 才能响应应用端
|
|
|
|
|
- 结论: 对主要查询要具有定向能力
|
|
|
|
|
|
|
|
|
|
- 比如用组合片键来解决这个问题
|
|
|
|
|
- 片键: {user_id: 1, time: 1}
|
|
|
|
|
|
|
|
|
|
- 足够的资源
|
|
|
|
|
- mongos 与 config 通常消耗很少的资源, 可以选择低规格虚拟机
|
|
|
|
|
- 资源的重点在于 shard 服务器
|
|
|
|
|
- 需要足以容纳热数据索引的内存
|
|
|
|
|
- 正确创建索引后CPU通常不会成为瓶颈, 除非涉及非常多的计算
|
|
|
|
|
- 磁盘尽量选用SSD
|
|
|
|
|
- 最后, 实际测试是最好的检验, 来看你的资源配置是否完备
|
|
|
|
|
- 即使项目初期已经具备了足够的资源, 仍然需要考虑在合适的时候扩展
|
|
|
|
|
- 建议监控各项资源使用情况, 无论哪一项达到 60% 以上, 则开始考虑扩展, 因为:
|
|
|
|
|
- 扩展需要新的资源, 申请新资源需要时间
|
|
|
|
|
- 扩展后数据需要均衡, 均衡需要时间, 应保证新数据入库速度慢于均衡速度
|
|
|
|
|
- 均衡需要资源, 如果资源即将或已经耗尽, 均衡会很低效的
|
|
|
|
|
|
|
|
|
|
### 3. 分片集群的搭建及扩容 - 实验
|
|
|
|
|
|
|
|
|
|
- 实验目标及流程
|
|
|
|
|
- 目标: 学习如何搭建一个2个分片集群
|
|
|
|
|
- 环境: 3台Linux 虚拟机, 4Core 8GB
|
|
|
|
|
- 步骤
|
|
|
|
|
- 配置域名解析
|
|
|
|
|
- 准备分片目录
|
|
|
|
|
- 创建第一个分片复制集并初始化
|
|
|
|
|
- 创建config复制集并初始化
|
|
|
|
|
- 初始化分片集群, 加入第一个分片
|
|
|
|
|
- 创建分片表
|
|
|
|
|
- 加入第二个分片
|
|
|
|
|
|
|
|
|
|
- 实验架构
|
|
|
|
|
- testdemo01
|
|
|
|
|
- Shard1{Primary 27010} Shard2{Primary 27011} Config1 27019 mongos 27017
|
|
|
|
|
- testdemo02
|
|
|
|
|
- Secondary 27010 Secondary 27011 Config2 27019
|
|
|
|
|
- testdemo03
|
|
|
|
|
- Secondary 27010 Secondary 27011 Config3 27019
|
|
|
|
|
- ![实验架构.png](pic/实验架构.png)
|
|
|
|
|
|
|
|
|
|
- 1 - **配置域名解析**
|
|
|
|
|
- 在三台虚拟机上分别执行以下3条命令, 注意替换实际IP地址
|
|
|
|
|
- echo "192.168.1.1 testdemo01 member1.example.com member2.example.com" >> /etc/hosts
|
|
|
|
|
- echo "192.168.1.2 testdemo02 member3.example.com member4.example.com" >> /etc/hosts
|
|
|
|
|
- echo "192.168.1.3 testdemo03 member5.example.com member6.example.com" >> /etc/hosts
|
|
|
|
|
|
|
|
|
|
- 2 - 准备分片目录
|
|
|
|
|
- 在各服务器上创建数据目录, 我们使用 /data, 按自己的需求修改为其他目录:
|
|
|
|
|
- 在 member1 / member3 / member5 上执行以下命令:
|
|
|
|
|
- mkdir -p /data/shard1/
|
|
|
|
|
- mkdir -p /data/config/
|
|
|
|
|
- 在 member2 / member4 / member6 上执行以下命令:
|
|
|
|
|
- mkdir -p /data/shard2/
|
|
|
|
|
- mkdir -p /data/mongos/
|
|
|
|
|
|
|
|
|
|
- 3 - 创建第一个分片用的复制集
|
|
|
|
|
- 在 member1 / member3 / member5 上执行以下命令:
|
|
|
|
|
- mongod --bind_ip 0.0.0.0 --replSet shard1 --dbpath /data/shard1 --logpath /data/shard1/mongod.log --port 27010 --fork --shardsvr --wiredTigerCacheSizeGB 1
|
|
|
|
|
- 检查是否成功运行: mongo localhost:27010
|
|
|
|
|
|
|
|
|
|
- 4 - 初始化第一个分片复制集
|
|
|
|
|
- mongo --host member1.example.com:27010
|
|
|
|
|
|
|
|
|
|
```spring-mongodb-json
|
|
|
|
|
rs.initiate({
|
|
|
|
|
_id: "shard1",
|
|
|
|
|
"members": [
|
|
|
|
|
{
|
|
|
|
|
"_id": 0,
|
|
|
|
|
"host": "member1.example.com:27010"
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"_id": 1,
|
|
|
|
|
"host": "member3.example.com:27010"
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"_id": 2,
|
|
|
|
|
"host": "member5.example.com:27010"
|
|
|
|
|
},
|
|
|
|
|
]
|
|
|
|
|
})
|
|
|
|
|
```
|
|
|
|
|
- 查看结果: rs.status()
|
|
|
|
|
|
|
|
|
|
- 5 - 创建 config server 复制集
|
|
|
|
|
- 在member1 / member3 / member5 上执行以下命令
|
|
|
|
|
- mongod --bind_ip 0.0.0.0 --replSet config --dbpath /data/config --logpath /data/config/mongod.log --port 27019 --fork --configsvr --wiredTigerCacheSizeGB 1
|
|
|
|
|
|
|
|
|
|
- 6 - 初始化 config server 复制集
|
|
|
|
|
- mongo --host member1.example.com:27019
|
|
|
|
|
```spring-mongodb-json
|
|
|
|
|
rs.initiate({
|
|
|
|
|
_id: "config",
|
|
|
|
|
"members": [
|
|
|
|
|
{
|
|
|
|
|
"_id": 0,
|
|
|
|
|
"host": "member1.example.com:27019"
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"_id": 1,
|
|
|
|
|
"host": "member3.example.com:27019"
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"_id": 2,
|
|
|
|
|
"host": "member5.example.com:27019"
|
|
|
|
|
},
|
|
|
|
|
]
|
|
|
|
|
})
|
|
|
|
|
```
|
|
|
|
|
- 查看结果: rs.status()
|
|
|
|
|
|
|
|
|
|
- 7 - 在第一台机器上搭建 mongos[也可以直接在别的机器上进行运行, 一般至少需要2个来做高可用]
|
|
|
|
|
- mongos --bind_ip 0.0.0.0 --logpath /data/mongos/mongos.log --port 27017 --fork --configdb config/member1.example.com:27019,member3.example.com:27019,member5.example.com:27019
|
|
|
|
|
- 连接到 mongos, 添加分片
|
|
|
|
|
- mongo --host member1.example.com:27017
|
|
|
|
|
- mongos>
|
|
|
|
|
- sh.addShard("shard1/member1.example.com:27010,member3.example.com:27010,member5.example.com:27010");
|
|
|
|
|
- 查看运行结果: sh.status()
|
|
|
|
|
|
|
|
|
|
- 8 - 创建分片表
|
|
|
|
|
- 连接到 mongos, 创建分片集合
|
|
|
|
|
- mongo --host memeber1.example.com:27017
|
|
|
|
|
- mongos> sh.status()
|
|
|
|
|
- mongos> sh.enableSharding("foo"); 指定库名
|
|
|
|
|
- mongos> sh.shardCollection("foo.bar", {_id:'hashed'}); 指定集合和分片键
|
|
|
|
|
- mongos> sh.status();
|
|
|
|
|
- 插入测试数据
|
|
|
|
|
- use foo
|
|
|
|
|
- for(var i=0;i < 10000; i++){ db.bar.insert({i:i});}
|
|
|
|
|
- 查看结果: sh.status()
|
|
|
|
|
|
|
|
|
|
- 9 - 创建第二个分片的复制集 - 扩容
|
|
|
|
|
- 在 member2 / member4 / member6 上执行以下命令
|
|
|
|
|
- mongod --bind_ip 0.0.0.0 --replSet shard2 --dbpath /data/shard2 --logpath /data/shard2/mongod.log --port 27011 --fork --shardsvr --wiredTigerCacheSizeGB 1
|
|
|
|
|
|
|
|
|
|
- 10 - 初始化第二个分片复制集 - 扩容
|
|
|
|
|
- mongo --host member2.example.com:27011
|
|
|
|
|
|
|
|
|
|
```spring-mongodb-json
|
|
|
|
|
rs.initiate({
|
|
|
|
|
_id: "shard2",
|
|
|
|
|
"members": [
|
|
|
|
|
{
|
|
|
|
|
"_id": 0,
|
|
|
|
|
"host": "member2.example.com:27011"
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"_id": 1,
|
|
|
|
|
"host": "member4.example.com:27011"
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"_id": 2,
|
|
|
|
|
"host": "member6.example.com:27011"
|
|
|
|
|
},
|
|
|
|
|
]
|
|
|
|
|
})
|
|
|
|
|
```
|
|
|
|
|
- 查看结果: rs.status()
|
|
|
|
|
|
|
|
|
|
- 11 - 加入第二个分片 - 扩容
|
|
|
|
|
- 连接到 mongos, 添加分片
|
|
|
|
|
- mongo --host member1.example.com:27017
|
|
|
|
|
- mongos>
|
|
|
|
|
- sh.addShard("shard2/member2.example.com:27011,member4.example.com:27011,member6.example.com:27011");
|
|
|
|
|
- 查看运行结果: sh.status()
|
|
|
|
|
|
|
|
|
|
### 4. MongoDB 监控最佳实践
|
|
|
|
|
|
|
|
|
|
- 常用的监控工具和手段
|
|
|
|
|
- MongoDB Ops Manager
|
|
|
|
|
- Percona
|
|
|
|
|
- 通用监控平台
|
|
|
|
|
- 程序脚本
|
|
|
|
|
|
|
|
|
|
- 如何获取监控数据
|
|
|
|
|
- 监控信息的来源
|
|
|
|
|
- db.serverStatus()(主要)
|
|
|
|
|
- db.isMaster()(次要)
|
|
|
|
|
- mongostats 命令行工具(只有部分信息)
|
|
|
|
|
- 注意: db.serverStatus() 包含的监控信息是从上次开机到现在为止的累计数据, 因此不能简单使用
|
|
|
|
|
|
|
|
|
|
- serverStatus() Output
|
|
|
|
|
- ![serverStatus.png](pic/serverStatus.png)
|
|
|
|
|
- serverStatus() 主要信息
|
|
|
|
|
- connections: 关于连接数的信息
|
|
|
|
|
- locks: 关于MongoDB使用的锁情况
|
|
|
|
|
- network: 网络使用情况统计
|
|
|
|
|
- opcounters: CRUD 的执行次数统计
|
|
|
|
|
- repl: 复制集配置信息
|
|
|
|
|
- wiredTiger: 包含大量 wiredTiger 执行情况的信息
|
|
|
|
|
- block-manager: WT数据块的读写情况
|
|
|
|
|
- session: session 使用数量
|
|
|
|
|
- cocurrentTransactions: Ticket 使用情况
|
|
|
|
|
- mem: 内存使用情况
|
|
|
|
|
- metrics: 一系列性能指标统计信息
|
|
|
|
|
|
|
|
|
|
- 监控报警的考量
|
|
|
|
|
- 具备一定的容错机制以减少误报的发生
|
|
|
|
|
- 总结应用各指标峰值
|
|
|
|
|
- 适时调整报警阈值
|
|
|
|
|
- 留出足够的处理时间
|
|
|
|
|
|
|
|
|
|
- 建议监控的指标
|
|
|
|
|
- ![建议监控的指标.png](pic/建议监控的指标.png)
|
|
|
|
|
- ![建议监控的指标.png](pic/建议监控的指标1.png)
|
|
|
|
|
- ![建议监控的指标.png](pic/建议监控的指标2.png)
|
|
|
|
|
|
|
|
|
|
### 5. MongoDB 备份和恢复
|
|
|
|
|
|
|
|
|
|
- 为何备份
|
|
|
|
|
- 备份的目的:
|
|
|
|
|
- 防止硬件故障引起的数据丢失
|
|
|
|
|
- 防止人为错误误删数据
|
|
|
|
|
- 时间回溯
|
|
|
|
|
- 监管要求
|
|
|
|
|
- 第一点MongoDB生产集群已经通过复制集的多节点实现, 本讲的备份主要是为其他几个目的
|
|
|
|
|
|
|
|
|
|
- MongoDB 的备份
|
|
|
|
|
- MongoDB的备份机制分为:
|
|
|
|
|
- 延迟节点备份
|
|
|
|
|
- 全量备份 + oplog 增量
|
|
|
|
|
- 最常见的全量备份方式包括:
|
|
|
|
|
- mongodump
|
|
|
|
|
- 复制数据文件
|
|
|
|
|
- 文件系统快照
|
|
|
|
|
|
|
|
|
|
- 方案1: 延迟节点备份
|
|
|
|
|
- ![延迟节点备份.png](pic/延迟节点备份.png)
|
|
|
|
|
- ![延迟节点备份.png](pic/延迟节点备份1.png)
|
|
|
|
|
|
|
|
|
|
- 方案2: 全量备份加 oplog
|
|
|
|
|
- ![全量备份加oplog.png](pic/全量备份加oplog.png)
|
|
|
|
|
- ![全量备份加oplog.png](pic/全量备份加oplog1.png)
|
|
|
|
|
|
|
|
|
|
- 复制文件全量备份注意事项
|
|
|
|
|
- 复制数据库文件:
|
|
|
|
|
- 必须先关闭节点才能复制, 否则复制到的文件无效;
|
|
|
|
|
- 也可以选择db.fsyncLock()锁定节点, 但完成后不要忘记db.fsyncUnLock()解锁
|
|
|
|
|
- 可以且应该在从节点上完成
|
|
|
|
|
- 该方法时间上会暂时宕机一个从节点, 所以整个过程中应注意投票节点总数
|
|
|
|
|
- 全量备份加oplog注意事项
|
|
|
|
|
- 文件系统快照
|
|
|
|
|
- MongoDB支持使用文件系统快照直接获取数据文件在某一时刻的镜像
|
|
|
|
|
- 快照过程中可以不用停机
|
|
|
|
|
- 数据文件和Journal必须在同一个卷上
|
|
|
|
|
- 快照完成后请尽快复制文件并删除快照
|
|
|
|
|
- 全量备份Mongodump注意事项
|
|
|
|
|
- mongodump
|
|
|
|
|
- 使用 mongodump 备份最灵活, 但速度上也是最慢的
|
|
|
|
|
- mongodump 出来的数据不能表示某个时间点, 只是某个时间段
|
|
|
|
|
- ![mongodump.png](pic/mongodump.png)
|
|
|
|
|
- 解决方案: 幂等性
|
|
|
|
|
- ![解决方案-幂等性.png](pic/解决方案-幂等性.png)
|
|
|
|
|
|
|
|
|
|
### 6. MongoDB 备份和恢复的操作
|
|
|
|
|
|
|
|
|
|
- 备份和恢复工具参数
|
|
|
|
|
- 几个重要参数:
|
|
|
|
|
- mongodump
|
|
|
|
|
- --oplog:复制 mongodump 开始到结束过程中的所有 oplog 并输出到结果中。输出文件位于 dump/oplog.bson
|
|
|
|
|
- mongorestore
|
|
|
|
|
- --oplogReplay: 恢复完数据文件后再重放 oplog。默认重放 dump/oplog.bson=><dump-directory>/local/oplog.rs.bson。如果 oplog 不在这,则可以:
|
|
|
|
|
- --oplogFile:指定需要重放的 oplog 文件位置
|
|
|
|
|
- --oplogLimit: 重放 oplog 时截止到指定时间点
|
|
|
|
|
|
|
|
|
|
- 实际操作:mongodump/mongorestore
|
|
|
|
|
- 创建一个数据集, test
|
|
|
|
|
- 为了模拟dump过程中的数据变化,我们开启一个循环插入数据的线程:
|
|
|
|
|
- for(var i=0;i<100000; i++){db.random.insertOne({x: Math.random()* 100000});}
|
|
|
|
|
- 在另一个窗口中我们对其进行mongodump:
|
|
|
|
|
- mongodump -h 127.0.0.1:27017 --oplog
|
|
|
|
|
- 回到第一个表, 进行删除表 - (模拟删除)
|
|
|
|
|
- db.random.drop()
|
|
|
|
|
- db.random.count() 查看是否删除成功
|
|
|
|
|
- 在第二台机器, 模拟恢复
|
|
|
|
|
- mongorestore --host localhost:27017 --oplogReplay
|
|
|
|
|
- dump 文件的目录结构
|
|
|
|
|
- ![dump文件的目录结构.png](pic/dump文件的目录结构.png)
|
|
|
|
|
- ![dump文件操作详解.png](pic/dump文件操作详解.png)
|
|
|
|
|
|
|
|
|
|
- 更复杂的重放 oplog 方式 - 增量重放
|
|
|
|
|
- ![更复杂的重放oplog方式.png](pic/更复杂的重放oplog方式.png)
|
|
|
|
|
|
|
|
|
|
- 分片集备份
|
|
|
|
|
- 分片集备份大致与复制集原理相同,不过存在以下差异:
|
|
|
|
|
- 应分别为每个片和config备份;
|
|
|
|
|
- 分片集备份不仅要考虑一个分片内的一致性问题,还要考虑分片间的一致性问题,因此每个片要能够恢复到同一个时间点;
|
|
|
|
|
|
|
|
|
|
- 分片集的增量备份
|
|
|
|
|
- 尽管理论上我们可以使用与复制集同样的方式来为分片集完成增量备份,但实际上分片集的情况更加复杂。这种复杂性来自两个方面:
|
|
|
|
|
- 各个数据节点的时间不一致:每个数据节点很难完全恢复到一个真正的一致时间点上,通常只能做到大致一致,而这种大致一致通常足够好,除了以下情况;
|
|
|
|
|
- 分片间的数据迁移:当一部分数据从一个片迁移到另一个片时,最终数据到底在哪里取决于config中的元数据。
|
|
|
|
|
如果元数据与数据节点之间的时间差异正好导致数据实际已经迁移到新分片上,而元数据仍然认为数据在旧分片上,就会导致数据丢失情况发生。虽然这种情况发生的概率很小,但仍有可能导致问题。
|
|
|
|
|
- 要避免上述问题的发生,只有定期停止均衡器;只有在均衡器停止期间,增量恢复才能保证正确。
|
|
|
|
|
|
|
|
|
|
### 7. MongoDB 安全架构
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|