■■ 概述
mongodb是最常用的nosql数据库,以下记录如何搭建高可用mongodb集群(分片+副本)
mongodb集群有三种模式:主从模式、副本集模式、sharding分片模式
副本集和sharding分片模式是最广泛使用的方案,这2种方案的选择通过数据量和并发数来权衡:GB级别采用副本集方案,TB级别或以上采用sharding模式,解决单机容量和单机并发能力
sharding模式分片越多,性能自然下降越多
对应社区版本:
https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel80-3.6.23.tgz
■ 下图是一个典型的3节点分片副本集群逻辑架构示意图
┌────node1────┐ ┌────node2────┐ ┌────node3────┐
│Mongos Server│ │Mongos Server│ │Mongos Server│
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
└─────────────────┼─────────────────┤
│ │
│ ┌─replica set─┐
│ │config server│ -- node1
│ ├─────────────┤
│ │config server│ -- node2
│ ├─────────────┤
│ │config server│ -- node3
│ └─────────────┘
│ │
┌─────────────────┼─────────────────┤
┌─replica set─┐ ┌─replica set─┐ ┌─replica set─┐
│shard server1│ │shard server2│ │shard server3│ -- node1
│(Primary) │ │(Arbiter) │ │(Secondary) │
├─────────────┤ ├─────────────┤ ├─────────────┤
│shard server1│ │shard server2│ │shard server3│ -- node2
│(Secondary) │ │(Primary) │ │(Arbiter) │
├─────────────┤ ├─────────────┤ ├─────────────┤
│shard server1│ │shard server2│ │shard server3│ -- node3
│(Arbiter) │ │(Secondary) │ │(Primary) │
└─────────────┘ └─────────────┘ └─────────────┘
如上图可见几个组件:
■ Mongos Server
数据库集群请求的入口,所有请求都通过mongos进行协调,不需在应用程序添加路由选择器,mongos自己就是一个请求分发中心,它负责把数据请求请求转发到对应的shard服务器上。在生产环境通常设置多mongos作为请求入口,防止其中一个挂掉所有的mongodb请求都没法操作。
1、用户访问 mongos 跟访问单个 mongod 类似
2、所有 mongos 是对等关系,用户访问分片集群可通过任意一个或多个mongos
3、mongos 本身是无状态的,可任意扩展,集群的服务能力为『Shard服务能力之和』与『mongos服务能力之和』的最小值
4、访问分片集群时,应将应用负载均匀的分散到多个 mongos 上
MongoClientURI connectionString = new MongoClientURI("mongodb://:rootpasswd@1.mongodb.rds.aliyuncs.com:3717,2.mongodb.rds.aliyuncs.com:3717/admin");
MongoDatabase database = client.getDatabase("mydb");
MongoCollection
通过上述方式连接分片集群时,客户端会自动将请求分散到多个mongos 上,以实现负载均衡;同时,当URI 里 mongos 数量在2个及以上时,当有mongos故障时,客户端能自动进行 failover,将请求都分散到状态正常的 mongos 上。
当 Mongos 数量很多时,还可以按应用来将 mongos 进行分组,比如有2个应用A、B、4个mongos,可以让应用A 访问 mongos 1-2(URI里只指定mongos 1-2 的地址), 应用B 来访问 mongos 3-4(URI里只指定mongos 3-4 的地址),根据这种方法来实现应用间的访问隔离(应用访问的mongos彼此隔离,但后端 Shard 仍然是共享的)。
总而言之,在访问分片集群时,请务必确保 MongoDB URI 里包含2个及以上的mongos地址,来实现负载均衡及高可用。
5、如何实现读写分离?
在options里添加readPreference=secondaryPreferred即可实现,读请求优先到Secondary节点,从而实现读写分离的功能,更多读选项参考Read preferences
6、如何限制连接数?
在options里添加maxPoolSize=xx即可将客户端连接池限制在xx以内
7、如何保证数据写入到大多数节点后才返回?
在options里添加w= majority即可保证写请求成功写入大多数节点才向客户端确认,更多写选项参考Write Concern
■ config server
配置服务器,存储所有数据库元信息(路由、分片)的配置。mongos本身没有物理存储分片服务器和数据路由信息,只是缓存在内存里,配置服务器则实际存储这些数据。mongos第一次启动或者关掉重启就会从 config server 加载配置信息,以后如果配置服务器信息变化会通知到所有的mongos更新自己的状态,这样mongos就能继续准确路由。在生产环境通常设置多个 config server ,因为它存储了分片路由的元数据,防止单点数据丢失!
■ shard server
分片(sharding)是指将数据库拆分,将其分散在不同的机器上的过程。将数据分散到不同的机器上,不需要功能强大的服务器就可以存储更多的数据和处理更大的负载。基本思想就是将集合切成小块,这些块分散到若干片里,每个片只负责总数据的一部分,最后通过一个均衡器来对各个分片进行均衡(数据迁移)。
■ replica set
中文翻译副本集,其实就是shard的备份,防止shard挂掉之后数据丢失。复制提供了数据的冗余备份,并在多个服务器上存储数据副本,提高了数据的可用性, 并可以保证数据的安全性。
■ 仲裁者(Arbiter)
是复制集中的一个MongoDB实例,它并不保存数据。仲裁节点使用最小的资源并且不要求硬件设备,不能将Arbiter部署在同一个数据集节点中,可以部署在其他应用服务器或者监视服务器中,也可部署在单独的虚拟机中。为了确保复制集中有奇数的投票成员(包括primary),需要添加仲裁节点做为投票,否则primary不能运行时不会自动切换primary。
■ 主节点(Primary)
在复制集中,最多只能拥有一个主节点,主节点是唯一能够接收写请求的节点。MongoDB在主节点进行写操作,并将这些操作记录到主节点的oplog中。而副节点将会从oplog复制到其本机,并将这些操作应用到自己的数据集上。
■ 副节点(Secondary)
副节点通过应用主节点传来的数据变动操作来保持其数据集与主节点一致。副节点也可以通过增加额外参数配置来对应特殊需求。例如,副节点可以是non-voting或是priority 0.
■ 仲裁节点(Arbiter)
仲裁节点即投票节点,其本身并不包含数据集,且也无法晋升为主节点。但是,一旦当前的主节点不可用时,投票节点就会参与到新的主节点选举的投票中。仲裁节点使用最小的资源并且不要求硬件设备。投票节点的存在使得复制集可以以偶数个节点存在,而无需为复制集再新增节点。不要将投票节点运行在复制集的主节点或副节点机器上。投票节点与其他复制集节点的交流仅有:选举过程中的投票,心跳检测和配置数据。这些交互都是不加密的。
■ 心跳检测
复制集成员每2秒向复制集中其他成员进行心跳检测。如果某个节点在10秒内没有返回,那么它将被标记为不可用。
MongoDB副本集是有故障恢复功能的主从集群,由一个primary节点和一个或多个secondary节点组成:
节点同步过程:Primary节点写入数据,Secondary通过读取Primary的oplog得到复制信息,开始复制数据并且将复制信息写入到自己的oplog。如果某个操作失败,则备份节点停止从当前数据源复制数据。如果某个备份节点由于某些原因挂掉了,当重新启动后,就会自动从oplog的最后一个操作开始同步,同步完成后,将信息写入自己的oplog,由于复制操作是先复制数据,复制完成后再写入oplog,有可能相同的操作会同步两份,不过MongoDB在设计之初就考虑到这个问题,将oplog的同一个操作执行多次,与执行一次的效果是一样的。
通俗理解:当Primary节点完成数据操作后,Secondary会做出一系列的动作保证数据的同步:
- 检查自己local库的oplog.rs集合,找出最近的时间戳
- 检查Primary节点local库oplog.rs集合,找出大于此时间戳的记录
- 将找到的记录插入到自己的oplog.rs集合中,并执行这些操作
副本集的同步和主从同步一样,都是异步同步的过程,不同的是副本集有个自动故障转移的功能。其原理是:slave端从primary端获取日志,然后在自己身上完全顺序的执行日志所记录的各种操作(该日志是不记录查询操作的),这个日志就是local数据库中的oplog.rs表,默认在64位机器上这个表是比较大的,占磁盘大小的5%,oplog.rs的大小可以在启动参数中设 定:–oplogSize 1000,单位是M。
注意:在副本集的环境中,要是所有的Secondary都宕机了,只剩下Primary,则Primary会变成Secondary,不能提供服务。■ 仲裁节点挂掉怎么办
要尽量使用奇数个节点而不要使用仲裁节点。
当仲裁节点挂掉后,若Primary节点正常,则不影响正常使用,将仲裁节点恢复即可;若在仲裁节点恢复之前,Primary节点就挂了,这时候因无法进行选择投票,所以只有secondary节点而没有Primary节点,因此只能进行检索操作无法进行更新操作,此时若将仲裁节点恢复,将会重新选举出Primary节点,集群重新恢复正常功能。
■■ 分片集群规划
■ 注:配置支持IPV6
■ Configure hostname、hosts file、ip address
vim /etc/hosts
172.72.6.84 os8 node1
172.72.6.85 os8_2 node2
172.72.6.86 os8_3 node3
■ 节点角色及端口分配
┌────node1────┬────node2────┬────node3────┬port─┐
│mongos server│mongos server│mongos server│20000│
├─────────────┼─────────────┼─────────────┼─────┤
│config server│config server│config server│21000│
├─────────────┼─────────────┼─────────────┼─────┤
│shard server1│shard server1│shard server1│27001│
│(Primary) │(Secondary) │(Arbiter) │ │
├─────────────┼─────────────┼─────────────┼─────┤
│shard server2│shard server2│shard server2│27002│
│(Arbiter) │(Primary) │(Secondary) │ │
├─────────────┼─────────────┼─────────────┼─────┤
│shard server3│shard server3│shard server3│27003│
│(Secondary) │(Arbiter) │(Primary) │ │
└─────────────┴─────────────┴─────────────┴─────┘
┌node─┬router┬config┬shard1─┬shard2─┬shard3─┐
│node1│20000 │21000 │27001/P│27002/A│27003/S│
├─────┼──────┼──────┼───────┼───────┼───────┤
│node2│20000 │21000 │27001/S│27002/P│27003/A│
├─────┼──────┼──────┼───────┼───────┼───────┤
│node3│20000 │21000 │27001/A│27002/S│27003/P│
└─────┴──────┴──────┴───────┴───────┴───────┘
Note: P-Primary, S-Secondary, A-Arbiter
■ 如需更大能力,则类似如下规划
┌node─┬router┬config┬shard1─┬shard2─┬shard3─┬shard4─┬shard5─┬shard6─┐
│node1│20000 │21000 │27001/P│27002/A│27003/S│27004/S│ │27006/A│
├─────┼──────┼──────┼───────┼───────┼───────┼───────┼───────┼───────┤
│node2│20000 │21000 │27001/S│27002/P│27003/A│27004/A│27005/A│ │
├─────┼──────┼──────┼───────┼───────┼───────┼───────┼───────┼───────┤
│node3│20000 │21000 │27001/A│27002/S│27003/P│ │27005/S│27006/S│
├─────┼──────┼──────┼───────┼───────┼───────┼───────┼───────┼───────┤
│node4│ │ │ │ │ │27004/P│27005/P│27006/P│
└─────┴──────┴──────┴───────┴───────┴───────┴───────┴───────┴───────┘
■■ Pre-task preparation
■ 依赖包
yum install libcurl openssl [3.6.23]
yum install xz-libs [6.0.0另需]
■ 用户及用户组
groupadd mongod
groupadd mongodb
useradd -g mongod -G mongodb mongod
echo "Passwd#"|passwd mongod --stdin
■ 下载、安装
https://www.mongodb.com/try/download/community
cd /data
wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel80-3.6.23.tgz
tar zxvf mongodb-linux-x86_64-rhel.tgz
ln -s mongodb-linux-x86_64-rhel80-3.6.23 mongodb
chown -R mongod:mongod /data/mongodb
ln -s /data/mongodb/bin/ /usr/local/bin/
■ mongosh下载、安装
cd /data
wget https://downloads.mongodb.com/compass/mongosh-1.5.4-linux-x64.tgz
tar zxvf mongosh-linux-x64.tgz
ln -s mongosh-1.5.4-linux-x64 mongosh
chown -R mongod:mongod /data/mongosh
ln -s /data/mongosh/bin/ /usr/local/bin/
■ mongosh使用
mongosh
mongosh "mongodb://127.0.0.1:21000"
show dbs
use admin
sh.status()
rs.status()
■ chronyd
略
■ selinux
关闭
■ firewalld
firewall-cmd --add-port=20000/tcp --permanent
firewall-cmd --add-port=21000/tcp --permanent
firewall-cmd --add-port=27001-27003/tcp --permanent
firewall-cmd --reload
■ 以下均用mongod用户操作
■ 3个节点均建立6个目录:conf、mongos、config、shard1、shard2、shard3
mkdir -p /data/mongodb/conf
mkdir -p /data/mongodb/mongos/log
mkdir -p /data/mongodb/config/data
mkdir -p /data/mongodb/config/log
mkdir -p /data/mongodb/shard1/data
mkdir -p /data/mongodb/shard1/log
mkdir -p /data/mongodb/shard2/data
mkdir -p /data/mongodb/shard2/log
mkdir -p /data/mongodb/shard3/data
mkdir -p /data/mongodb/shard3/log
■ 最终的目录结构
tree /data/mongodb -L 2 --dirsfirst
/data/mongodb
├── conf
│ ├── config.conf
│ ├── mongos.conf
│ ├── shard1.conf
│ ├── shard2.conf
│ └── shard3.conf
├── config
│ ├── data
│ └── log
├── mongos
│ └── log
├── shard1
│ ├── data
│ └── log
├── shard2
│ ├── data
│ └── log
└── shard3
├── data
└── log
■■ config server
mongodb3.4以后要求配置服务器也创建副本集,不然集群搭建不成功
■ 配置文件
cat > /data/mongodb/conf/config.conf << EOF
pidfilepath = /data/mongodb/config/log/configsrv.pid
dbpath = /data/mongodb/config/data
logpath = /data/mongodb/config/log/configsrv.log
logappend = true
#集群IP地址及端口
bind_ip = 0.0.0.0,::
port = 21000
fork = true
#声明这个配置是集群
configsvr = true
#副本集名称
replSet=configs
#设置最大连接数
maxConns=20000
EOF
■ 启动3个 config server:
mongod -f /data/mongodb/conf/config.conf --ipv6
■ 登录任意一台配置服务器,初始化配置副本集
mongosh node1:21000
config变量:
config = {_id: "configs", members: [
{_id: 0, host: "node1:21000"},
{_id: 1, host: "node2:21000"},
{_id: 2, host: "node3:21000"}
]
}
初始化副本集:
rs.initiate(config)
其中,”_id” : “configs”应与配置文件中配置的 replicaction.replSetName 一致,”members” 中的 “host” 为三个节点的 ip 和 port
查看此时状态:
rs.status()
■■ shard server
【3个节点执行】
■ shard server1
cat > /data/mongodb/conf/shard1.conf << EOF
pidfilepath = /data/mongodb/shard1/log/shard1.pid
dbpath = /data/mongodb/shard1/data
logpath = /data/mongodb/shard1/log/shard1.log
logappend = true
#集群IP地址及端口
bind_ip = 0.0.0.0,::
port = 27001
fork = true
#副本集名称
replSet=shard1
#声明这个配置是集群
shardsvr = true
#设置最大连接数
maxConns=20000
EOF
启动3个 shard1 server:
mongod -f /data/mongodb/conf/shard1.conf --ipv6
登陆任意节点,初始化副本集:
注:初始化副本集的操作不能在仲裁节点上执行!
mongosh node1:27001
使用admin数据库,定义副本集配置,"arbiterOnly":true 代表其为仲裁节点:
use admin
config = {_id: "shard1", members: [
{_id: 0, host: "node1:27001"},
{_id: 1, host: "node2:27001"},
{_id: 2, host: "node3:27001", arbiterOnly:true}
]
}
初始化副本集配置
rs.initiate(config);
rs.status()
■ shard server2
cat > /data/mongodb/conf/shard2.conf << EOF
pidfilepath = /data/mongodb/shard2/log/shard2.pid
dbpath = /data/mongodb/shard2/data
logpath = /data/mongodb/shard2/log/shard2.log
logappend = true
#集群IP地址及端口
bind_ip = 0.0.0.0,::
port = 27002
fork = true
#副本集名称
replSet=shard2
#声明这个配置是集群
shardsvr = true
#设置最大连接数
maxConns=20000
EOF
启动3个 shard2 server:
mongod -f /data/mongodb/conf/shard2.conf --ipv6
登陆任意节点,初始化副本集:
注:初始化副本集的操作不能在仲裁节点上执行!
mongosh node2:27002
使用admin数据库,定义副本集配置,"arbiterOnly":true 代表其为仲裁节点:
use admin
config = {_id: "shard2", members: [
{_id: 0, host: "node1:27002", arbiterOnly:true},
{_id: 1, host: "node2:27002"},
{_id: 2, host: "node3:27002"}
]
}
初始化副本集配置
rs.initiate(config);
rs.status()
■ shard server3
cat > /data/mongodb/conf/shard3.conf << EOF
pidfilepath = /data/mongodb/shard3/log/shard3.pid
dbpath = /data/mongodb/shard3/data
logpath = /data/mongodb/shard3/log/shard3.log
logappend = true
#集群IP地址及端口
bind_ip = 0.0.0.0,::
port = 27003
fork = true
#副本集名称
replSet=shard3
#声明这个配置是集群
shardsvr = true
#设置最大连接数
maxConns=20000
EOF
启动3个 shard3 server:
mongod -f /data/mongodb/conf/shard3.conf --ipv6
登陆任意节点,初始化副本集:
注:初始化副本集的操作不能在仲裁节点上执行!
mongosh node3:27003
使用admin数据库,定义副本集配置,"arbiterOnly":true 代表其为仲裁节点:
use admin
config = {_id: "shard3", members: [
{_id: 0, host: "node1:27003"},
{_id: 1, host: "node2:27003", arbiterOnly:true},
{_id: 2, host: "node3:27003"}
]
}
初始化副本集配置
rs.initiate(config);
rs.status()
■■ mongos server
先启动 config server 和 shard server,后启动 mongos server (3个节点)
cat > /data/mongodb/conf/mongos.conf << EOF
pidfilepath = /data/mongodb/mongos/log/mongos.pid
logpath = /data/mongodb/mongos/log/mongos.log
logappend = true
#集群IP地址及端口
bind_ip = 0.0.0.0,::
port = 20000
fork = true
#监听的配置服务器,只能有1个或者3个,configs为配置服务器的副本集名字
configdb = configs/node1:21000,node2:21000,node3:21000
#设置最大连接数
maxConns=20000
EOF
启动3个 mongos server:
mongos -f /data/mongodb/conf/mongos.conf --ipv6
■■ 启用分片机制
【6.0机制可能不一样,待验证】
搭建了mongodb的 config server, shard server, mongos server 后,应用程序连接到 mongos server 并不能使用分片机制,还需设置分片配置才能使分片生效。
登陆任一 mongos server, 使用 admin 数据库,串联路由服务器与分配副本集:
mongosh node1:20000
use admin
sh.addShard("shard1/node1:27001,node2:27001,node3:27001")
sh.addShard("shard2/node1:27002,node2:27002,node3:27002")
sh.addShard("shard3/node1:27003,node2:27003,node3:27003")
查看集群状态:
sh.status()
■■ 使用分片机制
配置服务、路由服务、分片服务、副本集服务都串联起来以后,为使插入的数据能自动分片,需连接 mongos server, 配置指定的数据库、指定的集合分片生效。
指定分片生效:
db.runCommand({enablesharding:"testdb"})
指定需分片的集合和片键,以及分片策略,如:
设置 table1 表需分片,根据 id 自动分片到 shard1, shard2, shard3
db.runCommand({shardcollection:"testdb.table1",key:{id:"hashed"}})
注意:要这样设置是因为不是所有 mongodb 的数据库和表都需分片!
测试分片配置结果
mongosh node1:20000
use testdb
插入测试数据
for (var i = 1; i <= 100000; i++){
db.table2.insertOne({id:i,"test1":"testval1"});
}
查看分片情况,确认数据是否分片,每个分片的数据数量是否大致相当,应该类似如下:
db.table1.stats()
{
sharded: true,
ns: 'testdb.table1',
count: 100000,
shards: {
shard1: {
ns: 'testdb.table1',
count: 3xxxx,
},
shard2: {
ns: 'testdb.table1',
count: 3xxxx,
},
shard3: {
ns: 'testdb.table1',
count: 33102,
}
},
ok: 1
}
可使用 sh.status() 查看各个数据库的分片使用情况
■■ 集群启停
■ 启动顺序
先启动 config server
再启动 shard server
再启动 mongos server
mongod -f /data/mongodb/conf/config.conf --ipv6
mongod -f /data/mongodb/conf/shard1.conf --ipv6
mongod -f /data/mongodb/conf/shard2.conf --ipv6
mongod -f /data/mongodb/conf/shard3.conf --ipv6
mongos -f /data/mongodb/conf/mongos.conf --ipv6
■ 关闭时,直接killall进程
killall mongos
killall mongod
文章评论