MongoDB4.0 分片集群+复制集+读写分离详细搭建教程

最近由于要存储大规模结构化数据,想到了MongoDB的口号For giant ideas,于是假设了下图架构的一个mongoDB集群。

先说说选型的原因,结构化数据很多数据库都可以存储,最常用的是mysql,而且mysql的集群也屡见不鲜。比如mycat或者mysqlproxy这种数据库网关就可以放置在多个普通的mysql服务器前面,或者像腾讯退出的TDSQL这种商业版本也是有内置数据库网关的,通过数据库网关解析SQL语句,然后把不同的INSERT语句分拆,分别放到不同的数据库中。而且mysql数据库原生可以通过binlog做某一个分片的复制集,但是mysql数据库缺点也有很多,比如mongoDB可以存储松散的结构化数据,其属性可以随时修改,但是mysql集群中要添加表的一列或修改表结构其成本和风险是巨大的。第二,MongoDB的这一整套路由分发和复制集都是mongoDB原生组件完成的,如mongos组件发挥的就是数据库路由器的功能,而该组件是mongodb官方开发和维护,其适配性要比采用第三方的数据网关要好一些。

MongoDB4.0 分片集群+复制集+读写分离详细搭建教程_第1张图片

除了选型之外,其他的数据分割的逻辑与mysql集群大同小异,都需要选择片键然后根据片键去分割数据,并确定数据落在哪一个分片上。

我上面建立的这个集群一共使用了8台物理服务器。最上面三台物理服务器中,每台物理服务器里放一个config(mongod)进程,和一个mongos进程。三个config节点组成config复制集,这样任意一个config挂掉都不会对整体造成影响。三个mongos采用相同的配置,在连接mongoDB集群的时候,将URL写成

mongodb://username:[email protected]:2000,192.168.1.2:2000,192.168.1.3:2000/DatabaseName

这种模式,由客户端自动的在3个mongos中选择一个连接,并在一个mongos出故障时由客户端自动迁移到另一个mongos上去。这样就不怕某个mongos突然挂掉。

下面的操作使用的MongoDB版本为4.0.6

然后是中间三台物理服务器每个里面都运行着一个mongod进程,用来存放真实的业务数据,这三个服务器的mongod均设置为Master,并且分属不同的分片,加载不同内容的配置文件。

然后我们就有了3个分片了,然后我们再为每个分片设置复制集,组成一个3x3的集群,即3个分片,每个分片里有3个数据副本,防止数据丢失。

于是在同一个机房或者同城机房内在找一台算力和存储都比Master大一些的两台物理机,每台物理机里执行三个mongod进程,分别数据分片1,分片2和分片3的Slave。这样三分片三复制集的集群就搭建完成了。下面是详细步骤

搭建Config节点复制集

config节点有一个就够用了,但是为了实现高可用,我这里使用3台物理机跑三个config节点,并组成复制集。

config节点的配置文件如下

sharding:
  clusterRole: configsvr
replication:
  replSetName: confset
net:
  port: 7017
  bindIp: 0.0.0.0
  
systemLog:
  destination: file
  logAppend: true
  path: /mnt/mongo/conf/mongod.log
  
storage:
  dbPath: /mnt/mongo/conf
  journal:
    enabled: true

processManagement:
  fork: true  # fork and run in background
  pidFilePath: /mnt/mongo/conf/mongod.pid  # location of pidfile
  timeZoneInfo: /usr/share/zoneinfo

跑mongod进程很方便,只需编写好一个简短的配置文件就可以了,这也是mongodb比mysql方便的地方,即配置文件结构非常简单,不需要考虑太多。

其中要注意,clusterRole必须填configsvr,这一点与普通的mongod数据库进程不一样。因为config节点虽然也可以当做普通的mongo数据来使用,但是其最主要的作用不是存储业务数据,而是与mongos进程配合,存储每个分片节点的信息,数据分片规则,和分片后每个chunk的存储信息。所以不应该在config节点中存储任何业务数据。

replSetName可以自己随便起,但在三个config节点中应该保持相同,这样才能把三个config节点添加到同一个复制集中去

port就是开放的端口,注意和其他的进程不冲突就可以了,我这里三台config节点的配置文件都是一样的,用的一样的数据库端口。

systemLog就是配置mongo运行时产生log日志的存放位置,storage里的dbPath就是存放数据库数据文件的位置,如果目录不存在需要手工创建。

在这里要提醒一下,默认搭建出来的mongo数据库集群是没有管理员密码的,连接也不需要提供用户名和密码,非常不安全。所以我们这里先使用mongodb集群密码验证的方式生成相关的密钥。

生成密钥使用openssl命令即可,如果数据库服务器本身没有openssl命令,可以在其他设备上生成后拷贝过来

openssl rand -base64 741 > /etc/mongo/mongodb-keyfile

生成的密钥大概是下面这个样子,懒得生成的同学可以直接把我下面这个拷贝过去使用

bJd/FkxJaYfZPL5bbP7eRI2sh8EydhuZa8jXUtzlT+uYz0oKHKhnMBECsX+CZfRG
xebslFdXrWdAuhwVvSoFDGtp9o7O9kwJbna0txKxhFfeYcDh5X81hQT7TbflY1oH
JVgCFMrfejz9vR5bMa5ieumhDJ0WruvO3y1fvOVZN4/SDkWahxY88mF40GTambGw
q6VeIV4aEPOrqDUu7TqTzuaCGZBRd6EQoWGqQ/bsmq8Q37hag8Pdk1dFE0jyUvsA
ol00lIFN+49OkTcO/t20/MUae+opcIn8sUtV7a80WyiklH9AxIRR6Xjl/Peb+vbg
gsyq2XK3LFqga/WWDY16AYGBBGn53oCxjkmDH+HZ5VL2IlAoEELUIYHKpSiafUCg
/fsVloVJh7niZaOsBrsd7ltK3SDeK2BNdtSGFLdRR80CzmYn+CAmcW87pxlhTopQ
iqmHbkAL6AwLyDUkuqVjQCExYNT5jOv2UqHwq758m1PRZJrfikBM3m/oiF1j+R7c
i8sqz2qHYKyc4C2l3bRM27IIpayJB976CK4a9TOI/qunNLLNlWGcKlJOvHSDbHEN
mvFP5AekkL1rhmPLdk8GQdWTPXe52qTHsH10T9okF59r0qnqx3AXsuOTw/AR1r5d
qq3ERE/xB/y2mrXVRmkygel6UKwA7q5UjVQt6w0JubnhKWWtb4xVUbvSgHC/M9jY
MaVx6B9O4Ga2ENrAGkH/8iRXiR7BlMR9DZeE+QG1vaz35c/Gj74EbqnGrmyCnlNV
T+J1ovhPg6q0L4UsPCWoaXCz2d4HOBNr6Tr3sd7L0wB3UdWINXA3kMUqOiUSLpt/
2VbnETSdLoNWtezzT09SY4wn+N5yvHbZt+ugwB1i62FvNQLx3IIq9sJqIPvR8R3W
HlhxiYTWQp9IaKxIuITOZ+eL+ybmym4jGHErfheRFnNq9ywnJt/ZiGoiPB3xWYZl
4qmi9ZRb38MMNI/8wnwVKm5lWYM4

把密钥文件放到相关目录后,还需要修改密钥文件的权限,否则mongod进程会启动失败

chown mongod:mongod /etc/mongo/mongodb-keyfile 
chmod 600 /etc/mongo/mongodb-keyfile

然后我们在配置文件上加入下面这几行,就可以开启mongodb集群的密码功能了。注意这个mongodb-keyfile要保存好,之后所有分片,复制集的mongod进程都要使用同一个mongodb-keyfile才能建立集群。

security:
  keyFile: /etc/mongo/mongodb-keyfile

然后我们在三个物理服务器上建立相同的目录,主要是数据库数据存储目录,pid文件目录,log文件目录,然后把密钥文件mongodb-keyfile和mongod配置文件在三台服务器上都拷贝一个,就可以准备启动了。

假设我们mongod的配置文件为/etc/mongo/config.conf,那么在三台config节点服务器上都运行

mongod --config /etc/mongo/config.conf

启动三个config节点,然后我们使用mongo命令任意连接其中一个节点,初始化配置三个config节点为一个复制集

rs.initiate(
  {
    _id: "confset",
    configsvr: true,
    members: [
      { _id : 0, host : "192.168.1.1:7017" },
      { _id : 1, host : "192.168.1.2:7017" },
      { _id : 2, host : "192.168.1.3:7017" }
    ]
  }
)

上面的_id就是前面配置文件中replSetName你起的名字,在三个config节点的配置文件中是相同的,这里rs.initiate的命令只需在一个config节点中执行即可,其他两个节点会自动添加进来,无需每个节点都执行一遍。但前提是你必须在防火墙上打开你设置的监听端口,上文中是7017,保证三个节点可以互相通信

这样,由三个mongod组成的复制集集群就建设好了,此时推荐建立root用户,这样在开启了密码之后,我们就可以方便的对config节点登录和修改配置。

此时要注意添加root用户的时机,要在复制集初始化之后,密钥配置之前配置。因为如果还没有初始化集群,那么创建用户命令就只对一个节点生效,需要创建三遍,而如果创建的不一致还会导致复制集的问题,所以要先初始化集群。初始化之后如果配置文件中带着上面讲的keyFile: /etc/mongo/mongodb-keyfile来启动,那么启动之后仍然没有权限创建新用户,恰当的时机就是一开始不写keyFile: /etc/mongo/mongodb-keyfile这两行到配置文件中去,然后初始化集群,创建root用户,然后把mongod全部kill掉再在配置文件中加入这两行后重启mongod

db.createUser(
  {
    user: "root",
    pwd: "passwd",
    roles: [ { role: "root", db: "admin" } ]
  }
)

官网的文档只给了role为userAdminAnyDatabase的用户,但是这个用户的权限并不大,不能修改集群内的配置,所以还是需要root role。

建立一个分片节点并组成复制集

有了config节点之后,下一步就需要建立分片节点,而且为了保证一个分片里数据库挂掉之后数据不丢失,还需要给每个分片建立一个复制集组成集群里的集群,这样不仅有了多个数据副本,还可以实现读写分离。

首先我们在Shard 1 Master这台服务器上建立第一个分片的主节点。同样我们先建立数据库数据存储目录,pid文件目录,log文件目录,然后把密钥文件mongodb-keyfile拷贝到/etc/mongo/下,或者其他的什么位置,然后开始编写mongod的配置文件

sharding:
  clusterRole: shardsvr
replication:
  replSetName: shard1
net:
  port: 17017
  bindIp: 0.0.0.0
  
systemLog:
  destination: file
  logAppend: true
  path: /mnt/mongo/shard1/mongod.log
  
storage:
  dbPath: /mnt/mongo/shard1
  journal:
    enabled: true

processManagement:
  fork: true  # fork and run in background
  pidFilePath: /mnt/mongo/shard1/mongod.pid  # location of pidfile
  timeZoneInfo: /usr/share/zoneinfo
  
security:
  keyFile: /etc/mongo/mongodb-keyfile

此时clusterRole:要改成shardsvr,这样才能和config节点,mongos节点一起组成分片集群。replSetName可以随便填一个,但是对于分片一和它的几个副本都应该是相同的名字。分片一和分片二两个复制集的replSetName一般为不一样的,和config节点的集群也应该不一样。

然后我们就可以启动master了,一般使用mongod用户启动

 mongod --config /etc/mongo/shard1Master.conf

然后我们去另外两台放Slave节点的服务器,编写slave节点的配置文件,而且别忘了建立数据库数据存储目录,pid文件目录,log文件目录,然后把密钥文件mongodb-keyfile拷贝到/etc/mongo/下。

sharding:
  clusterRole: shardsvr
replication:
  replSetName: shard1
net:
  port: 17017
  bindIp: 0.0.0.0
  
systemLog:
  destination: file
  logAppend: true
  path: /mnt/mongo/shard1/mongod.log
  
storage:
  dbPath: /mnt/mongo/shard1
  journal:
    enabled: true
  wiredTiger:
    engineConfig:
      cacheSizeGB: 4

processManagement:
  fork: true  # fork and run in background
  pidFilePath: /mnt/mongo/shard1/mongod.pid  # location of pidfile
  timeZoneInfo: /usr/share/zoneinfo
  
security:
  keyFile: /etc/mongo/mongodb-keyfile

这里要注意,repSetName要和Master节点填一样的。在我的集群里,由于有两台服务器是存放了三个分片的slave节点。而在mongodb的内存使用规则中,使用LRU方式,一个mongod进程最多可以使用50%的物理内存,所以三个节点可分配的内存就是150%的物理内存,这样最后肯定会触发OOM,所以在slave节点的配置文件中,要加上上面的cacheSizeGB,限定最大使用的内存数量,我这里使用的4GB,我的物理服务器大约有24GB的物理内存。

这里顺便提一下mongodb的内存管理,从MongoDB 3.2版本开始,就开始使用WiredTiger作为默认的存储引擎,下面摘自mongodb的官方document

Starting in MongoDB 3.4, the default WiredTiger internal cache size is the larger of either:

  • 50% of (RAM - 1 GB), or
  • 256 MB.

For example, on a system with a total of 4GB of RAM the WiredTiger cache will use 1.5GB of RAM (0.5 * (4GB - 1 GB) = 1.5 GB). Conversely, a system with a total of 1.25 GB of RAM will allocate 256 MB to the WiredTiger cache because that is more than half of the total RAM minus one gigabyte (0.5 * (1.25 GB - 1GB) = 128 MB < 256 MB).

在实际应用中,发现这个存储引擎特别的牛逼,比如我插入10GB的数据,只要数据量没有超过上面的内存限制,即总物理内存-1的一半,那么数据再保存在磁盘的同时,还会保存在内存中,如果你的数据库本身不大而物理内存特别大的话,那此时你已经有了一个内存型数据库,所有的查询在内存中就可以完成,这样的扫描速度往往是磁盘的上千倍。性能还是非常恐怖的。

官网文档还明确的指出,在一个服务器又多个mongod进程或者容器中运行时,需要设定cacheSizeGB这个参数,原文如下

NOTE

The storage.wiredTiger.engineConfig.cacheSizeGB limits the size of the WiredTiger internal cache. The operating system will use the available free memory for filesystem cache, which allows the compressed MongoDB data files to stay in memory. In addition, the operating system will use any free RAM to buffer file system blocks and file system cache.

To accommodate the additional consumers of RAM, you may have to decrease WiredTiger internal cache size.

The default WiredTiger internal cache size value assumes that there is a single mongod instance per machine. If a single machine contains multiple MongoDB instances, then you should decrease the setting to accommodate the other mongod instances.

If you run mongod in a container (e.g. lxccgroups, Docker, etc.) that does not have access to all of the RAM available in a system, you must set storage.wiredTiger.engineConfig.cacheSizeGB to a value less than the amount of RAM available in the container. The exact amount depends on the other processes running in the container.

 然后我们启动Master节点的mongod,然后启动两个slave节点的mongod,然后使用mongo命令连接master节点,来把这三个mongod节点配置成一主两备的单一分片集群

rs.initiate(
  {
    _id : "shard1",
    members: [
      { _id : 0, host : "192.168.1.4:17017" },
      { _id : 1, host : "192.168.1.5:17017" },
      { _id : 2, host : "192.168.1.6:17017" }
    ]
  }
)

上面的是最简单的配置方式,mongodb自己会决定哪个节点是master,哪些是salve,priority默认为0,priority越大越有可能成为master,但是你也可用通过传参数来手工指定priority,并且在节点数多于2的时候可以选择一个节点不存储数据只做仲裁。也就是三副本模式可以改成两副本+仲裁模式,如下

rs.initiate(
  {
    _id : "shard1",
    members: [
      { _id : 0, host : "192.168.1.4:17017" ,priority : 2 },
      { _id : 1, host : "192.168.1.5:17017" ,priority : 1 },
      { _id : 2, host : "192.168.1.6:17017" ,arbiterOnly :true }
    ]
  }
)

然后,为了能在集群之后还能登陆进节点修改数据库配置,我们要添加一个root用户,注意添加用户的时机应该是集群初始化之后,keyFile: /etc/mongo/mongodb-keyfile写在配置文件之前。等创建完root之后再把keyFile: /etc/mongo/mongodb-keyfile写入进配置文件重启。

db.createUser(
  {
    user: "root",
    pwd: "root",
    roles: [ { role: "root", db: "admin" } ]
  }
)

创建第二个分片的集群和第三个分片的集群也大同小异,下面光把配置文件贴出来

shard 2 master 配置文件:

sharding:
  clusterRole: shardsvr
replication:
  replSetName: shard2
net:
  port: 27017
  bindIp: 0.0.0.0
  
systemLog:
  destination: file
  logAppend: true
  path: /mnt/mongo/shard2/mongod.log
  
storage:
  dbPath: /mnt/mongo/shard2
  journal:
    enabled: true

processManagement:
  fork: true  # fork and run in background
  pidFilePath: /mnt/mongo/shard2/mongod.pid  # location of pidfile
  timeZoneInfo: /usr/share/zoneinfo
  
security:
  keyFile: /etc/mongo/mongodb-keyfile

shard 2 两个slave节点配置文件

sharding:
  clusterRole: shardsvr
replication:
  replSetName: shard2
net:
  port: 27017
  bindIp: 0.0.0.0
  
systemLog:
  destination: file
  logAppend: true
  path: /mnt/mongo/shard2/mongod.log
  
storage:
  dbPath: /mnt/mongo/shard2
  journal:
    enabled: true
  wiredTiger:
    engineConfig:
      cacheSizeGB: 4

processManagement:
  fork: true  # fork and run in background
  pidFilePath: /mnt/mongo/shard2/mongod.pid  # location of pidfile
  timeZoneInfo: /usr/share/zoneinfo
  
security:
  keyFile: /etc/mongo/mongodb-keyfile

初始化shard2 复制集的命令

rs.initiate(
  {
    _id : "shard2",
    members: [
      { _id : 0, host : "192.168.1.7:27017" },
      { _id : 1, host : "192.168.1.5:27017" },
      { _id : 2, host : "192.168.1.6:27017" }
    ]
  }
)

shard3 master 配置文件:

sharding:
  clusterRole: shardsvr
replication:
  replSetName: shard3
net:
  port: 37017
  bindIp: 0.0.0.0
  
systemLog:
  destination: file
  logAppend: true
  path: /mnt/mongo/shard3/mongod.log
  
storage:
  dbPath: /mnt/mongo/shard3
  journal:
    enabled: true

processManagement:
  fork: true  # fork and run in background
  pidFilePath: /mnt/mongo/shard3/mongod.pid  # location of pidfile
  timeZoneInfo: /usr/share/zoneinfo
  
security:
  keyFile: /etc/mongo/mongodb-keyfile

shard3 两个slave节点配置文件

sharding:
  clusterRole: shardsvr
replication:
  replSetName: shard3
net:
  port: 37017
  bindIp: 0.0.0.0
  
systemLog:
  destination: file
  logAppend: true
  path: /mnt/mongo/shard3/mongod.log
  
storage:
  dbPath: /mnt/mongo/shard3
  journal:
    enabled: true
  wiredTiger:
    engineConfig:
      cacheSizeGB: 4

processManagement:
  fork: true  # fork and run in background
  pidFilePath: /mnt/mongo/shard3/mongod.pid  # location of pidfile
  timeZoneInfo: /usr/share/zoneinfo
  
security:
  keyFile: /etc/mongo/mongodb-keyfile

初始化分片3复制集的命令

rs.initiate(
  {
    _id : "shard3",
    members: [
      { _id : 0, host : "192.168.1.8:37017" },
      { _id : 1, host : "192.168.1.5:37017" },
      { _id : 2, host : "192.168.1.6:37017" }
    ]
  }
)

注意三个分片mongod一般采用不同的接口这样不容易出错。

然后分别在shard2 和shard3创建root用户,一遍以后登录调试。

现在3个分片,9个配置文件,9个mongod进程已经都启动起来了,现在可以开始把这3个分片+config节点集群配置成一个大集群了。

首先我们编写mongos的配置文件,这个配置比起mysql的路由网关配置简单太多了,因为mongodb的mongos配置基本全是启动之后在里面配置,不用提前写进配置文件,这一点比mycat要方便很多,修改参数无需重启mongos了。

sharding:
  configDB: confset/192.168.1.1:7017,192.168.1.2:7017,192.168.1.3:7017
net:
  port: 2000
  bindIp: 0.0.0.0
  
systemLog:
  destination: file
  logAppend: true
  path: /mnt/mongo/mongos/mongod.log
  

processManagement:
  fork: true  # fork and run in background
  pidFilePath: /mnt/mongo/mongos/mongod.pid  # location of pidfile
  timeZoneInfo: /usr/share/zoneinfo

security:
  keyFile: /etc/mongo/mongodb-keyfile

同样的,我们需要建立日志目录,需要keyfile密钥文件,但是无需建立数据存储目录了,因为mongos不会存储任何数据,只做路由转发,而片键和chunk存储的位置等信息都记录在config节点上。

在配置文件中需要指明config集群中所有节点的IP和端口,并且保证网络畅通。然后使用mongos命令启动

mongos --config /etc/mongo/mongos.conf

启动一个或者多个mongos之后,使用mongo命令连接任何一个mongos,并且建立好root用户。注意此处可以先不加keyfile把整个集群创建完成,root用户建完,然后再每个节点修改配置文件加入keyfile,再把整个集群的所有节点重启一遍。

db.createUser(
  {
    user: "root",
    pwd: "root",
    roles: [ { role: "root", db: "admin" } ]
  }
)

然后使用root用户登录,把三个分片都添加进去,每个分片添加里面的一个节点即可,master和slave都行,因为复制集里任何一个节点都是可以的。

sh.addShard( "shard1/192.168.1.4:17017")
sh.addShard( "shard2/192.168.1.7:27017")
sh.addShard( "shard3/192.168.1.8:37017")

至此,我们的3x3集群,既包含复制集又包含分片的集群就搭建完成了,下一步就是选择片键,然后对collection进行切分,这又是一个很大的话题,可以看我的上一篇博客《MongoDB 4.0自动分片+手工分片+片键选择逻辑》

使用如下bash命令即可接入mongos进行配置

mongo --port 2000 -u root -p password --authenticationDatabase admin

建立好集群之后,使用root用户登录,然后就可以使用

rs.status()

命令来来查看集群中有哪些分片和数据库了。

注意mongos只需配置一遍,另外两个mongos只需按照配置文件启动起来即可,因为mongos会调取config节点提供的信息。

客户端连接数据库的时候,可以选择任意一个mongos连接,或者只选择一个,之用URL这种方式连接。URL方式连接的好处就是对于多种语言的驱动程序都能良好的支持,通用性比较高,因为用到mongo的场合很多,nodejs,python和JAVA都会经常调用,如果每种语言写一套连接方式那就太麻烦了。

下面是我常用的一种URL

mongodb://username:[email protected]:2000,192.168.1.2:2000,192.168.1.3:2000/数据库名?authSource=admin&readPreference=secondary&maxStalenessSeconds=120&connectTimeoutMS=300000

同时写明了用户名密码,三个mongos地址,数据库名,用户验证库,读写分离,超时时间等。在python,java,node均有很好的支持。

你可能感兴趣的:(MongoDB4.0 分片集群+复制集+读写分离详细搭建教程)