前言
mongodb高可用实战分为概念篇和实战篇两部分内容。从概念理解到项目实践详尽介绍mongodb高可用相关内容以及如何在项目中实施。本篇为实战篇,主要以实际项目为背景一步步实现一个mongodb高可用集群。
实战篇
下面介绍下云测试系统是如何应用mongodb副本集的。首先云测试系统采用的是经典的一主二从模式,数据库部署上不限于一地,以做到异地容灾。其次云测试的mongodb数据库是docker化的,数据库的数据是在docker启动时会挂载到容器中。由于使用副本集容器启动方式跟之前不一样了,因此首先需要修改的事mongodb的dockerfile文件。
改造mongodb数据库镜像
改造前:
FROM docker-registry.zte.intra:5000/ubuntu4zte:latest
ADD ./ /app
WORKDIR /app
CMD ./bin/mongod --dbpath /app/mongodb/data --logpath /app/mongodb/log/log.txt --nojournal --auth
相较之前的mongodb镜像,在启动mongodb的时候需要加replSet和keyFile两个参数,其中replSet参数就是让mongodb以副本集的方式启动并且明确集群的名字,keyFile则是实现数据库集群访问控制的关键(最近mongodb黑客赎金事件想必大家也有所耳闻,就是连最基本的鉴权都没有使能)。 改造后:
FROM docker-registry.zte.intra:5000/ubuntu4zte:latest
ADD ./ /app
WORKDIR /app
CMD ./bin/mongod --dbpath /app/mongodb/data --logpath /app/mongodb/log/log.txt --nojournal --keyFile mongodb-keyfile --replSet test_mmm
如果觉得参数过多不易管理的话,还可以使用配置文件,例如: dockerfile:
FROM docker-registry.zte.intra:5000/ubuntu4zte:latest
ADD ./ /app
WORKDIR /app
CMD ./bin/mongod -f /etc/mongod.conf
配置文件内容如下:
# 数据库文件保存位置
dbpath=/app/mongodb/data
# 日志文件的保存位置
logpath=/app/mongodb/log
# nojournal用于故障恢复和持久化,64位计算机一般设为nojournal
nojournal=true
# keyFile文件指定
keyFile=mongodb-keyfile
# 副本集名称
replSet=test_mmm
好像还没介绍keyFile文件从哪来,接下来让我们来生成一个keyFile吧!
生成keyFile文件
keyFile文件中的内容是副本集中成员之间的共享密码,也就是说各个成员必须共享同一份keyFile文件。生成keyFile文件的方式很简单,只需在linux机器上输入下面两行命令即可:
openssl rand -base64 756 > <path-to-keyfile>
chmod 400 <path-to-keyfile>
keyFile有以下特点:
- 至少6个字符,小于1024字节
- 认证时候不考虑文件中空白字符
- 连接到副本集的成员和mongos进成的keyfile文件内容必须一样
- 必须是base64编码,但是不能有等号
- 文件权限必须是x00,也就是说,不能分配任何权限给group成员和other成员(仅对UNIX系统而言)
keyFile文件实例化:
Ni1UWB5DPTeShvTOOgvBoOi24pe1Vuy5Lk6qxKYlODbFoXpC9vfFnz1bCpL+vLbi
94+kwwQ0RHJ9qNdjnAeZKB2tatnxqCYCCbjH2VPsSZcTvDINt1HxbtqDhKVGsSIh
EwzPCgIeF6SkX/1DR8B763jvXSuRX1g4WUniRBdSPY5PLrg3Q9gBJKqtWhl8ld67
SH7VEmAS7KCC/WoO1XnKqMR2wV69KfIkHxmorFbDp+27ingliuG5W6XhO0BpHJK8
PqFqGkQ+tYHkJLJiiSnjgwKAzbcWTThRSyREEVWrgxiEAgiD3XoL40NXQm8H0cAI
WgsCRACPp5bsBQ1HdW3t3EBli0GpCqBte0e+kvFvHuf0g12ndOvqlJU7DKRWayEt
WFtdF6c4pgvgFw/vnnQgM2YCHilYR8zjFbtnMeoTaZ3E1nh0QjlNH85Ru3ze5Uml
3qUBKPk3iVQv9gP9XZWPbtVYA5r+w5UhiE1UInSOQuJ9Wnqwv+Phaf4yn/nIPiU/
oLyv9IB74ItlIhBz6va+az/dmM5XQEkcJb7V5ppwwUwiDCIpZdLnvHY/QSPdyPmU
uS1GhFS2YlFYR9s3iUww/5lxEKGb4t7w/RApZT8AOcrBNqfgKZ0R1tJoTkEK/hE1
Qmcq9GYZSo5TZ9O74GpoioZMxJiVvxT8yhjdQfzLhCqdQDkxlpOZPCK3WAarpM22
OOtHzVXOKSvHogF4fe42h8OEB1AARPY089rW6DC4VW6gvgSN2/lLk5Tl+uc/h0cC
dLaDf8XY9FwqNaVaBCviwgeGyZ2P0n9FnAqNK7dkotrt1DwfFXsqKDf5om+TgePK
uh4pPRtGdMO/H5JgSzXiqVPpqRJYOBitdfHP+hXlqHYfhuwp40/6w44MAEsCwZ+K
i8z0chwPcmJj05Ea4rwEFrfPRbf2VQSWwY5h/q9JLUzrfgJZi8h5XBgjUWfRV4sV
f+i/Hk1X7rv00wSVKcP5JzkfVozG
另外,mongodb为生产环境提供了更佳安全可靠的鉴权机制x.509。
启动并初始化集群
在启动集群前,还需要制作docker镜像mongodb_replset,并将其推送到内部dockerhub中。然后在数据库服务器上执行下面的命令启动数据库:
docker run --name mongodb1 -d -v /dev/shm/app/mongodb:/app/mongodb -p 27017:27017 docker-registry.zte.intra:5000/mongodb_replset
分别在10.10.10.1、10.10.10.2及10.10.10.3上启动数据库后,还要对数据库集群进行初始化:
rs.initiate(
{"_id" : "test_mmm",
"members" : [
{"_id" : 1, "host" : "10.10.10.1:27017"},
{"_id" : 2, "host" : "10.10.10.2:27017"},
{"_id" : 3, "host" : "10.10.10.3:27017"}
]
});
注意该命令只能在主节点上执行。
部署验证可以通过rs.status()命令来查看:
test_mmm:SECONDARY> rs.status()
{
"set" : "test_mmm",
"date" : ISODate("2017-01-13T02:13:39.592Z"),
"myState" : 2,
"term" : NumberLong(16),
"syncingTo" : "10.10.10.3:27017",
"heartbeatIntervalMillis" : NumberLong(2000),
"optimes" : {
"lastCommittedOpTime" : {
"ts" : Timestamp(0, 0),
"t" : NumberLong(-1)
},
"appliedOpTime" : {
"ts" : Timestamp(1484274019, 7),
"t" : NumberLong(16)
},
"durableOpTime" : {
"ts" : Timestamp(1484273508, 1),
"t" : NumberLong(13)
}
},
"members" : [
{
"_id" : 1,
"name" : "10.10.10.1:27017",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 19,
"optime" : {
"ts" : Timestamp(1484274019, 7),
"t" : NumberLong(16)
},
"optimeDate" : ISODate("2017-01-13T02:20:19Z"),
"syncingTo" : "10.10.10.3:27017",
"infoMessage" : "syncing from: 10.10.10.3:27017",
"configVersion" : 3,
"self" : true
},
{
"_id" : 2,
"name" : "10.10.10.2:27017",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 19,
"optime" : {
"ts" : Timestamp(1484274019, 7),
"t" : NumberLong(16)
},
"optimeDate" : ISODate("2017-01-13T02:20:19Z"),
"syncingTo" : "10.10.10.3:27017",
"infoMessage" : "syncing from: 10.10.10.3:27017",
"configVersion" : 3,
"self" : true
},
{
"_id" : 3,
"name" : "10.10.10.3:27017",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 18,
"optime" : {
"ts" : Timestamp(1484274019, 7),
"t" : NumberLong(16)
},
"optimeDurable" : {
"ts" : Timestamp(0, 0),
"t" : NumberLong(-1)
},
"optimeDate" : ISODate("2017-01-13T02:20:19Z"),
"optimeDurableDate" : ISODate("1970-01-01T00:00:00Z"),
"lastHeartbeat" : ISODate("2017-01-13T02:13:36.144Z"),
"lastHeartbeatRecv" : ISODate("2017-01-13T02:13:39.155Z"),
"pingMs" : NumberLong(27),
"electionTime" : Timestamp(1484274019, 5),
"electionDate" : ISODate("2017-01-13T02:20:19Z"),
"configVersion" : 3
}
],
"ok" : 1
}
可以看出10.10.10.3节点目前为主(PRIMARY),其余两个节点目前均为从(SECONDARY)。另外通过该命令还可以查看节点的状态,如下就是一个异常状态:
{
"_id" : 2,
"name" : "10.10.10.2:27017",
"health" : 0,
"state" : 8,
"stateStr" : "(not reachable/healthy)",
"uptime" : 0,
"optime" : {
"ts" : Timestamp(0, 0),
"t" : NumberLong(-1)
},
"optimeDurable" : {
"ts" : Timestamp(0, 0),
"t" : NumberLong(-1)
},
"optimeDate" : ISODate("1970-01-01T00:00:00Z"),
"optimeDurableDate" : ISODate("1970-01-01T00:00:00Z"),
"lastHeartbeat" : ISODate("2017-01-13T02:13:36.382Z"),
"lastHeartbeatRecv" : ISODate("1970-01-01T00:00:00Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "Connection refused",
"configVersion" : -1
},
ssh到该节点上发现mongodb的容器实例已经退出了。
业务改造
业务改造以云测试系统的env服务为例,该服务主要职责是测试环境管理。
部署脚本改造
由于之前是连单库的,故需要对env服务的部署脚本进行调整,将其中docker执行从单库连接:
HOST_IP=10.10.10.1
docker run --name everest-env-default -d -e DB_IP=$HOST_IP -e DB_PORT=27017 -e HA_PROXY_IP=$HOST_IP -p :80 -v /tmp/servicelog/env:/app/log $DOCKER_REGISTRY/everest-env-default
改为多库连接:
REPL_SET=10.10.10.1:27017,110.10.10.2:27017,10.10.10.3:27017
docker run --name everest-env-default -d -e DB_SET=$REPL_SET -e HA_PROXY_IP=$HOST_IP -p :80 -v /tmp/servicelog/env:/app/log $DOCKER_REGISTRY/everest-env-default
dockerfile改造
由于传入参数发生变化,还需将env服务的dockerfile中CMD从:
CMD ./env -p 80 -db $DB_IP:$DB_PORT -m $HA_PROXY_IP:8000
改为:
CMD ./env -p 80 -db $DB_SET -m $HA_PROXY_IP:8000
调用示例
具体业务代码是完全不需要修改的,addr为传入参数:
func main() {
// 连接数据库
addr := "10.10.10.1:27017,10.10.10.2:27017,10.10.10.3:27017"
session, err := mgo.Dial(addr)
if err != nil {
panic(err)
return
}
defer session.Close()
// 数据库鉴权
err = session.DB("admin").Login("user", "psw")
if err != nil {
log.Fatal("!!!auth failed to mongodb:", addr, ", err:", err.Error())
session, err = mgo.Dial(addr)
if err != nil {
log.Fatal("!!!Can not connect to mongodb:", addr, ", err:", err.Error())
return
}
}
// 数据操作示例
col := session.DB("cloud_test_all").C("test")
count, err2 := col.Count()
if err2 != nil {
panic(err2)
}
fmt.Println(fmt.Sprintf("Messages count: %d", count))
}
补充说明
切换推荐实施步骤
对于具体实施步骤,在尽量不影响现有业务的前提下可以参照下面步骤来实施:
- 部署新数据库集群;
- 部署业务进行数据验证;
- 备份原始数据库数据;
- 还原原始数据库数据到新集群;
- 部署业务进行数据验证;
- 将正式业务的数据访问导到新集群;
- 关闭并清理原始数据库。
集群节点增删
在使用副本集时,有时会对副本集内节点进行增删操作,以将10.10.10.4替换10.10.10.3为例,分别使用两种方法实现增删:
使用reconfig方法
config = {_id:"test_mmm",members:[{_id:0,host:'10.10.10.1:27017'},{_id:1,host:'10.10.10.2:27017'},{_id:1,host:'10.10.10.4:27017'}]}
rs.reconfig(config)
使用add和remove方法
rs.remove("10.10.10.3:27017");
rs.add("10.10.10.4:27018");
当然除了增删以外,副本集还支持很多其他操作,这里就不一一列举了,感兴趣的读者可查阅官方文档。
参考文献
- 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议。转载请注明出处!