MongoDB自身可组成分片加复制的集群,在这个集群的前端加上负载均衡器(比如HAProxmy + Keepalived),就可组建成一个无单点故障、十分完美的高可用负载均衡集群(如图8-1所示)。
图8- 1
整个MongDB高可用体系结构中,存在四个应用集群:入口路由集群Mongos、配置集群“Config Server”、分片集群1、分片集群2。
入口路由集群“Mongos”由负载均衡器来实现高可用及负载均衡功能。
配置集群“Config Server”的高可用及负载均衡有MongDB自身功能实现。
分片集群至少有两组及两组以上的集群所组成,每一个分片集群是一个备份集,即每个单独的分片集群的节点所保存的数据是一致的。多个分片集群,由配置服务“Config Server”提供负载分发,分片集群本身的高可用性由MongDB自身功能实现。
一个可用于生产环境的MongDB高可用集群,除了负载均衡外,至少要12个单元运行这些服务。看起来单元数量很多,但对系统的配置要求不高,比如路由集群Mongos和配置集群“Config Server”。虽然可以在一个操作系统上运行多个MongoDB实例(不同的端口区分不同的功能),但还是建议一个系统一个实例,这样便于维护与扩容。将所有运行MongoDB实例的系统,部署在Proxmox VE超融合集群平台,那就更有优势,比容器部署、管理都方便。因为仅需在虚拟机系统安装MongoDB,并做好基础的配置,然后以此做克隆,快速生成所需的运行单元。
MongoDB支持多种Linux 发行版包管理器进行安装,比如Debian之“apt”、Suse之“zypper”、Rocky/Centos之“dnf/yum”等。作者所采用的操作系统版本为Rocky 9.2,包管理器自然就是“dnf/yum”了。
Rocky 9默认情况下,执行指令“dnf install mongodb-org”是得不到期望的结果。用文本编辑器在目录“/etc/yum.repo.d”创建文件“mongodb-org-6.0.repo”,其完整内容如下:
[mongodb-org-6.0] name=MongoDB Repository baseurl=https://repo.mongodb.org/yum/redhat/9Server/mongodb-org/6.0/x86_64/ gpgcheck=1 enabled=1 gpgkey=https://www.mongodb.org/static/pgp/server-6.0.asc |
为保证软件仓库源的正确性,我们需要对文件“mongodb-org.repo”中的“baseurl”与“gpgkey”的设定做检查,比如用浏览器访问“baseurl”设定的地址,应该是真实存在的,如图8-2所示。
同样,用浏览器访问https://www.mongodb.org/static/pgp/server-6.0.asc,也可获得正确的结果。
接下来,系统命令行执行“dnf install mongdb-org”,就应该可以进行MongoDB的安装,如图8-3所示。
安装过程基本不会有错误出现,并且速度很快。安装完成以后,会在系统目录“/etc”生成MongoDB所必须的配置文件“mongod.conf”。我们可直接对其修改进行相应配置,也可单独自建配置文件,在启动“mongod”服务时加选项“-f”进行显式指定。
登录Proxmox VE超融合集群Web管理后台,以安装好MongoDB的虚拟机为基础,克隆出其它所需的运行单元,如图8-4所示。
如果没有作者这样的超融合集群平台,就挨个安装系统和MongoDB。
分片集群是实际存储数据的设施,对集群的单个节点而言,存储空间与系统独立,在文件系统层面就是使用独立的分区(如图8-5所示)。分片Shard最少是两组集群,每组三个节点或者更多节点。两组集群本身之间不会产生直接的联系,客户端的读写请求,调度到哪一组集群是由“Config Server”决定的。
我们约定,MongoDB分片的第一个集群的副本集名称为“shard1”,第二个副本集的名称为“shard2”;数据的实际存储路径为“/data/mongodb/data”,日志文件路径为“/data/momgodb/logs”. 为调试和配置集群方便起见,MongoDB暂时不设置管理员密码,集群节点之间的暂不启用认证机制。
分片Shard集群1的配置文件“/etc/mongod.conf”完整内容如下:
# mongod.conf # for documentation of all options, see: # http://docs.mongodb.org/manual/reference/configuration-options/ # where to write logging data. systemLog: destination: file logAppend: true path: /data/mongodb/logs/mongod.log # Where and how to store data. storage: dbPath: /data/mongodb/data journal: enabled: true engine: wiredTiger: # how the process runs processManagement: timeZoneInfo: /usr/share/zoneinfo # network interfaces net: port: 27017 bindIp: 0.0.0.0 #security: replication: replSetName: shard1 sharding: clusterRole: shardsvr |
将此配置同步到分片集群“shard1”中的其它节点,然后执行命令“systemctl start mongod”启动所有节点的MongoDB服务。启动过程没有任何错误信息输出,不一定代表正确启动,需要进一步验证。比如作者启动的MongoDB,就存在问题,没有被正确的启动运行,如图8-6所示。
导致MongoDB启动失败的原因是配置文件书写格式的问题,键与值之间需要一个空格站位,比如键“clusterRole:”,冒号后需要输入一个空格,再输入值“shardsvr”。
还有一种情况也能导致“mongod”服务启动失败,那就是数据存储目录“/data/mogodb”的属主与属组需要修改成“mongod:mongod”,因为以包管理器安装的MongoDB启动服务是以普通账号(安装过程会自动生成系统普通账号)“mongod”来执行的。不要养成一看到权限问题,就用“chmod -R 777 dir”胡乱赋权的坏习惯。
确认分片集群“shard1”所有节点的MongoDB服务都正常启动以后,切换到这些节点的任意节点系统命令行,登录到MongoDB交互界面,进行初始化操作,将所有节点组成一个集群,具体的命令如下:
[root@MongoDB-200-149 ~]# mongosh Current Mongosh Log ID: 6491279f3770cc22c927a6b6 Connecting to: mongodb://127.0.0.1:27017/?directConnection=true&serverSelectionTimeoutMS=2000&appName=mongosh+1.10.0 Using MongoDB: 6.0.6 Using Mongosh: 1.10.0 For mongosh info see: https://docs.mongodb.com/mongodb-shell/ To help improve our products, anonymous usage data is collected and sent to MongoDB periodically (https://www.mongodb.com/legal/privacy-policy). You can opt-out by running the disableTelemetry() command. ------ The server generated these startup warnings when booting 2023-06-19T17:00:44.278+08:00: Access control is not enabled for the database. Read and write access to data and configuration is unrestricted 2023-06-19T17:00:44.278+08:00: /sys/kernel/mm/transparent_hugepage/enabled is 'always'. We suggest setting it to 'never' 2023-06-19T17:00:44.278+08:00: vm.max_map_count is too low ------ test> use admin switched to db admin shard1 [direct: primary] admin> rs.initiate({ ... _id:"shard1", ... members:[ ... {_id:0,host:"10.122.200.148:27017",priority:1}, ... {_id:1,host:"10.122.200.149:27017",priority:1}, ... {_id:2,host:"10.122.200.150:27017",priority:1} ... ] ... }); { ok: 1 } |
分片集群“shard1”创建完毕后,继续在此交互界面执行指令“rs.status()”,查看刚创建好的分片集群“shard1”的运行状态,如图8-7所示。
依照此法,将第二个分片集群“shard2”也创建好。
MongoDB配置服务器集群“Config Server”节点配置文件,除了副本集名称(replSetName)与集群角色(clusterRole)不同而外,其他的设定与分片集群“shard1”完全相同。在这里,我们约定,集群副本集名字为“configcls”,而集群的角色指定为“configsvr”。注意:副本集的名称可以随意命名,而集群角色是有限定的。一个完整的配置服务器“Config Server”配置文件“/etc/mongodb.conf”的内容如下:
# mongod.conf systemLog: destination: file logAppend: true path: /data/mongodb/logs/mongod.log storage: dbPath: /data/mongodb/data journal: enabled: true engine: wiredTiger: # how the process runs processManagement: timeZoneInfo: /usr/share/zoneinfo # network interfaces net: port: 27017 bindIp: 0.0.0.0 #security: replication: replSetName: configcls sharding: clusterRole: configsvr |
启动配置集群“Config Server”所有节点的MongoDB服务“systemctl start mongod”,确认MongoDB服务的监听地址没有绑定到接口“127.0.0.1”上。用MongoDB客户端工具“mongosh”登录,创建集群“configcls”,具体的指令如下:
[root@MongoDB-200-147 ~]# mongosh 10.122.200.147 Current Mongosh Log ID: 64917e4587284270d42be6ec Connecting to: mongodb://10.122.200.147:27017/?directConnection=true&appName=mongosh+1.10.0 Using MongoDB: 6.0.6 Using Mongosh: 1.10.0 ------省略若干-------------------------------- test> use admin switched to db admin admin> rs.initiate({ ... _id:"configcls", ... members:[ ... {_id:0,host:"10.122.200.154:27017",priority:1}, ... {_id:1,host:"10.122.200.147:27017",priority:1}, ... {_id:2,host:"10.122.200.143:27017",priority:1} ... ] ... }); { ok: 1, lastCommittedOpTime: Timestamp({ t: 1687256871, i: 1 }) } |
集群建立起后,继续在交互界面执行“rs.status()”验证其正确性。
Mongos路由本身不需要像分片“Shard”和配置“Config Sever”以副本集方式集群,多个Mongos节点由前端的负载均衡器(例如HAProxy或者Nginx)一起组成高可用集群。Mongos路由集群分三个大的步骤:单节点配置与配置集群的关联、单节点与分片集群的关联以及节点间的集群。
编辑Mongos所在节点的配置文件“/etc/mongod.conf”,将配置服务集群“Config Server”添加到文件中,一个已经编辑好的“/etc/mongod.conf”文件的完整内容如下:
# mongod.conf systemLog: destination: file logAppend: true path: /data/mongodb/logs/mongod.log engine: wiredTiger: # how the process runs processManagement: timeZoneInfo: /usr/share/zoneinfo fork: true # network interfaces net: port: 27017 bindIp: 0.0.0.0 sharding: configDB: configcls/10.122.200.154:27017,10.122.200.147:27017,10.122.200.143:27017 |
保存文件后,执行命令“mongos -f /etc/mongod.conf &”启动路由服务。注意:执行“systemctl start mongos”将不能成功,除非手动创建一个“systemd”服务。
用命令行指令“mongosh”验证路由服务“mongos”的正确性。客户端“mongosh”连接成功,会输出连接地址、MongDB版本信息等,然后进入交互界面,等待用户输入。
在运行正常的Mongos路由节点,以“mongosh”登录本地“mongos”,执行指令“sh.status()”查看一下路由初始状态,其输出如图8-8所示。
在Mongos客户端交互界面继续输入如下指令,将前边建立起来的两个分片“Shard”集群添加进来:
[direct: mongos] admin> sh.addShard("shard1/10.122.200.148:27017,10.122.200.149:27017,10.122.200.150:27017") { shardAdded: 'shard1', ok: 1, '$clusterTime': { clusterTime: Timestamp({ t: 1687260112, i: 9 }), signature: { hash: Binary(Buffer.from("0000000000000000000000000000000000000000", "hex"), 0), keyId: Long("0") } }, operationTime: Timestamp({ t: 1687260112, i: 9 }) } [direct: mongos] admin> sh.addShard("shard2/10.122.200.151:27017,10.122.200.152:27017,10.122.200.153:27017") { shardAdded: 'shard2', ok: 1, '$clusterTime': { clusterTime: Timestamp({ t: 1687260132, i: 8 }), signature: { hash: Binary(Buffer.from("0000000000000000000000000000000000000000", "hex"), 0), keyId: Long("0") } }, operationTime: Timestamp({ t: 1687260132, i: 8 }) |
上述指令执行完毕,就达到将路由与分片相关联的目的。这时,我们输入指令“sh.status()”,查看其状态,应该有分片信息存在,如图8-9所示。
依照此法,将剩余节点的Mongos路由启动并与Shard分片关联。需要注意的是,Mongos路由节点之间,并不存在关联,不像分片Shard集群与配置集群“Config Server”,节点之间是相互感知的(比如主节点故障,剩余节点立即选举新的主节点)。由于存在多个Mongos路由节点,某个路由Mongos-A节点写入的数据,能否被其它路由Mongos-B节点所识别呢?如果不能,或者产生讹误,就不能将这些路由Mongos节点组成集群。
以MongoDB客户端工具,连接到路由Mongos节点“10.122.200.143”,创建数据库“sery”,并插入几条数据,具体的指令及输出如下:
[root@MongoDB-200-144 ~]# mongosh 10.122.200.144 Current Mongosh Log ID: 6492a92a52c3eae27b3b24c8 Connecting to: mongodb://10.122.200.144:27017/?directConnection=true&appName=mongosh+1.10.0 Using MongoDB: 6.0.6 Using Mongosh: 1.10.0 ……………………………………………. [direct: mongos] admin> show dbs admin 112.00 KiB config 2.77 MiB [direct: mongos] admin> use sery switched to db sery [direct: mongos] sery> db.sery.insertOne({"one":"001"}) { acknowledged: true, insertedId: ObjectId("6492cc189467940c5c0dfbb9") } [direct: mongos] sery> db.sery.insertOne({"two":"002"}) { acknowledged: true, insertedId: ObjectId("6492cc3c9467940c5c0dfbbb") } |
上边的插入操作,将在MongoDB中创建数据库“sery”。继续在Mongosh交互界面,输入指令“sh.status()”,可进一步了解数据库“sery”被写入到哪个分片Shard集群,如图8-10所示。
登录到另一个Mongos路由节点“10.122.200.145”,查看在Mongos路由节点“10.122.200.144”创建的数据库“sery”,是否可以被检索到,操作指令及输出结果如图8-11所示。
相应的,在Mongos路由节点“10.122.200.145”创建数据库,然后登录Mongos路由节点“10.122.200.144”,也应该能够看到所有创建好的数据库。
在负载均衡器所在的宿主系统,文本编辑器创建文件“/etc/haproxy/mongos.cfg”,并添加如下文本行:
frontend mongos mode tcp bind *:27017 option tcplog log global default_backend mycat_lb backend mongos_lb mode tcp balance source server mongos144 10.122.200.144:27017 check server mongos145 10.122.200.145:27017 check server mongos146 10.122.200.146:27017 check |
HAProxy对配置文件执行语法检查无误后,启动或重启“Keepalived”服务,远程客户端仅需用VIP加端口27017来连接MongoDB高可用集群。
在本章“8.4”节,我们创建了一个名为“sery”的数据库,并在其中插入了两条数据,并且我们已经定位到“sery”数据的数据被存储到分片集群“shard2”中。而我们可能希望数据库“sery”的一部分数据被存储到分片集群“shard1”,而另一部分被存储于分片集群“shard2”。数据分片以横向方式扩展数据容量,以支持更大规模的数据存储及读写效率。
以Mongosh登录Mongos路由集群(以负载均衡器VIP加TCP27017端口),创建新的数据库“formyz”,开启分片功能并以循环方式向数据库“formyz”的表“mytable”插入50000条记录,然后查验数据库“formyz”的记录是否被分散存储到所有的分片集群。具体的操作及输出如下:
#开启数据库“formyz”分片功能,同时创建空的数据库“formyz” [direct: mongos] admin> sh.enableSharding("formyz") { ok: 1, '$clusterTime': { clusterTime: Timestamp({ t: 1687403738, i: 2 }), signature: { hash: Binary(Buffer.from("0000000000000000000000000000000000000000", "hex"), 0), keyId: Long("0") } }, operationTime: Timestamp({ t: 1687403738, i: 1 }) } #在数据库“formyz”创建表“mytable”,基于键“_id”进行“hash”分片 [direct: mongos] admin> sh.shardCollection("formyz.mytable",{"_id":"hashed"}); { collectionsharded: 'formyz.mytable', ok: 1, '$clusterTime': { clusterTime: Timestamp({ t: 1687404504, i: 28 }), signature: { hash: Binary(Buffer.from("0000000000000000000000000000000000000000", "hex"), 0), keyId: Long("0") } }, operationTime: Timestamp({ t: 1687404504, i: 24 }) } #数据库“formyz”的表“mytable”插入5万条记录 [direct: mongos] admin> use formyz switched to db formyz [direct: mongos] formyz> for(i=1;i<=50000;i++){db.usertable.insertOne({"id":i,"name":"nnn"+i})} { acknowledged: true, insertedId: ObjectId("6493c13abcd4d10dfc6f054c") } |
数据插入完毕,继续在Mongos路由交互界面输入指令“sh.status()”,查验数据库“formyz”表“mytable”数据的分布情况,如图8-12所示。
从状态输出可知,数据库“formyz”的数据确实分布到分片集群“shard1”和“shard2”,到达预期目标。
MongoDB高可用集群已经创建完毕,分片功能已经验证通过,基于数据安全层面的考虑,需要给MongoDB数据访问设置账号、各成员节点之间启用安全认证。