docker 部署mongoDB集群与读写分离

一.生成key文件

需要注意集群中所有机器都需要用同一个文件,否则会出现验证失败的情况

# 生成key
openssl rand -base64 756 > /data/volume/mongodb/configdb/mongo.key
# 设置访问权限
chmod 400 /data/volume/mongodb/configdb/mongo.key

 二.启动MongoDB的docker 容器

docker run -di --name=mongo -p 27017:27017 -v /data/volume/mongodb/configdb/:/data/configdb/ -v /data/volume/mongodb/db/:/data/db/ mongo mongod --replSet mongos --keyFile /data/configdb/mongo.key
# -itd:其中,i是交互式操作,t是一个终端,d指的是在后台运行。
# --name mongo-test:容器名称
# -p 27017:27017 :映射容器服务的 27017 端口到宿主机的 27017 端口。外部可以直接通过 宿主机 ip:27017 访问到 mongo 的服务。
# --auth:需要密码才能访问容器服务。
# -v /data/volume/mongodb/configdb/:/data/configdb/ :映射配置文件路径
# -v /data/volume/mongodb/db/:/data/db/ :映射数据文件路径
# mongod 设置mongod配置
# --replSet mongos 设置集群名称 mongos
# --keyFile /data/configdb/mongo.key 指定秘钥文件路径
# --config /data/configdb/mongo.conf 指定配置文件路径
# --dbpath:指定存放 MongoDB 数据的目录,启动服务时必须指定。
# --logpath:默认日志是打印在命令行中的,使用该选项指定日志输出的文件。如果对此目录有写权限且文件不存在,则会自动创建该文件。如果日志文件已经存在,默认会覆盖掉该文件,并删除所有旧的日志。如果希望保留旧的日志,除了使用 --logpath 之外,还应该使用 --logappend 选项。

三.登录到mongo

docker exec -it mongo mongosh admin
docker exec -it mongo mongo admin #5.0以下版本使用 

登录成功 显示 连接地址、版本信息等

四.初始化集群

注意_id要与“--replSet mongos”一致

rs.initiate({ 
    _id:"mongos",
    members:[  
        {_id:0,host:"192.168.230.128:27017"}, 
        {_id:1,host:"192.168.230.129:27017"}, 
        {_id:2,host:'192.168.230.130:27017',arbiterOnly:true}
    ]
})
# arbiterOnly:true 设为仲裁节点
修改使用:rs.reconfig(...)

五.创建root账号

Mongodb默认是不需要用户密码就可以连接的,如果使用命令报错"not authorized on admin to execute command ",则表示当前登陆用户不具备相应权限; 

解决办法:通过创建一个用户,赋予用户授权管理集群权限(clusterAdmin)即可(在createUser之前先use admin切换一下)

添加用户权限成功之后,使用root用户登陆,再次使用命令即可成功,仅需要在primary上创建即可,读写分离完成后会同步到secondary节点

db.createUser(
{ 
    user:'root',
    pwd:'123456',
    roles:[{ role:'userAdminAnyDatabase', db: 'admin'}, 'readWriteAnyDatabase','clusterAdmin']
});

# user:'root' :设置用户名为root
# pwd:'123456':设置密码为123456
# role:'userAdminAnyDatabase':只在admin数据库中可用,赋予用户所有数据库的userAdmin权限
# db: 'admin':可操作的数据库
# 'readWriteAnyDatabase':赋予用户对其他所有库读写权限
  • 数据库用户角色:read、readWrite;
  • 数据库管理角色:dbAdmin、dbOwner、userAdmin;
  • 集群管理角色:clusterAdmin、clusterManager、4. clusterMonitor、hostManage;
  • 备份恢复角色:backup、restore;
  • 所有数据库角色:readAnyDatabase、readWriteAnyDatabase、userAdminAnyDatabase、dbAdminAnyDatabas
  • 超级用户角色:root
  • 内部角色:__system

若修改指定用户权限使用:

use admin # 切换到管理库
db.grantRolesToUser("root", ["clusterAdmin"]) # 为用户“root”授权 集群管理

六.登录root账号

 

七.查看集群配置

rs.conf()

 八.查看集群状态

rs.status();  

如下:

mongos [direct: primary] admin> rs.status();  
{
  set: 'mongos',
  date: ISODate("2023-03-25T09:15:26.242Z"),
  myState: 1,
  term: Long("1"),
  syncSourceHost: '',
  syncSourceId: -1,
  heartbeatIntervalMillis: Long("2000"),
  majorityVoteCount: 2,
  writeMajorityCount: 1,
  votingMembersCount: 2,
  writableVotingMembersCount: 1,
  optimes: {
    lastCommittedOpTime: { ts: Timestamp({ t: 1679735718, i: 1 }), t: Long("1") },
    lastCommittedWallTime: ISODate("2023-03-25T09:15:18.494Z"),
    readConcernMajorityOpTime: { ts: Timestamp({ t: 1679735718, i: 1 }), t: Long("1") },
    appliedOpTime: { ts: Timestamp({ t: 1679735718, i: 1 }), t: Long("1") },
    durableOpTime: { ts: Timestamp({ t: 1679735718, i: 1 }), t: Long("1") },
    lastAppliedWallTime: ISODate("2023-03-25T09:15:18.494Z"),
    lastDurableWallTime: ISODate("2023-03-25T09:15:18.494Z")
  },
  lastStableRecoveryTimestamp: Timestamp({ t: 1679735678, i: 1 }),
  electionCandidateMetrics: {
    lastElectionReason: 'electionTimeout',
    lastElectionDate: ISODate("2023-03-25T08:17:58.236Z"),
    electionTerm: Long("1"),
    lastCommittedOpTimeAtElection: { ts: Timestamp({ t: 1679732267, i: 1 }), t: Long("-1") },
    lastSeenOpTimeAtElection: { ts: Timestamp({ t: 1679732267, i: 1 }), t: Long("-1") },
    numVotesNeeded: 2,
    priorityAtElection: 1,
    electionTimeoutMillis: Long("10000"),
    numCatchUpOps: Long("0"),
    newTermStartDate: ISODate("2023-03-25T08:17:58.307Z"),
    wMajorityWriteAvailabilityDate: ISODate("2023-03-25T08:17:58.317Z")
  },
  members: [
    {
      _id: 0,
      name: '192.168.230.128:27017',
      health: 1,
      state: 1,
      stateStr: 'PRIMARY',
      uptime: 4073,
      optime: { ts: Timestamp({ t: 1679735718, i: 1 }), t: Long("1") },
      optimeDate: ISODate("2023-03-25T09:15:18.000Z"),
      lastAppliedWallTime: ISODate("2023-03-25T09:15:18.494Z"),
      lastDurableWallTime: ISODate("2023-03-25T09:15:18.494Z"),
      syncSourceHost: '',
      syncSourceId: -1,
      infoMessage: '',
      electionTime: Timestamp({ t: 1679732278, i: 1 }),
      electionDate: ISODate("2023-03-25T08:17:58.000Z"),
      configVersion: 1,
      configTerm: 1,
      self: true,
      lastHeartbeatMessage: ''
    },
    {
      _id: 1,
      name: '192.168.230.129:27017',
      health: 1,
      state: 7,
      stateStr: 'ARBITER',
      uptime: 3458,
      lastHeartbeat: ISODate("2023-03-25T09:15:25.565Z"),
      lastHeartbeatRecv: ISODate("2023-03-25T09:15:25.566Z"),
      pingMs: Long("0"),
      lastHeartbeatMessage: '',
      syncSourceHost: '',
      syncSourceId: -1,
      infoMessage: '',
      configVersion: 1,
      configTerm: 1
    }
  ],
  ok: 1,
  '$clusterTime': {
    clusterTime: Timestamp({ t: 1679735718, i: 1 }),
    signature: {
      hash: Binary(Buffer.from("a880e39a0ace5a06ee9474d8c77a4b862d9cd46f", "hex"), 0),
      keyId: Long("7214395200045580294")
    }
  },
  operationTime: Timestamp({ t: 1679735718, i: 1 })
}

节点类型
  MongoDB的节点类型有主节点(primary或称为Master),副本节点(Slave或者称为Secondary),arbiter仲裁节点,Secondary-Only节点,Hidden节点,Delayed节点和Non-Voting节点。

  • primary:主节点,提供增删查改服务
  • secondary:备节点,只提供读(需要执行 “db.getMongo().setReadPref('secondary')”)
  • arbiter:仲裁节点不存储数据,只是用于投票。所以仲裁节点对于服务器负载很低。节点一旦以仲裁者的身份加入集群,他就只能是仲裁者,无法将仲裁者配置为非仲裁者。
  • Secondary-Only:不能成为primary节点,只能作为secondary副本节点,防止一些性能不高的节点成为主节点。
  • Hidden:这类节点是不能够被客户端制定IP引用,也不能被设置为主节点,但是可以投票,一般用于备份数据。
  • Delayed:可以指定一个时间延迟从primary节点同步数据。主要用于备份数据,如果实时同步,误删除数据马上同步到从节点。所以延迟复制主要用于避免用户错误。
  • Non-Voting:没有选举权的secondary节点,纯粹的备份数据节点。

副本集成员状态
  副本集成员状态指的是rs.status()的stateStr字段。

  • STARTUP:刚加入到复制集中,配置还未加载
  • STARTUP2:配置已加载完,初始化状态
  • RECOVERING:正在恢复,不适用读
  • ARBITER: 仲裁者
  • DOWN:节点不可到达
  • UNKNOWN:未获取其他节点状态而不知是什么状态,一般发生在只有两个成员的架构
  • REMOVED:移除复制集
  • ROLLBACK:数据回滚,在回滚结束时,转移到RECOVERING或SECONDARY状态
  • FATAL:出错。查看日志grep “replSet FATAL”找出错原因,重新做同步
  • PRIMARY:主节点
  • SECONDARY:备份节点。

九.修改集群配置

可以通过re.config(...)重新初始化配置修改集群

rs.reconfig({ _id: "mongos", members: [ { _id: 1, host: "192.168.230.131:27017" }, { _id: 2, host: '192.168.230.130:27017', arbiterOnly: true }] }, {force : true})

在primary主机上执行,如果都已经不存在primary主机的话,或以上不能执行,则任意进入该副本集的任意一台主机,使用下面方法进行操作:

use admin;
db.auth('root','123456');
rs.config(); #观察members中哪一部分需要更改,会影响下面members[n]中n的值
cfg = rs.config();
cfg.members[0].host = "192.168.1.3:2777";
rs.reconfig(cfg,{"force":true}); #如果不是primary主机,则会发生replSetReconfig should only be run on PRIMARY, but my state is REMOVED; use the "force" argument to override这个错误,那么则运行rs.reconfig(rsc,{"force":true});
rs.config(); #观察members中地址是否已经变更
exit

十.添加/移除一个节点

添加/移除节点需要先设置 setDefaultRWConcern 否者会提示

Reconfig attempted to install a config that would change the implicit default write concern. Use the setDefaultRWConcern command to set a cluster-wide write concern and try the reconfig again.

# 设置写关注WriteConcern
db.adminCommand({
    "setDefaultRWConcern" : 2,
    "defaultWriteConcern" : {
      "w" : majority
    }
  })

写关注WriteConcern
指定了写入操作的确认级别,单机、副本集、分片集群都可以指定写关注级别。
可通过setDefaultRWConcern设置全局默认写关注级别(从mongoDB4.4开始)。
写关注需使用事务级的,不能在事务内为某个写操作单独指定写关注级别,会导致报错。
参数包括:
{ w: <value>, j: <boolean>, wtimeout: <number> }
w:数字—写入请求确认写入操作已传播到指定数量的节点(mongod实例)。
"majority"—请求请求确认写入操作已传播到计算出的多数节点。
其他自定义写关注名称—settings.getLastErrorModes中可自定义写关注条件。
j:指定写入请求是否确认已写入磁盘预写日志。true时会确保写入操作已经写入w指定数量节点的预写日志中。
wtimeout:指定写入操作超时时间(毫秒)。
需注意:到达wtimeout后无论写入最终能否成功都会返回错误,且已成功的写入修改不会回退;如果不指定wtimeout,且写关注级别无法满足,写入操作会一直阻塞。
## 具体使用方式参考:https://blog.csdn.net/Synup/article/details/123668468
https://www.mongodb.com/docs/manual/reference/command/setDefaultRWConcern/#mongodb-dbcommand-dbcmd.setDefaultRWConcern

添加节点:

rs.add('192.168.230.130:27017')

 

登录被添加的节点,查看命令提示符状态变化

添加仲裁节点

rs.addArb("192.168.230.130:27017")

移除节点

rs.remove('192.168.230.130:27017')

十一.设置为读写分离

Read Preference 描述了mongodb 如何将请求路由到副本集的节点,默认下,会路由到primary节点,如果primary节点读写压力过大,可以考虑读写分离的方案。
不过需要考虑一种场景,就是primary的写入压力非常的大,所以副本节点复制的写入压力同样很大。
这时副本节点如果读取压力也很大的话,根据MongoDB库级别读写锁的机制,
很可能复制写入程序拿不到写锁,从而导致副本节点与主节点有较大延迟。

如果进行读写分离,首先需要在副本节点声明其为secondary

db.getMongo().setReadPref('secondary')

其中的ReadRreference有几种设置:

  • primary:默认参数,只从主节点上进行读取操作;
  • primaryPreferred:大部分从主节点上读取数据,只有主节点不可用时从secondary节点读取数据。
  • secondary:只从secondary节点上进行读取操作,存在的问题是secondary节点的数据会比primary节点数据“旧”。
  • secondaryPreferred:优先从secondary节点进行读取操作,secondary节点不可用时从主节点读取数据;
  • nearest:不管是主节点、secondary节点,从网络延迟最低的节点上读取数据。

十二.Springboot客户端连接

# MongoDB URI
spring.data.mongodb.uri=mongodb://testUser:pwd@192.168.230.130:27017,192.168.230.131:27017/testDb?replicaSet=mongos&readPreference=secondaryPreferred
# “test”:用户名
# “123456”:密码
# “192.168.230.130:27017,192.168.230.131:27017” :连接地址多个使用逗号隔开。
# “testDb”:数据库
# “replicaSet=mongos”:集群id
# “readPreference=secondaryPreferred” 将请求路由到副本集方式,这里设置为“优先从secondary节点进行读取操作,secondary节点不可用时从主节点读取数据”

这里不能通过, ROOT_USERNAME 来读取数据库(我们的 root 用户可用于创建新的数据库、集合和新用户),否者会报错:

Command failed with error 18 (AuthenticationFailed)

可以创建一个普通读写用户用于客户端连接

# 切换到管理库
use admin
# 添加一个用户,用于客户端连接
db.createUser({user: "testUser", pwd: "pwd", roles : [{role: "readWrite", db: "interviewTest"}]});
# 显示所有用户
db.system.users.find()

十三.验证

1.服务端数据复制验证:

  • 在primary中插入数据  
# 切换到用户库
use admin
# 用户登录
db.auth('root','123456')
# 切换到指定业务库
use testDb 
# 插入一条数据
db.student.insert({"name":"张三",age:21})

  

  • 在secondary中查询数据
# 切换到用户库
use admin
# 用户登录
db.auth('root','123456')
# 切换到指定业务库
use testDb 
# 插入一条数据
db.student.find()

   

在primary插入的数据,secondary中可以查询得到,表示成功!

2.服务端故障测试

  • 分别观察三台服务器,命令提示为:

  192.168.230.130  ->  mongos [direct: secondary] admin>_

  192.168.230.131  ->  mongos [direct: primary] admin>_  

  192.168.230.131  ->  mongos [direct: arbiter] admin>_  

  • 直接停掉192.168.230.131,观察192.168.230.130 是否变成为了 primary

  

3.客户端连接测试  

public static void main(String[] args) {
        //创建一个mongo连接
        MongoDatabaseFactory mongoDatabaseFactory = new SimpleMongoClientDatabaseFactory("mongodb://testUser:pwd@192.168.230.130:27017,192.168.230.131:27017/testDb?replicaSet=mongos&readPreference=secondaryPreferred");
        MongoOperations mongoTemplate = new MongoTemplate(mongoDatabaseFactory);
        //开启1000个线程进行循环读取
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                while (true){
                    System.out.println(Thread.currentThread().getId());
                    List<TestModel> list = mongoTemplate.findAll(TestModel.class, "student");
                    System.out.println(JSON.toJSONString(list));
                }

            }).start();
        }
    }

通过调整连接字符串中的 readPreference 参数,观察服务器压力,判断查询落在哪个副本上

  • primary 节点压力显示

  • secondary节点压力显示

posted @ 2023-04-04 10:29  ejiyuan  阅读(681)  评论(0编辑  收藏  举报