更多请看 Linux MongoDB 安装并搭建集群(服务器架设篇)
MongoDB(来自于英文单词“Humongous”,中文含义为“庞大”)是可以应用于各种规模的企业、各个行业以及各类应用程序的开源数据库。作为一个适用于敏捷开发的数据库,MongoDB的数据模式可以随着应用程序的发展而灵活地更新。与此同时,它也为开发人员 提供了传统数据库的功能:二级索引,完整的查询系统以及严格一致性等等。 MongoDB能够使企业更加具有敏捷性和可扩展性,各种规模的企业都可以通过使用MongoDB来创建新的应用,提高与客户之间的工作效率,加快产品上市时间,以及降低企业成本。
MongoDB是专为可扩展性,高性能和高可用性而设计的数据库。它可以从单服务器部署扩展到大型、复杂的多数据中心架构。利用内存计算的优势,MongoDB能够提供高性能的数据读写操作。 MongoDB的本地复制和自动故障转移功能使您的应用程序具有企业级的可靠性和操作灵活性。
对比mysql/oracle请看 对比MySQL,什么场景MongoDB更适用
上面的四种方式,灵活度是逐渐递增的,当然安装难度也是逐渐递增的。
一般就是可执行文件安装,很简单大家都懂的不做介绍。
我这次使用的是Windows10上安装VirtualBox,虚拟出来的CentOS6.9来模拟的。本身喜欢更新的CentOS7.x系列但是公司电脑上只有Cent6的镜像,大致原理一样,就这样吧。
我先简单介绍下包管理器安装的方式:
这种方式很简单操作如下:
官方安装指南
先建立yum资源文件
使用vim创建下面的文件
sudo vim /etc/yum.repos.d/mongodb-org-4.0.repo
填入下面的内容后保存文件
[mongodb-org-4.0]
name=MongoDB Repository
baseurl=https://repo.mongodb.org/yum/redhat/$releasever/mongodb-org/4.0/x86_64/
gpgcheck=1
enabled=1
gpgkey=https://www.mongodb.org/static/pgp/server-4.0.asc
yum命令行安装
sudo yum install -y mongodb-org
启动
sudo service mongod start
检查是否启动成功
cat /var/log/mongodb/mongod.log
#里面有这样的提示语就是可以了
[initandlisten] waiting for connections on port
设置开机自启动
sudo chkconfig mongod on
停止与重启
# 停止
sudo service mongod stop
# 重启
sudo service mongod restart
这样就安装完了
可以登陆进mongodb体会下怎么玩
mongo --host 127.0.0.1:27017
其实二进制软件包安装是上面四种安装方式的第2,3种。我以具体平台的二进制安装包也就是第二种来讲,相比第3种,这种方式在具体的平台上安装方便些而且集成了SSL加密,第3种安装方式在Linux上更加通用,不过没有集成SSL加密,需要SSL的还是要自己再次配置
安装
# 下载CentOS6 X64安装包
[root@localhost local]# tar -zxvf mongodb-linux-x86_64-rhel62-4.0.1.tgz
[root@localhost local]# cd mongodb
[root@localhost mongodb]# ls
bin GNU-AGPL-3.0 LICENSE-Community.txt MPL-2 README THIRD-PARTY-NOTICES
[root@localhost mongodb]# pwd
/usr/local/mongodb
# 将MongoDB的安装目录和bin目录配置到PATH里
[root@localhost mongodb]# vim /etc/profile
[root@localhost mongodb]# source /etc/profile
# 如果开启了Selinux,设置放行mongodb监听27017端口
semanage port -a -t mongod_port_t -p tcp 27017
# 或者修改/etc/selinux/config 关闭Selinux,重启生效
SELINUX=disabled
# 或者修改/etc/selinux/config 设置Selinux为宽容模式,重启生效
SELINUX=permissive
接下来就是命令行启动mongodb和设置mongodb开机自启动了。这里我暂时不演示了,启动时指定数据库的有关参数就可以了,相比包管理器安装灵活很多但是开机自启动脚本还是得自己写,建议参考包管理器安装后的自启动脚本来写
常见的有三种:
1. 主从结构 (master-slave)
2. 副本集 (Replication)
3. 分片集(Sharding)
当前主从结构已经是弃用不被推荐使用了的,主从复制弃用,这样我就不做分析了,副本集的原理与主从很相似,唯一不同的是,在主节点出现故障的时候,主从配置的从服务器不会自动的变为主服务器,而是要通过手动修改配置.但是副表集就不用,它会自动选出一台服务器做为主节点,从而保障系统的稳定性。那么关于副本集和分片集,各有优劣,副本集配置简单性能要好一些,但是并不适合大型的项目,主要在于一主多副的结构限制了它的写数据能力无法做到负载均衡,所有的删除,修改,添加操作都要在一个主数据服务器上进行,但是读数据的能力很强,一般多个主副库服务器可以做到读数据负载均衡,总的来讲副本集配置简单些,同配置下单台服务器下的性能比分片集高。分片集有点类似于Mysql的集群而副本集类似于Mysql的主从,与mysql类似,集群模式单台性能一般都会低于主从。分片集的有点在水平扩展力好,结构灵活多样,可以做到读写的负载均衡和动态加入新的数据节点。缺点在于为了达到这些目的,数据节点间需要很多资源来维护这些关系,单台的能力并不强。不过目前也有分片集和副本集结合的配置,这种算是很高级的集群了,在性能,可靠性,ACID上取得了不错的效果。
关于副本集和分片集请看:
一般我们中型项目用上副本集就可以满足要求了,毕竟业务的主要压力还是在于读这一块,写相对来讲小的多,中型项目的服务器也不是很多,单台的数据服务器能力比较重要。即使倾向架设集群,可能服务器的数量也不是那么多。
我们项目目前没到大型超大型数据的阶段,当前副本集集群就可以了,如果后续数据压力上来了再考虑副本集和分片集混合集群方案升级也方便。
为了演示方便,我直接使用包管理器安装,软件平台为windows10安装VirtualBox开3个CentOS6.9虚拟机。3个虚拟机间的是可以互相ping通网络的,并且防火墙都放行了27017端口。安装教程请看:https://docs.mongodb.com/manual/tutorial/install-mongodb-on-red-hat/
当前我们有3台虚拟机在同一个局域网,momgo端口都是27017。
192.168.56.102
momgo端口:27017
192.168.56.104
momgo端口:27017
192.168.56.101
momgo端口:27017
放行27017端口,方便mongo相互访问
[root@localhost ~]# semanage port -a -t mongod_port_t -p tcp 27017
[root@localhost ~]# /sbin/iptables -I INPUT -p tcp --dport 27017 -j ACCEPT
[root@localhost ~]# /etc/init.d/iptables save
iptables: Saving firewall rules to /etc/sysconfig/iptables:[ OK ]
设置mongodb监听
# MongoDB 4.0.1
[root@localhost ~]# mongo -version
MongoDB shell version v4.0.1
git version: 54f1582fc6eb01de4d4c42f26fc133e623f065fb
OpenSSL version: OpenSSL 1.0.1e-fips 11 Feb 2013
allocator: tcmalloc
modules: none
build environment:
distmod: rhel62
distarch: x86_64
target_arch: x86_64
# 修改配置
[root@localhost ~]# vi /etc/mongod.conf
# network interfaces
net:
port: 27017
bindIp: 0.0.0.0 # 改为0.0.0.0监听所有ip的访问
# 重启
[root@localhost ~]# service mongod restart
Stopping mongod: [ OK ]
Starting mongod: [ OK ]
# 在类外一台虚拟机(192.168.56.104)上查看这台虚拟机(192.168.56.102)的mongo端口27017访问情况,如下为成功
[root@localhost ~]# telnet 192.168.56.102 27017
Trying 192.168.56.102...
Connected to 192.168.56.102.
Escape character is '^]'.
# 同样的操作对例外的虚拟机也这样操作
开启副本模式
[root@localhost ~]# vi /etc/mongod.conf
# network interfaces
net:
port: 27017
bindIp: 0.0.0.0 # Enter 0.0.0.0,:: to bind to all IPv4 and IPv6 addresses or, alternatively, use the net.bindIpAll setting.
#security:
#operationProfiling:
replication: # 这行解除屏蔽
replSetName: tcl # 这行加上,代表集合名字为tcl,其余的保持默认配置就好
# 这种完了重启mongo
[root@localhost ~]# service mongod restart
Stopping mongod: [ OK ]
Starting mongod: [ OK ]
# 其余的虚拟机也按上面的操作即可
mongo接入副本集
在这几个虚拟机的任意一台登入mongodb
# 创建副本集配置
> config = {_id: "tcl", members: [
... {_id: 0, host: "192.168.56.102:27017"},
... {_id: 1, host: "192.168.56.104:27017"},
... {_id: 2, host: "192.168.56.101:27017"}
... ]}
{
"_id" : "tcl",
"members" : [
{
"_id" : 0,
"host" : "192.168.56.102:27017"
},
{
"_id" : 1,
"host" : "192.168.56.104:27017"
},
{
"_id" : 2,
"host" : "192.168.56.101:27017"
}
]
}
# 初始化副本集
> rs.initiate(config)
{
"ok" : 1,
"operationTime" : Timestamp(1535349879, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1535349879, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
# 这里上一条命令没有报错说明副本集设置成功,当前这台mongo是从机,我们可以再手动设置它为主机
tcl:SECONDARY> rs.isMaster()
{
"hosts" : [
"192.168.56.102:27017",
"192.168.56.104:27017",
"192.168.56.101:27017"
],
"setName" : "tcl",
"setVersion" : 1,
"ismaster" : true,
"secondary" : false,
"primary" : "192.168.56.102:27017",
"me" : "192.168.56.102:27017",
"electionId" : ObjectId("7fffffff0000000000000001"),
"lastWrite" : {
"opTime" : {
"ts" : Timestamp(1535349892, 1),
"t" : NumberLong(1)
},
"lastWriteDate" : ISODate("2018-08-27T06:04:52Z"),
"majorityOpTime" : {
"ts" : Timestamp(1535349892, 1),
"t" : NumberLong(1)
},
"majorityWriteDate" : ISODate("2018-08-27T06:04:52Z")
},
"maxBsonObjectSize" : 16777216,
"maxMessageSizeBytes" : 48000000,
"maxWriteBatchSize" : 100000,
"localTime" : ISODate("2018-08-27T06:05:06.469Z"),
"logicalSessionTimeoutMinutes" : 30,
"minWireVersion" : 0,
"maxWireVersion" : 7,
"readOnly" : false,
"ok" : 1,
"operationTime" : Timestamp(1535349892, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1535349892, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
# 查看副本集状态
tcl:PRIMARY> rs.status()
{
"set" : "tcl",
"date" : ISODate("2018-08-27T06:05:17.595Z"),
"myState" : 1,
"term" : NumberLong(1),
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"heartbeatIntervalMillis" : NumberLong(2000),
"optimes" : {
"lastCommittedOpTime" : {
"ts" : Timestamp(1535349911, 1),
"t" : NumberLong(1)
},
"readConcernMajorityOpTime" : {
"ts" : Timestamp(1535349911, 1),
"t" : NumberLong(1)
},
"appliedOpTime" : {
"ts" : Timestamp(1535349911, 1),
"t" : NumberLong(1)
},
"durableOpTime" : {
"ts" : Timestamp(1535349911, 1),
"t" : NumberLong(1)
}
},
"lastStableCheckpointTimestamp" : Timestamp(1535349891, 1),
"members" : [
{
"_id" : 0,
"name" : "192.168.56.102:27017",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 448,
"optime" : {
"ts" : Timestamp(1535349911, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2018-08-27T06:05:11Z"),
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "could not find member to sync from",
"electionTime" : Timestamp(1535349889, 1),
"electionDate" : ISODate("2018-08-27T06:04:49Z"),
"configVersion" : 1,
"self" : true,
"lastHeartbeatMessage" : ""
},
{
"_id" : 1,
"name" : "192.168.56.104:27017",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 38,
"optime" : {
"ts" : Timestamp(1535349911, 1),
"t" : NumberLong(1)
},
"optimeDurable" : {
"ts" : Timestamp(1535349911, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2018-08-27T06:05:11Z"),
"optimeDurableDate" : ISODate("2018-08-27T06:05:11Z"),
"lastHeartbeat" : ISODate("2018-08-27T06:05:15.998Z"),
"lastHeartbeatRecv" : ISODate("2018-08-27T06:05:16.564Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "",
"syncingTo" : "192.168.56.102:27017",
"syncSourceHost" : "192.168.56.102:27017",
"syncSourceId" : 0,
"infoMessage" : "",
"configVersion" : 1
},
{
"_id" : 2,
"name" : "192.168.56.101:27017",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 38,
"optime" : {
"ts" : Timestamp(1535349911, 1),
"t" : NumberLong(1)
},
"optimeDurable" : {
"ts" : Timestamp(1535349911, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2018-08-27T06:05:11Z"),
"optimeDurableDate" : ISODate("2018-08-27T06:05:11Z"),
"lastHeartbeat" : ISODate("2018-08-27T06:05:15.998Z"),
"lastHeartbeatRecv" : ISODate("2018-08-27T06:05:16.551Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "",
"syncingTo" : "192.168.56.102:27017",
"syncSourceHost" : "192.168.56.102:27017",
"syncSourceId" : 0,
"infoMessage" : "",
"configVersion" : 1
}
],
"ok" : 1,
"operationTime" : Timestamp(1535349911, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1535349911, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
tcl:PRIMARY>
验证
同步功能
# 主库建一个库和表并插入一条数据
tcl:PRIMARY> use testdb;
switched to db testdb
tcl:PRIMARY> db.synctable.insert({name:"验证"});
WriteResult({ "nInserted" : 1 })
# 进入从库
# 报错,正常。因为SECONDARY是不允许读写的,可以执行rs.slaveOk();解除
tcl:SECONDARY>
tcl:SECONDARY> show dbs;
2018-08-27T14:13:17.963+0800 E QUERY [js] Error: listDatabases failed:{
"operationTime" : Timestamp(1535350391, 1),
"ok" : 0,
"errmsg" : "not master and slaveOk=false",
"code" : 13435,
"codeName" : "NotMasterNoSlaveOk",
"$clusterTime" : {
"clusterTime" : Timestamp(1535350391, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
} :
_getErrorWithCode@src/mongo/shell/utils.js:25:13
Mongo.prototype.getDBs@src/mongo/shell/mongo.js:67:1
shellHelper.show@src/mongo/shell/utils.js:876:19
shellHelper@src/mongo/shell/utils.js:766:15
@(shellhelp2):1:1
# 解除限制
tcl:SECONDARY> rs.slaveOk();
tcl:SECONDARY> show dbs;
admin 0.000GB
config 0.000GB
local 0.000GB
testdb 0.000GB
tcl:SECONDARY> use testdb;
switched to db testdb
tcl:SECONDARY> db.synctable.find();
{ "_id" : ObjectId("5b8395ae80365b296a69ddc0"), "name" : "验证" }
# 可以看到同步成功
模拟主库崩溃后的从库自动选举为主库
当前192.168.56.102为主库,192.168.56.104、192.168.56.101为从库,没有设置仲裁节点
我们关闭主库服务器
tcl:PRIMARY> db.synctable.insert({name:"验证"});
WriteResult({ "nInserted" : 1 })
tcl:PRIMARY>
bye
[root@localhost ~]# service mongod stop
Stopping mongod: [ OK ]
# 192.168.56.104
tcl:SECONDARY>
tcl:SECONDARY>
bye
[root@localhost ~]#
# 192.168.56.101当选为新的主库
tcl:PRIMARY>
tcl:PRIMARY>
bye
[root@localhost ~]#
# 继续模拟192.168.56.101新主库也崩溃
# 192.168.56.101
tcl:PRIMARY>
bye
[root@localhost ~]# service mongod stop
Stopping mongod: [ OK ]
# 192.168.56.104
tcl:SECONDARY>
tcl:SECONDARY>
bye
[root@localhost ~]#
# 为什么192.168.56.104没有再次成为新的主库呢? 主要是除了它自己没有其他mongo了,不再有其他mongo给他投选票了,它将永远的为从库没法当选为主库了。解决办法是在副本集添加仲裁节点或者设置一个隐藏从节点,这样工作节点无论崩溃多少个,只要有仲裁或者隐藏节点,就一定选的出新的主节点。
复制集通过replSetInitiate
命令(或mongo shell
的rs.initiate()
)进行初始化,初始化后各个成员间开始发送心跳消息,并发起Priamry
选举操作,获得『大多数』成员投票支持的节点,会成为Primary
,其余节点成为Secondary
。
『大多数』的定义
假设复制集内投票成员(后续介绍)数量为N,则大多数为 N/2 + 1,当复制集内存活成员数量不足大多数时,整个复制集将无法选举出Primary,复制集将无法提供写服务,处于只读状态。
投票成员数 | 大多数 | 容忍失效数 |
---|---|---|
1 | 1 | 0 |
2 | 2 | 0 |
3 | 2 | 1 |
4 | 3 | 1 |
5 | 3 | 2 |
6 | 4 | 2 |
7 | 4 | 3 |
.. | .. | .. |
我们可以这样计算 假设 有 x 台节点在复制集里面,其中1台为主其余的都是从,即x个节点都是为可以被选举的也可以选举别人。那么主节点崩溃后剩下 x-1个节点,这x-1个节点每个都有x-1-1即x-2个人可以选它,那么实际投票成员数n=x-2。每个节点就必须实际至少有y=n/2+1=x/2张选票(y>=1,n>=1,x>=2)才可以成为主。那么如果x=3,崩溃了一个x=2,y=1这时得到一张选票就可以了先得选票的先成为主,当在崩溃了一个x=1,y=0这时选民都为0了就无法当选了。解决思路就是设置一个不参与数据负载的仲裁节点或隐藏节点。它们基本不会因为数据压力而崩溃,永远会投出决定性的一票。
启动各个mongod节点,在主节点下添加用户,会自动同步到其他复制集中
tcl:PRIMARY> db.createUser({ user: "tcl", pwd: "tcl123", roles: [{ role: "root", db: "admin" }] })
Successfully added user: {
"user" : "tcl",
"roles" : [
{
"role" : "root",
"db" : "admin"
}
]
}
[root@localhost ~]# openssl rand -base64 745 > mongodb-keyfile
[root@localhost ~]# ls
anaconda-ks.cfg install.log install.log.syslog mongodb-keyfile
[root@localhost ~]# mv mongodb-keyfile /var/lib/mongo/
[root@localhost mongo]# chown mongod:mongod mongodb-keyfile
[root@localhost mongo]# chmod 400 mongodb-keyfile
[root@localhost mongo]# vi /etc/mongod.conf
# 修改配置如下
# network interfaces
net:
port: 27017
bindIp: 0.0.0.0 # Enter 0.0.0.0,:: to bind to all IPv4 and IPv6 addresses or, alternatively, use the net.bindIpAll setting.
security:
authorization: enabled # 开启认证
clusterAuthMode: keyFile
keyFile: /var/lib/mongo/mongodb-keyfile #指定文件路径
#operationProfiling:
# 重启
[root@localhost mongo]# service mongod restart
Stopping mongod: [ OK ]
Starting mongod: [ OK ]
# 其他的虚拟机也按这样操作
# 最后全部的节点重启,再登陆验证。看到这次mongo登陆报的警告少了很多
[root@localhost mongo]# mongo
MongoDB shell version v4.0.1
connecting to: mongodb://127.0.0.1:27017
MongoDB server version: 4.0.1
tcl:PRIMARY>
这样安全配置基本就完成了,只有拥有同一份认证文件的mongodb节点才可以加入到副本集里面来,未认证的节点则无法加入集合。同时设置用户和密码防止恶意登陆**