MongoDB集群
# 复制集架构
所有数据都写入Primary,Secondary从Primary同步写入的数据,以保持复制集内所有成员存储相同的数据集
# PSS模式(官方推荐模式)
PSS模式由一个主节点和两个备节点所组成,即Primary+Secondary+Secondary。
# PSA模式
PSA模式由一个主节点、一个备节点和一个仲裁者节点组成,即Primary+Secondary+Arbiter
仲裁者/Arbiter节点不存储数据副本,也不提供业务的读写操作。Arbiter节点发生故障不影响业务,仅影响选举投票。
# 单机
只有一台服务器,也要以单节点模式启动复制集
- 单机多实例启动复制集
- 单节点启动复制集
# 注意事项
- 因为正常的复制集节点都有可能成为主节点,它们的地位是一样的,因此硬件配置上必须一致;
- 为了保证节点不会同时宕机,各节点使用的硬件必须具有独立性。 也就是不会有互相影响。如共享ssd
- 复制集各节点软件版本必须一致,以避免出现不可预知的问题。
- 增加节点不会增加系统写性能,写性能只能是配置、jonmal、单机硬件。
# 搭建
复制集通过replSetInitiate命令 或mongo shell的rs.initiate()进行初始化
# mongo --port 28017
# 初始化复制集
> rs.initiate()
# 将其余成员添加到复制集
> rs.add("192.168.65.174:28018")
> rs.add("192.168.65.174:28019")
# 查看复制集整体状态:
> rs.status()
# 查看当前节点角色
> db.isMaster()
rs.secondaryOk() 为当前的连接设置 从节点可读
rs.reconfig() 通过重新应用复制集配置来为复制集更新配置
rs.conf() 返回复制集配置信息
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 安全认证
use admin
#创建用户
db.createUser( {
user: "fox",
pwd: "fox",
roles: [ { role: "clusterAdmin", db: "admin" } ,
{ role: "userAdminAnyDatabase", db: "admin"},
{ role: "userAdminAnyDatabase", db: "admin"},
{ role: "readWriteAnyDatabase", db: "admin"}]
})
#mongo.key采用随机算法生成,用作节点内部通信的密钥文件。
openssl rand -base64 756 > /data/mongo.key
#权限必须是600
chmod 600 /data/mongo.key
# 创建keyFile前,需要先停掉复制集中所有主从节点的mongod服务,然后再创建,否则有可能出现服务启动不了的情况。
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 连接方式
mongodb://userName:passWord@ip:port,ip2:port2,ip3:port3/test?authSource=admin&replicaSet=rs0
# 成员角色属性
# Priority
当 Priority 等于 0 时,它不可以被复制集选举为主,Priority 的值越高,则被选举为主的概率更大
# Vote
当 Vote 等于 0 时,不可以参与选举投票,此时该节点的 Priority 也必须为 0,即它也不能被选举为主。
由于一个复制集中最多只有7个投票成员,因此多出来的成员则必须将其vote属性值设置为0,即这些成员将无法参与投票。
# 成员角色
# Primary
主节点,其接收所有的写请求,然后把修改同步到所有备节点。
# Secondary
备节点,与主节点保持同样的数据集。当主节点“挂掉”时,参与竞选主节点。
- Hidden = false:正常的只读节点,是否可选为主,是否可投票,取决于 Priority,Vote 的值;
- Hidden = true:隐藏节点,对客户端不可见, 可以参与选举,但是 Priority 必须为 0,即不能被提升为主。由于隐藏节点不会接受业务访问,因此可通过隐藏节点做一些数据备份、离线计算的任务,这并不会影响整个复制集。
- Delayed :延迟节点,必须同时具备隐藏节点和Priority0的特性,会延迟一定的时间(SlaveDelay 配置决定)从上游复制增量,常用于快速回滚场景。
# Arbiter
仲裁节点,只用于参与选举投票,本身不承载任何数据,只作为投票角色。
# 成员配置
- 配置隐藏节点
- 配置延时节点
- 查看复制延迟
- 添加投票节点
- 移除复制集节点
- 更改复制集节点
# 高可用
# 选举
选举使用Raft算法,选举成功的必要条件是大多数投票节点存活。
一个复制集最多可以有50个成员,但只有7个投票成员。(这是因为一旦过多的成员参与数据复制、投票过程,将会带来更多可靠性方面的问题。)
原理扩展:
- 支持chainingAllowed链式复制,即备节点不只是从主节点上同步数据,还可以选择一个离自己最近(心跳延时最小)的节点来复制数据。
- 增加了预投票阶段,即preVote,这主要是用来避免网络分区时产生Term(任期)值激增的问题
- 支持投票优先级,如果备节点发现自己的优先级比主节点高,则会主动发起投票并尝试成为新的主节点。
当复制集内存活的成员数量不足大多数时,整个复制集将无法选举出主节点,此时无法提供写服务,这些节点都将处于只读状态。此外,如果希望避免平票结果的产生,最好使用奇数个节点成员,比如3个或5个。当然,在MongoDB复制集的实现中,对于平票问题已经提供了解决方案:
- 为选举定时器增加少量的随机时间偏差,这样避免各个节点在同一时刻发起选举,提高成功率。
- 使用仲裁者角色,该角色不做数据复制,也不承担读写业务,仅仅用来投票。
# 自动故障转移
# 备节点是怎么感知到主节点已经发生故障的?
一个影响检测机制的因素是心跳,在复制集组建完成之后,各成员节点会开启定时器,持续向其他成员发起心跳。
另一个重要的因素是选举超时检测,一次心跳检测失败并不会立即触发重新选举。
触发选举的条件是:
- 当前节点是备节点。
- 当前节点具备选举权限。
- 在检测周期内仍然没有与主节点心跳成功。
# 如何降低故障转移对业务产生的影响?
- 在复制集发生主备节点切换的情况下,会出现短暂的无主节点阶段,此时无法接受业务写操作。
- 如果主节点属于强制掉电,那么整个故障转移过程将会变长
- 对于非常重要的业务,建议在业务层面做一些防护策略,比如设计重试机制。
# 如何优雅的重启复制集?
- 逐个重启复制集里所有的Secondary节点
- 对Primary发送rs.stepDown()命令,等待primary降级为Secondary
- 重启降级后的Primary
# 复制集数据同步机制
主节点与备节点之间是通过oplog来同步数据的,这里的oplog是一个特殊的固定集合,当主节点上的一个写操作完成后,会向oplog集合写入一条对应的日志,而备节点则通过这个oplog不断拉取到新的日志,在本地进行回放以达到数据同步的目的。
# oplog
- MongoDB oplog 是 Local 库下的一个集合,用来保存写操作所产生的增量日志(类似于 MySQL 中 的 Binlog)。
- 它是一个 Capped Collection(固定集合),即超出配置的最大值后,会自动删除最老的历史数据,MongoDB 针对 oplog 的删除有特殊优化,以提升删除效率。
- 主节点产生新的 oplog Entry,从节点通过复制 oplog 并应用来保持和主节点的状态一致;
# 查看oplog
use local
db.oplog.rs.find().sort({$natural:-1}).pretty()
2
# 幂等性
每一条oplog记录都描述了一次数据的原子性变更,对于oplog来说,必须保证是幂等性的。
幂等性的代价 : 有些指令会在内部被转化,如 数组的$push/$pull/$addToSet操作被转换为了$set操作、$inc 转化为 $set。
# oplog的写入被放大,导致同步追不上
生产环境案例:
用户的文档内包含一个很大的数组字段,1000个元素总大小在64KB左右,这个数组里的元素按时间反序存储,新插入的元素会放到数组的最前面($position: 0),然后保留数组的前1000个元素($slice: 1000)。
上述场景导致,Primary上的每次往数组里插入一个新元素(请求大概几百字节),oplog里就要记录整个数组的内容,Secondary同步时会拉取oplog并重放,Primary到Secondary同步oplog的流量是客户端到Primary网络流量的上百倍,导致主备间网卡流量跑满,而且由于oplog的量太大,旧的内容很快被删除掉,最终导致Secondary追不上,转换为恢复/RECOVERING状态。
# 编码注意事项
- 数组的元素个数不要太多,总的大小也不要太大
- 尽量避免对数组进行更新操作
- 如果一定要更新,尽量只在尾部插入元素,复杂的逻辑可以考虑在业务层面上来支持
# 复制延迟
如果备节点的复制不够快,就无法跟上主节点的步伐,从而产生复制延迟(replication lag)问题。这是不容忽视的,一旦备节点的延迟过大,则随时会发生复制断裂的风险,这意味着备节点的optime(最新一条同步记录)已经被主节点老化掉,于是备节点将无法继续进行数据同步。
注意: 复制延迟是不可避免的,这意味着主备节点之间的数据无法保持绝对的同步
措施:
- 增加oplog的容量大小,并保持对复制窗口的监视。
- 通过一些扩展手段降低主节点的写入速度。
- 优化主备节点之间的网络。
- 避免字段使用太大的数组(可能导致oplog膨胀)。
# 数据回滚
当复制集中的主节点宕机时,备节点会重新选举成为新的主节点。那么,当旧的主节点重新加入时,必须回滚掉之前的一些“脏日志数据”,以保证数据集与新的主节点一致。
措施:
- 应用上可以通过设定更高的写入级别(writeConcern:majority)来保证数据的持久性。
- 当rollback发生时,MongoDB将把rollback的数据以BSON格式存放到dbpath路径下rollback文件夹中,后期可以手工补充回来。
# 同步源选择
MongoDB是允许通过备节点进行复制的(以减轻主节点的压力),这会发生在以下的情况中:
- 在settings.chainingAllowed开启的情况下,备节点自动选择一个最近的节点(ping命令时延最小)进行同步。settings.chainingAllowed选项默认是开启的,也就是说默认情况下备节点并不一定会选择主节点进行同步,这个副作用就是会带来延迟的增加,你可以通过下面的操作进行关闭:
cfg = rs.config() cfg.settings.chainingAllowed = false rs.reconfig(cfg)
- 使用replSetSyncFrom命令临时更改当前节点的同步源,比如在初始化同步时将同步源指向备节点来降低对主节点的影响。
db.adminCommand( { replSetSyncFrom: "hostname:port" })
# 分片集群
分片(shard)是指在将数据进行水平切分之后,将其存储到多个不同的服务器节点上的一种扩展方式。
在分片模式下,存储不同的切片数据的节点被称为分片节点。
# 应用场景
- 存储容量需求超出单机的磁盘容量。
- 活跃的数据集超出单机内存容量,导致很多请求都要从磁盘读取数据,影响性能。
- 写IOPS超出单个MongoDB节点的写服务能力。
# 核心概念/角色
- 数据分片 : 用于存储真正的数据,并提供最终的数据读写访问。可以是单独的mongod实例,也可以是一个复制集。在生产环境中也一般会使用复制集的方式,这是为了防止数据节点出现单点故障。
- 配置服务器(Config Server): 配置复制集中保存了整个分片集群中的元数据,其中包含各个集合的分片策略,以及分片的路由表等。 也可以是一个复制集结构。
- 查询路由(mongos) : 分片集群的访问入口,其本身并不持久化数据,将用户的请求正确路由到对应的分片。在分片集群中可以部署多个mongos以分担客户端请求的压力。
# 分片集群搭建
- 搭建数据分片节点
- 搭建 mongos
- mongos加入第1个分片
- 创建分片集合
- mongos加入第2/3....个分片
# 使用分片集群
- 先开启database的分片功能
- 执行shardCollection命令,对集合执行分片初始化
- 向分片集合写入数据
# 分片策略
# chunk (数据块)
集群在操作分片集合时,会根据分片键找到对应的chunk,并向该chunk所在的分片发起操作请求。
# 分片算法
chunk切分是根据分片策略进行实施的,分片策略的内容包括分片键和分片算法。
MongoDB支持两种分片算法:范围分片 、哈希分片、范围和哈希分片组合
范围分片的缺点在于,如果Shard Key有明显递增(或者递减)趋势,则新插入的文档会分布到同一个chunk,此时写压力会集中到一个节点,从而导致单点的性能瓶颈。
哈希分片,由于哈希算法保证了随机性,所以文档可以更加离散地分布到多个chunk上,这避免了集中写问题。
哈希分片但是只能选择单个字段,而范围分片允许采用组合式的多字段作为分片键。
# 分片键(ShardKey)的选择
- 分片键的基数(cardinality),取值基数越大越有利于扩展。
- 分片键的取值分布应该尽可能均匀。
- 业务读写模式,尽可能分散写压力,而读操作尽可能来自一个或少量的分片。
- 分片键应该能适应大部分的业务操作。
# 分片键(ShardKey)的约束
ShardKey 必须是一个索引。
# 分片标签
通过为分片添加标签(tag)的方式来控制数据分发。一个标签可以关联到多个分片区间(TagRange)。
分片标签适用于一些特定的场景 : 如,集群中可能同时存在OLTP和OLAP处理。
// 使用:
//让分片拥有指定的标签
sh.addShardTag("shard01","oltp")
sh.addShardTag("shard02","oltp")
sh.addShardTag("shard03","olap")
// 声明TagRange
sh.addTagRange("main.devices",{shardKey:MinKey},{shardKey:MaxKey},"oltp")
sh.addTagRange("other.systemLogs",{shardKey:MinKey},{shardKey:MaxKey},"olap")
// 最终: main.devices集合将被均衡地分发到shard01、shard02分片上,而other.systemLogs集合将被单独分发到shard03分片上。
2
3
4
5
6
7
8
9
# 数据均衡
现象:
- 一方面,在没有人工干预的情况下,chunk会持续增长并产生分裂(split),而不断分裂的结果就会出现数量上的不均衡;
- 另一方面,在动态增加分片服务器时,也会出现不均衡的情况。
目标:
- 所有的数据应均匀地分布于不同的chunk上。 由业务场景和分片策略来决定
- 每个分片上的chunk数量尽可能是相近的。 可以使用手动均衡、自动均衡方式来处理。
# 手动均衡
- 初始化集合时预分配一定数量的chunk
- 通过splitAt、moveChunk命令进行手动切分、迁移
# 自动均衡
均衡器会在后台对各分片的chunk进行监控,一旦发现了不均衡状态就会自动进行chunk的搬迁以达到均衡。
自动均衡是开箱即用的,可以极大简化集群的管理工作。
# chunk分裂
在默认情况下,一个chunk的大小为64MB,数据量超过了chunk大小,则MongoDB会自动进行分裂,将该chunk切分为两个相同大小的chunk。
chunk分裂是基于分片键进行的,如果分片键的基数太小,则可能因为无法分裂而会出现jumbo chunk(超大块)的问题(多次迁移失败后,将会导致无法迁移)。
所以超大块对水平扩展有负面作用,该情况不利于数据的均衡,业务上应尽可能避免。
# 数据均衡带来的问题
- 数据均衡会影响性能,可以设置再晚上去做迁移。
- 对分片集合中执行count命令可能会产生不准确的结果。 替代办法是使用db.collection.countDocuments({})方法做聚合操作。
- 在执行数据库备份的期间,不能进行数据均衡操作,否则会产生不一致的备份数据。
# 两地三中心集群架构
双中心双活+异地热备=两地三中心
同一中心设置为主,同城的距离近的,优先级越高。 这样如果出现故障恢复的时延就更短。
# 全球多写集群架构
分片+指定tag + 各个分片中再设置一些不同分片的oplog数据同步。