Mongodb快速搭建副本集群#

https://www.mongodb.com/docs/manual/replication/

多看文档。基本概念不再一一叙述。

启动副本集#

三个节点

创建目录。docker-compose起服务

services:

    # mongo_2和mongo_3类似。放在别的目录
    mongo_1:
        hostname: mongo_1
    
        image: mongo:latest
    
        deploy:
            resources:
                limits:
                    memory: 1G
        
        networks:
            - my_app
    
        ports:
            - "27017:27017" # mongo_1
            #- "27018:27017"  mongo_2
            #- "27019:27017"  mongo_3
    
        volumes:
            - "/xxx/xxx/test/mongodb/:/xxx/xxx/test/mongodb/"
            - "/xxx/xxx/test/mongodb/mongodb_1/data:/data/db/"
        
        environment: # 环境变量
            TZ: Asia/Shanghai
            MONGO_INITDB_ROOT_USERNAME: toor
            MONGO_INITDB_ROOT_PASSWORD: 123456

        # 需要生成一个key_file。随便填一些数据。
        # 需要chown 999:999和chmod 600
        # replSet指定要起副本集
        # 副本集的名字my_repl_set
        command: mongod --bind_ip_all --replSet my_repl_set --keyFile /xxx/xxx/test/mongodb/key_file.txt

基本测试#

# 进入容器
sudo docker-compose exec mongo_1 bash

# 用mongosh或者gui工具连上mongodb
# 官方的Compass挺好的

# 常用命令
use admin
db.auth('toor', '123456')
db.hostInfo()

# 集群状态
rs.status()  

# 初始化副本集
rs.initiate( {
   _id : "my_repl_set",
   members: [
      { _id: 0, host: "x.x.x.x:27017" },
      { _id: 1, host: "x.x.x.x:27018" },
      { _id: 2, host: "x.x.x.x:27019" }
   ]
})

# 此时查rs.status()可以看到一堆信息,包含每个节点的状态等。

# 只有主可写。写入后从节点立即可见。  
# 默认可能也只有主能写。有各种设置。  
# 比如db.getMongo().setReadPref('secondaryPreferred')可让直连到从节点的client可读。Compass的连接有选项设定。  

简单大剂量测试#

用pymongo/motor操作数据库。

MONGODB_AUTH = 'mongodb://toor:123456@x.x.x.x:27017,x.x.x.x:27018,x.x.x.x:27019/?replicaSet=my_repl_set'

mongodb_client = motor.motor_asyncio.AsyncIOMotorClient(MONGODB_AUTH, readPreference = 'secondaryPreferred')
# 不设置secondaryPreferred的话默认只能在主上读  

写程序大量并发读/写/插入数据。每秒上千的读写。

然后对三个节点进行各种折腾,看应用端状态。
用Compass看每个节点的实时请求数。

集群默认的Write Concern(w)参数是majority。即写操作会达成大多数一致性。

Read Concern和Write Concern需要特别关注,概念和Mysql Group Replication大同小异。进行一致性的设置。


关掉一个节点。过会再加入。
可看到这个节点追数据非常快。

最后看count。
查数据数量一定用countDocuments。用count可能不准,非常容易坑人,会以为数据不对。
db.getCollection("xxx").countDocuments()


关掉主时发生重新选主。
期间可出现各种相关错误,属于正常情况。

pymongo.errors.WriteConcernError
pymongo.errors.ServerSelectionTimeoutError: No primary available for writes
pymongo.errors.NotPrimaryError: Not primary so we cannot begin or continue a transaction

重新选出主后自动恢复。选主速度较快。


只剩一个节点时。无法写。可读。
加入一个节点后可自动恢复。


3节点运行过程中,可用rs.add(“xxx.xxx.xxx.xxx:port”)新加一个节点。
只能在可写节点操作。
如果新节点之前有数据,会完全删掉,完全同步成群组的数据。
mongodb同步数据非常快,应该是得益于nosql的数据结构。


测试程序读写期间,对数据库进行各种折腾,不再详写。
最后达到几百万条数据,所有节点数据量仍然一致。
期间Compass的实时统计曲线也符合实际操作,很满意。

pymongo相当于自带一个简单中间件,可实现failover自动切换入口。
不用像mysql那样,一般需要另起一个独立的proxysql之类。
非常好,可满足一般需要。

但是读的一致性还有很多相关问题。

https://pymongo.readthedocs.io/en/stable/examples/high_availability.html


https://www.mongodb.com/docs/manual/tutorial/force-member-to-be-primary/
https://www.mongodb.com/docs/manual/tutorial/configure-secondary-only-replica-set-member/

可设置优先级,让某节点优先成为主节点,让某节点禁止成为主节点。
只能在可写节点操作。

cfg = rs.conf()
cfg.members[0].priority = 10
cfg.members[1].priority = 0 # 设置为0禁止成为主节点
rs.reconfig(cfg)

把当前某个从节点提高优先级,稍后可看到它变成了主节点。


Replication Lag and Flow Control

db.adminCommand({ “getDefaultRWConcern”: 1 })

主上的写操作默认需要达成大多数一致。

Troubleshoot Replica Sets#

查看副本集的lag#

rs.printSecondaryReplicationInfo()

Write Concern相关#

https://www.mongodb.com/docs/manual/reference/write-concern/

Write Concern决定了写操作的表现。
mysql group replication也有相似的概念。
在集群环境中很重要。
如果允许在从节点上读,有可能读到老数据,有可能读到之后被rollback的数据。

# 查看默认设置
db.adminCommand({
  "getDefaultRWConcern": 1
})

defaultReadConcern: { level: 'local' },
defaultWriteConcern: { w: 'majority', wtimeout: 0 },
defaultWriteConcernSource: 'implicit',
defaultReadConcernSource: 'implicit',

w参数可以是数字x,规定写操作必须等待在x个节点上生效后才返回。

w可以是默认值”majority”。规定写操作必须等待在多数节点上生效后才返回。
多数节点数到底是多少。见rs.status()返回的writeMajorityCount。默认会根据节点数改变。

j参数。决定如果日志打开,w为”majority”时,是否要等写操作再多数节点上写入log再返回。
rs.conf()writeConcernMajorityJournalDefault默认为true。
默认要等。

一致性问题#

默认的majority是保证写到大多数,因为是大多数,所以是有可能写返回之后在一个少数节点读到老数据的。
那么如果要确保读不到老数据,要把此次操作的w参数设置为总节点数,确保写入所有节点才返回。
但有个问题是此时如果有节点挂了,这个数量就对不上了。可以把重要的读强制放到主上来解决?
或者用下面的linearizable读,本质就是读主节点,一定不会读到老数据。

相比mgr稍有不同。mgr如果设置了after,保证所有节点都commit。无论如何不会读到老数据。

Read Preference#

https://www.mongodb.com/docs/manual/core/read-preference/

副本集中的读操作,如果不是强制读主节点,一定会有几率读到老数据。

Read Concern相关#

https://www.mongodb.com/docs/manual/reference/read-concern/

Read Concern控制一致性和隔离属性,决定读操作的表现。
结合Write Concern,可以在各种场景按需组合。

上面的getDefaultRWConcern命令可以看到defaultReadConcern的level默认值是local。

  • local
    返回的数据不保证会在集群中达成一致(可能rollback)。
    local文档
    观察基本流程,主直接下发写操作,从1收到后立刻能读到这个数据。
    此时还没走完一致性流程,如果此时两个从突然挂了。那么这次写操作就挂了。但从1却读出了挂的数据。

  • available
    如果没有用sharding。和local完全一样。
    暂不考虑sharding。

  • majority
    如果不是multi-document事务,保证读到的数据已经走完一致性流程,不可能rollback。
    majority文档
    从节点要在t5收到主的一致性ack后才能读到新写的值。
    如果是multi-document事务,只保证write concern设置为majority的事务。

  • linearizable
    一定会读到majority写成功的数据。也就是一定会读到新数据。

  • snapshot