Fork me on GitHub

mongodb高可用实战篇

mongodb replset practical experience

Posted by Kaelzhang on January 18, 2017

img

前言

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有以下特点:

  1. 至少6个字符,小于1024字节
  2. 认证时候不考虑文件中空白字符
  3. 连接到副本集的成员和mongos进成的keyfile文件内容必须一样
  4. 必须是base64编码,但是不能有等号
  5. 文件权限必须是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))
}

补充说明

切换推荐实施步骤

对于具体实施步骤,在尽量不影响现有业务的前提下可以参照下面步骤来实施:

  1. 部署新数据库集群;
  2. 部署业务进行数据验证;
  3. 备份原始数据库数据;
  4. 还原原始数据库数据到新集群;
  5. 部署业务进行数据验证;
  6. 将正式业务的数据访问导到新集群;
  7. 关闭并清理原始数据库。

集群节点增删

在使用副本集时,有时会对副本集内节点进行增删操作,以将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");

当然除了增删以外,副本集还支持很多其他操作,这里就不一一列举了,感兴趣的读者可查阅官方文档

参考文献

  1. 《Mongodb权威指南》
  2. 《Mongodb官方文档》
  3. 《mongodb复制—创建副本集》

  • 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议。转载请注明出处!