MongoDB HA Test with Pymongo

One CentOS 7 machine

One CentOS 7 machine

$ more /etc/redhat-release CentOS Linux release 7.2.1511 (Core)

Mongodb version

$ mongod --version db version v3.2.6 git version: 05552b562c7a0b3143a729aaa0838e558dc49b25 OpenSSL version: OpenSSL 1.0.1e-fips 11 Feb 2013 allocator: tcmalloc modules: none build environment: distmod: rhel70 distarch: x86_64 target_arch: x86_64

Pymongo version

>>> import pymongo >>> pymongo.version '3.2' >>>

1.1 Install mongodb

Please see https://docs.mongodb.com/manual/tutorial/install-mongodb-on-red-hat/

1.2 Configure mongodb

We will configure three nodes replication in one machine,like:

1) Prepare three mongodb config file

$ ls /etc/ | grep mongo mongod1.conf mongod2.conf mongod3.conf

Each config file is:

$ more /etc/mongod1.conf # mongod.conf systemLog: destination: file logAppend: true path: /var/log/mongodb1/mongod.log storage: dbPath: /var/lib/mongo1 journal: enabled: true processManagement: fork: true # fork and run in background pidFilePath: /var/run/mongodb1/mongod.pid # location of pidfile net: port: 27017 replication: replSetName: rs0 $ more /etc/mongod2.conf # mongod.conf systemLog: destination: file logAppend: true path: /var/log/mongodb2/mongod.log storage: dbPath: /var/lib/mongo2 journal: enabled: true processManagement: fork: true # fork and run in background pidFilePath: /var/run/mongodb2/mongod.pid # location of pidfile net: port: 27018 replication: replSetName: rs0 $ more /etc/mongod3.conf # mongod.conf systemLog: destination: file logAppend: true path: /var/log/mongodb3/mongod.log storage: dbPath: /var/lib/mongo3 journal: enabled: true processManagement: fork: true # fork and run in background pidFilePath: /var/run/mongodb3/mongod.pid # location of pidfile net: port: 27019 replication: replSetName: rs0

1.3 Start 3 mongodb instances and init them

$ sudo /usr/bin/mongod -f /etc/mongod1.conf $ sudo /usr/bin/mongod -f /etc/mongod2.conf $ sudo /usr/bin/mongod -f /etc/mongod3.conf

Connect to one of your mongod instances through the mongo shell. MongoDB initiates a set that consists of the current member and that uses the default replica set configuration.

1.4 Add the remaining members to the replica set.

rs.add("127.0.0.1:27018") rs.add("127.0.0.1:27019")

through rs.status we can see the status of the replica set

rs0:PRIMARY> rs.status() { "set" : "rs0", "date" : ISODate("2016-05-22T04:45:22.195Z"), "myState" : 1, "term" : NumberLong(3), "heartbeatIntervalMillis" : NumberLong(2000), "members" : [ { "_id" : 0, "name" : "127.0.0.1:27017", "health" : 1, "state" : 1, "stateStr" : "PRIMARY", "uptime" : 166805, "optime" : { "ts" : Timestamp(1463737764, 8), "t" : NumberLong(3) }, "optimeDate" : ISODate("2016-05-20T09:49:24Z"), "electionTime" : Timestamp(1463725527, 1), "electionDate" : ISODate("2016-05-20T06:25:27Z"), "configVersion" : 3, "self" : true }, { "_id" : 1, "name" : "127.0.0.1:27018", "health" : 1, "state" : 2, "stateStr" : "SECONDARY", "uptime" : 166799, "optime" : { "ts" : Timestamp(1463737764, 8), "t" : NumberLong(3) }, "optimeDate" : ISODate("2016-05-20T09:49:24Z"), "lastHeartbeat" : ISODate("2016-05-22T04:45:20.570Z"), "lastHeartbeatRecv" : ISODate("2016-05-22T04:45:20.570Z"), "pingMs" : NumberLong(0), "syncingTo" : "127.0.0.1:27019", "configVersion" : 3 }, { "_id" : 2, "name" : "127.0.0.1:27019", "health" : 1, "state" : 2, "stateStr" : "SECONDARY", "uptime" : 166794, "optime" : { "ts" : Timestamp(1463737764, 8), "t" : NumberLong(3) }, "optimeDate" : ISODate("2016-05-20T09:49:24Z"), "lastHeartbeat" : ISODate("2016-05-22T04:45:21.356Z"), "lastHeartbeatRecv" : ISODate("2016-05-22T04:45:21.356Z"), "pingMs" : NumberLong(0), "syncingTo" : "127.0.0.1:27017", "configVersion" : 3 } ], "ok" : 1 } rs0:PRIMARY>

1.5 Add authentication

1) Create the keyfile your deployment will use to authenticate to members to each other.

$ openssl rand -base64 741 > /home/mongodb/mongodb-keyfile $ chmod 600 mongodb-keyfile

2)Enable authentication for each member of the sharded cluster or replica set.

In each replica member’s configure file, please add this:

security: keyFile: /home/mongodb/mongodb-keyfile

If the replica members are in different machines, Pleas copy the key file to the host machine where the replica member located in.

3) Create user administrator

use admin db.createUser( { user: "myUserAdmin", pwd: "abc123", roles: [ { role: "userAdminAnyDatabase", db: "admin" } ] } )

4) Restart all mongodb instances

$ mongo MongoDB shell version: 3.2.6 connecting to: test rs0:PRIMARY> rs.status() { "ok" : 0, "errmsg" : "not authorized on admin to execute command { replSetGetStatus: 1.0 }", "code" : 13 } rs0:PRIMARY> use admin switched to db admin rs0:PRIMARY> db.auth("myUserAdmin","abc123") 1

2 Testing

2.1 Test case 1: Basic operations

We use pymongo to do some basic testing.

Connecting to a Replica Set

>>> from pymongo import MongoClient >>> MongoClient("10.75.44.10", replicaset='rs1') MongoClient([u'mongodb2:27017', u'mongodb1:27017', u'mongodb3:27017']) >>>

Write operation:

>>> from pymongo import MongoClient >>> db = MongoClient("127.0.0.1", replicaset='rs1').demo >>> db Database(MongoClient([u'mongodb2:27017', u'mongodb1:27017', u'mongodb3:27017']), u'demo') >>> db.connection.host '10.75.44.10' >>> db.connection.port 27017 >>>

看到目前对于数据库的操作是PRIMARY,也就是host mongodb1。 然后对数据库写入一条record:

>>> db.test.insert({'x':1}) ObjectId('54b8b1a1c77b3b3b354869a3') >>> db.test.find_one() {u'x': 1, u'_id': ObjectId('54b8b1a1c77b3b3b354869a3')} >>>

此时,把mongodb1的mongod stop掉。

>>> db.test.find_one() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/local/lib/python2.7/dist-packages/pymongo/collection.py", line 713, in find_one for result in cursor.limit(-1): File "/usr/local/lib/python2.7/dist-packages/pymongo/cursor.py", line 1038, in next if len(self.__data) or self._refresh(): File "/usr/local/lib/python2.7/dist-packages/pymongo/cursor.py", line 982, in _refresh self.__uuid_subtype)) File "/usr/local/lib/python2.7/dist-packages/pymongo/cursor.py", line 906, in __send_message res = client._send_message_with_response(message, **kwargs) File "/usr/local/lib/python2.7/dist-packages/pymongo/mongo_client.py", line 1186, in _send_message_with_response sock_info = self.__socket(member) File "/usr/local/lib/python2.7/dist-packages/pymongo/mongo_client.py", line 913, in __socket "%s %s" % (host_details, str(why))) pymongo.errors.AutoReconnect: could not connect to 10.75.44.10:27017: [Errno 111] Connection refused >>> db.test.find_one() {u'x': 1, u'_id': ObjectId('54b8b1a1c77b3b3b354869a3')} >>> db.connection.host u'mongodb3' >>> db.connection.port 27017 >>>

发现有个pymongo.errors.AutoReconnect的异常,不过马上恢复了,而且此时的操作数据库变成了mongodb3,也就是现在的PRIMARY.

如果需要从Secondary读取数据,可以设置ReadPreference.

>>> from pymongo.read_preferences import ReadPreference >>> db.test.find_one() {u'x': 1, u'_id': ObjectId('54b8b1a1c77b3b3b354869a3')} >>> db.test.find_one(read_preference=ReadPreference.SECONDARY) {u'x': 1, u'_id': ObjectId('54b8b1a1c77b3b3b354869a3')} >>>

假如PRIMARY和其它所有的SECONDARY失去联系了,那么PRIMARY就无法进行读写操作了。

>>> db.test.insert({'x':3}) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/local/lib/python2.7/dist-packages/pymongo/collection.py", line 402, in insert gen(), check_keys, self.uuid_subtype, client) File "/usr/local/lib/python2.7/dist-packages/pymongo/mongo_client.py", line 1125, in _send_message raise AutoReconnect(str(e)) pymongo.errors.AutoReconnect: not master >>> >>> >>> >>> >>> db.test.insert({'x':3}) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/local/lib/python2.7/dist-packages/pymongo/collection.py", line 363, in insert client._ensure_connected(True) File "/usr/local/lib/python2.7/dist-packages/pymongo/mongo_client.py", line 924, in _ensure_connected self.__ensure_member() File "/usr/local/lib/python2.7/dist-packages/pymongo/mongo_client.py", line 797, in __ensure_member member, nodes = self.__find_node() File "/usr/local/lib/python2.7/dist-packages/pymongo/mongo_client.py", line 888, in __find_node raise AutoReconnect(', '.join(errors)) pymongo.errors.AutoReconnect: [Errno 111] Connection refused, [Errno 111] Connection refused, mongodb3:27017 is not primary or master >>>

直到有至少两个Replica Set的host连接,然后选出新的PRIMARY。

2.3 Test case 3: Write Concern

根据Write Concern for Replica Sets的介绍:

pymongo write concern

>>> db Database(MongoClient([u'mongodb2:27017', u'mongodb1:27017', u'mongodb3:27017']), u'demo') >>> db.write_concern {} >>> db.write_concern = {'w':2, 'wtimeout':5000} >>> db.write_concern {'wtimeout': 5000, 'w': 2} >>> db.test.insert({'y':2}) ObjectId('54b8b89dc77b3b3b354869a4')

默认的write concern是空的配置。write concern有四个参数:w,wtimeout,j, fsync

其中比较重要的是wwtimeout

w: (integer or string)If this is a replica set, write operations will block until they have been replicated to the specified number or tagged set of servers. w= always includes the replica set primary (e.g. w=3 means write to the primary and wait until replicated to two secondaries). Setting w=0 disables write acknowledgement and all other write concern options.

wtimeout: (integer) Used in conjunction with w. Specify a value in milliseconds to control how long to wait for write propagation to complete. If replication does not complete in the given timeframe, a timeout exception is raised.

Reference

Three Member Replica Sets from mongodb.org

http://docs.mongodb.org/manual/replication/

How to Setup MongoDB Replication Using Replica Set and Arbiters

pymongo documentation


Originally published at ciscochina.github.io on July 8, 2017.

Discussion