MongoDB是个面向文档管理的NoSQL数据库,能够直接存取JSON数据,支持海量数据。公司内部的一个单点登录系统使用MongoDB来存储用户的session,以便在不同应用,服务器之间共享登录信息,解决了服务器切换用户状态丢失(被登出)的问题。单点登录系统前端采用PHP,与LDAP交互认证用户信息,提供登录界面,API等。MongoDB则采用Replica Set模式以便支持高可用。
在CentOS 6.5上安装MongoDB,首先添加源仓库
$ suod vim /etc/yum.repos.d/mongodb.repo [mongodb] name=MongoDB Repository baseurl=http://downloads-distro.mongodb.org/repo/redhat/os/x86_64/ gpgcheck=0 enabled=0
安装,启动,连接MongoDB
$ sudo yum --enablerepo=mongodb install mongodb-org $ sudo /sbin/chkconfig --levels 235 mongod on $ sudo mongod --port 27017 --dbpath /data/db1 $ mongo --port 27017
创建用户SSOReplUser
> use admin > db.createUser( { user: "SSOReplUser", pwd: "<password>", roles: [ { role: "root", db: "admin" } ] });
创建集群要用到的key
$ sudo openssl rand -base64 741 > /home/sso/mongodb-keyfile $ sudo chmod 600 /home/sso/mongodb-keyfile
编辑MongoDB配置文件
$ sudo vim /etc/mongod.conf # Replication Options # in replicated mongo databases, specify the replica set name here replSet=SSOReplSet # maximum size in megabytes for replication operation log #oplogSize=1024 # path to a key file storing authentication info for connections # between replica set members keyFile=/home/sso/mongodb-keyfile
重启mongod服务
$ sudo /etc/init.d/mongod stop $ sudo /usr/bin/mongod -f /etc/mongod.conf
使用SSOReplUser登录
> use admin > db.auth("SSOReplUser", "<password>");
初始化集群
> rs.initiate() > rs.conf() { "_id": "SSOReplSet", "version": 1, "members": [ { "_id": 1, "host": "192.168.33.10:27017" } ] }
在其他机器上按照上面步骤安装MongoDB,更改配置,复制对应的mongodb-keyfile并启动。
回到192.168.33.10并添加机器
SSOReplSet:PRIMARY> rs.add("192.168.33.11:27017") SSOReplSet:PRIMARY> rs.add("192.168.33.12:27017") SSOReplSet:PRIMARY> rs.add("192.168.33.13:27017") #SSOReplSet:PRIMARY> rs.remove("192.168.33.13:27017")
在从机上认证查看
SSOReplSet:SECONDARY> use admin SSOReplSet:SECONDARY> db.auth("SSOReplUser", "<password>"); SSOReplSet:SECONDARY> rs.status()
MongoDB的Replica set模式最少要求3台机器,以便在PRIMARY机器故障时,能够自我选举出新的PRIMARY,但需要n/2+1的机器投票同意。例如刚才配置了4台机器,那么需要4/2+1=3台机器投票,能够承受一台机器故障。如果是集群有5台机器,则能够承受2台机器故障。因此再配置一台不存储数据的Arbiter机器比较合理。
按照上面的步骤安装,复制mongodb-keyfile文件,但需要更改mongod.conf
$sudo vim /etc/mongod.conf nojournal=true
然后启动mongod服务并在PRIMARY机器上将这台机器加入机器
SSOReplSet:PRIMARY> use admin SSOReplSet:PRIMARY> db.auth("SSOReplUser", "<password>"); SSOReplSet:PRIMARY> rs.addArb("<ip of arbiter>");
PHP的mongo扩展安装比较简单,下载对应版本,启用即可。然而在应用过程中发现登录有点慢,启用看PHP mongo扩展profiling功能,查看PHP日志
<?php \MongoLog::setLevel(\MongoLog::ALL); \MongoLog::setModule(\MongoLog::ALL); try{ echo microtime(true) . PHP_EOL; echo "aaa01(primary)" . PHP_EOL; $m = new \MongoClient("mongodb://admin:[email protected]:27017/?replicaSet=SSOReplSet "); echo microtime(true) . PHP_EOL; echo "aaa01(primay), bbb01(secondary),ccc01(secondary),ddd01(secondary)" . PHP_EOL; $m = new \MongoClient("mongodb://admin:[email protected]:27017,192.168.33.11:27017,192.168.33.12:27017,192.168.33.13:27017/?replicaSet=SSOReplSet "); echo microtime(true) . PHP_EOL; } catch (\MongoConnectionException $e) { var_dump($e); }
MongoDB 的写操作默认在Primary节点上操作完成即返回成功;读操作默认是也是从Primary上读取,所以需要去查询服务器。由于SSO应用部署在多个数据中心,网络抖动会造成较大影响,跨数据中心的查询并不会很快,如果每次都去连接并查询整个列表是比较耗时。另外如果在php里面配置的是IP而MongoDB Replica Set里面配置的是域名,则连接名会出现不匹配,而创建新的连接并销毁旧连接,也耗时。如果配置的是域名,则需要DNS解析。由于每台PHP服务器均已配置HA检测,最终每个应用只配置了一台服务器,并统一配置MongoDB集群为IP。而连接最快的是在Primary机器上,可以根据本机是否Primary来做HA:
#!/usr/bin/env bash count=`ps -fe |grep "mongod" | grep -v "grep" | wc -l` FILE="/home/scripts-bits/master.conf" SERVER=`hostname` if [ $count -lt 1 ]; then rm -f $FILE else PRIMAY=`/usr/bin/mongo ${SERVER}:27017 --quiet --eval 'printjson(db.isMaster().ismaster);'` if [ "$PRIMAY" == "true" ]; then if [ ! -f "$FILE" ]; then touch "$FILE" fi REMOVE=`/usr/bin/mongo ${SERVER}:27017/admin --quiet /home/scripts-bits/mongo_status.js` fi fi
删除故障节点(not reachable/healthy)的脚本mongo_status.js:
db.auth('admin','<password>'); conf=rs.status(); members=conf["members"]; for(i in members){ if(members[i]["state"] == 8){ rs.remove(members[i]["name"]); } }
这中间也出现过因为网络/机器故障,导致PHP等待连接超时的情况的,将该机器从集群中移除即可。然而当服务器是启动的(可以到达)情况下,MongoDB故障,则不会超时。可以更改connectTimeoutMS,以便减少等待。
MongoDB的日志默认都是记录在/var/log/mongodb/下面并且会越来越大。创建Python脚本来定时清除它:
#!/bin/env python import commands import datetime,time def rotate_log(path, expire = 30): str_now = time.strftime("%Y-%m-%d") dat_now = time.strptime(str_now, "%Y-%m-%d") array_dat_now = datetime.datetime(dat_now[0], dat_now[1], dat_now[2]) lns = commands.getoutput("/bin/ls --full-time %s|awk '{print $6, $9}'" % path) for ln in lns.split('\n'): ws = ln.split() if len(ws) != 2: continue ws1 = time.strptime(ws[0], "%Y-%m-%d") ws2 = datetime.datetime(ws1[0], ws1[1], ws1[2]) if (array_dat_now - ws2).days > expire: v_del = commands.getoutput("/bin/rm -rf %s/%s" % (path, ws[1])) def rotate_mongo(): # get mongo pid mongo_pid = commands.getoutput("/sbin/pidof mongod") #print mongo_pid # send Sig to mongo if mongo_pid != '': cmd = "/bin/kill -USR1 %s" % (mongo_pid) # print cmd mongo_rotate = commands.getoutput(cmd) else: print "mongod is not running..." if __name__ == "__main__": log_path = "/var/log/mongodb/" expire = 30 rotate_mongo() rotate_log(log_path, expire)
加入到crontab里面去执行
10 1 * * * /usr/bin/python /home/sso/mongo_rotate.py > /dev/null 2>&1
参考连接
mongodb与mysql相比的优缺点
PHP MongoDB 复制集合
MongoDB Replication
MongoDB Enable Auth
MongoDB Rotate Log Files
Blocking connect() leads to cumulative timeouts for multiple inaccessible servers
How Raft consensus algorithm will make replication even better in MongoDB 3.2
Write Concern for Replica Sets
MongoDB Read Preference