mongodb进阶
mongodb集群有三种模式,主从模式,副本集模式、sharding分片集模式。官网不推荐上生产环境上部署主从模式,主要是安全性太低。副本集和sharding分片集模式目前使用的最广的方案,通常这2种方案的选择通过数据量和并发数来权衡。在GB级别的基本上副本集方案可满足,TB级别或以上采用sharding分片集模式,分片集模式可以解决单机容量和单机并发能力。这两种既有自己的优势也有自己的缺点,比如sharding分片集模式分片越多,性能自然下降越多。
副本集
也叫复制集,本质上来说就是一种具有监控能力的主从模式,类似redis中的哨兵主从模式。
一组Mongodb复制集,就是一组mongod进程,这些进程维护同一个数据集合。复制集提供了数据冗余和高等级的可靠性,这是生产部署的基础。说白了,就是高级主从,它可以保证数据在生产部署时的冗余和可靠性,通过在不同的机器上保存副本来保证数据的不会因为单点损坏而丢失。能够随时应对数据丢失、机器损坏带来的风险。
文档:https://www.mongodb.com/docs/manual/replication/
复制集的基本架构
-
副本集:一个副本集就是一组MongoDB实例组成的集群,由一个主服务器(Primary,也叫主节点)和多个备份服务器(Secondary,也叫副节点)构成
-
主节点(primary):主节点接收所有写入操作。主节点将对其数据集所做的所有更改记录到其 oplog日志。
-
副节点(secondary):复制主节点的 oplog日志 并将操作应用到其数据集,如果主节点不可用,一个合格的副节点将被选举为新的主节点。
-
仲裁节点(arbiter):负责选举,当主节点不可用,它将从副节点中选一个作为主节点。(不存数据)
复制集的基本架构最少由3台服务器组成,有2种组成模式:
-
三成员的复制集,有3个主从节点(1主2从)
-
两成员的复制集,有2个主从节点,1个仲裁节点(arbiter)。(1主1从1仲裁)
不使用仲裁节点的架构
架构组成:
一个主节点;
两个从节点组成,主节点宕机时,这两个从节点都可以经过选举primary以后被选为主节点。
replication:复制
heartbeat:心跳检测(互相请求检测是否还活着宕机)
当主库宕机后,两个从库都会进行竞选,其中一个变为主库,当原主库恢复后,作为从库加入当前的复制集群即可。
使用仲裁节点的架构
架构组成:
一个主节点
一个从节点,可以在主节点宕机以后,通过选举成为主节点
一个仲裁节点,在选举中,只进行投票,不能成为主库,不会成为从库
说明:由于arbiter节点没有复制数据,因此这种架构中仅提供一个完整的数据副本。arbiter节点只需要更少的资源,代价是更有限的冗余和容错。
当主库宕机时,将会选择从库成为主,主库修复后,将其加入到现有的复制集群中即可。
Primary选举
复制集通过replSetInitiate命令(或mongo shell的rs.initiate())进行初始化,初始化后各个成员间开始发送心跳消息,并发起Priamry选举操作,获得『大多数 (总服务器-仲裁节点/ 2 > 50%)』成员投票支持的节点,会成为Primary主节点,其余节点成为Secondary。
『大多数』的定义
假设复制集内投票成员数量为N,则大多数为 N/2 + 1,当复制集内存活成员数量不足大多数时,整个复制集将无法选举出Primary,复制集将无法提供写服务,处于只读状态。
投票成员数 | 大多数 | 容忍失效数 |
---|---|---|
1 | 1 | 0 |
2 | 2 | 0 |
3 | 2 | 1 |
4 | 3 | 1 |
5 | 3 | 2 |
6 | 4 | 2 |
7 | 4 | 3 |
通常建议将复制集成员数量设置为奇数,从上表可以看出3个节点和4个节点的复制集都只能容忍1个节点失效,从『服务可用性』的角度看,其效果是一样的。(但无疑4个节点能提供更可靠的数据存储)
docker-compose搭建MongoDB复制集
keyfile是mongodb副本集的实例之间的权限认证,复制集要求每一个服务器下的keyfile内容必须相同。内容不同,那么该实例添加到副本集的时候,会出现不可达的状态。同时对于keyfile文件还要求如下:
-
keyfile文件的权限必须是600/400;
-
keyfile文件的内容必须在各个服务器下完全相同。
生成keyfile秘钥文件命令如下,但是因为在windows下容易出现权限问题,所以我们可以基于系统镜像创建Dockerfile构建自定义镜像,并在Dockerfile中编写生成keyfile文件的命令和设置权限的命令。
首先安装docker
再安装docker-compose
756就是秘钥长度,最好3的倍数。
cd ~/Desktop/ mkdir -p repl/data/conf cd repl openssl rand -base64 756 > data/conf/mongodb.key chmod 600 data/conf/mongodb.key
上面是手动生成的,我们也可以基于Dockerfile来生成,Dockerfile,代码:
FROM mongo:6.0.2 RUN mkdir -p /data/conf \ && openssl rand -base64 756 > /data/conf/mongodb.key \ && chown mongodb:mongodb /data/conf/mongodb.key \ && chmod 400 /data/conf/mongodb.key
编译镜像,如下;
docker build -t mongo-replica-set . docker images vim docker-compose.yml # 创建文件
docker-compose.yml,代码:
version: '3.7' services: # 主节点 master: image: mongo-replica-set:6.0.2 restart: always privileged: true container_name: master ports: - 27017:27017 environment: MONGO_INITDB_ROOT_USERNAME: root MONGO_INITDB_ROOT_PASSWORD: 123 command: --replSet replica-set --keyFile /data/conf/mongodb.key volumes: - ./data/mongo/master:/data/db # 副节点 secondary: image: mongo-replica-set:6.0.2 restart: always privileged: true container_name: secondary ports: - 27018:27017 environment: MONGO_INITDB_ROOT_USERNAME: root MONGO_INITDB_ROOT_PASSWORD: 123 command: --replSet replica-set --keyFile /data/conf/mongodb.key volumes: - ./data/mongo/secondary:/data/db # 仲裁节点 arbiter: image: mongo-replica-set:6.0.2 restart: always privileged: true container_name: arbiter ports: - 27019:27017 environment: MONGO_INITDB_ROOT_USERNAME: root MONGO_INITDB_ROOT_PASSWORD: 123 command: --replSet replica-set --keyFile /data/conf/mongodb.key volumes: - ./data/mongo/arbiter:/data/db
复制集初始化配置
文档:https://www.mongodb.com/docs/v6.0/reference/method/rs.initiate/
docker-compose -f docker-compose.yml up -d # 选择一个容器进行配置,这里我们进入master节点 docker exec -it master mongosh # 配置时需要在admin数据库中 use admin # 验证权限 db.auth('root','123') # 验证通过后,则可以执行副本集的初始化操作initiate(),设置当前mongodb为primary节点 rs.initiate({ "_id" : "replica-set", "members" : [ { "_id" : 0, "host" : "master:27017" }, { "_id" : 1, "host" : "secondary:27017" }, { "_id" : 2, "host" : "arbiter:27017", "arbiterOnly": true } ] }) # 最后,SECONDARY节点默认是不可读写的,所以要进入从节点,执行如下操作: docker exec -it secondary mongosh use admin db.auth("root", "123") # 设置告诉复制集中的主节点master,当前从节点secondary已经准备就绪 db.getMongo().setReadPref("secondary") # 接下来,我们到arbiter仲裁节点中查看当前是否已经成为了仲裁节点 docker exec -it arbiter mongosh # 上面已经确认配置完成以后,我们就可以再次进入主节点,操作数据,看从节点是否会复制数据到自己仓库中 docker exec -it master mongosh # 使用root账号登陆,验证权限 use admin db.auth('root','123') # 切换到test库 use test db.orders.insertOne({"title": "xxxx", "price": 100}) # 查看副本集的配置 use admin rs.config() # 查看副本集的节点状态 rs.status() # 到从节点中,查看同步过来的数据 docker exec -it secondary mongosh use admin db.auth("root", "123") use test db.getMongo().setReadPref("secondary") # 同步数据 db.orders.find()
分片集
当数据量比较大的时候(TB级别以上),我们需要把数据分片保存并运行在不同的服务器中,以降低CPU、内存和IO的压力,分片集(Sharding Set)就是mongoDB用于横向扩容的技术。
分片是数据跨多台机器存储,MongoDB使用分片来支持具有非常大的数据集和高吞吐量操作的部署。MongoDB分片技术类似MySQL的水平切分和垂直切分,mongo数据库实现Shardin分片集主要有两种方式:垂直扩展和横向切分(也叫水平扩展)。
-
垂直扩展的方式就是进行计算机资源扩容,添加更多的CPU,内存,磁盘空间等。
-
水平扩展则是通过数据分片的方式将数据集分布在多个服务器上,通过集群统一提供服务。
分片设计思想
分片为应对高吞吐量与大数据量提供了方法。使用分片减少了每个分片需要处理的请求数,因此通过水平扩展,集群可以提高自己的存储容量和吞吐量。举例来说,当插入一条数据时,项目应用只需要访问存储这条数据的分片.
使用分片减少了每个分片存储的数据。例如如果数据库1tb的数据集,并有4个分片,然后每个分片可能仅持有256 GB的数据。如果有40个分片,那么每个切分可能只有25GB的数据。
分片机制的优势
1.对集群进行抽象,让集群“不可见”
MongoDB自带了一个叫做mongos的专有路由进程。mongos就是掌握统一路口的路由器,其会将客户端发来的请求准确无误的路由到集群中的一个或者一组服务器上,同时会把接收到的响应拼装起来发回到客户端。
2.保证集群总是可读写
MongoDB通过多种途径来确保集群的可用性和可靠性。将MongoDB的分片和复制功能结合使用,在确保数据分片到多台服务器的同时,也确保了每分数据都有相应的备份,这样就可以确保有服务器换掉时,其他的从库可以立即接替坏掉的部分继续工作。
3.*使集群易于扩展
当系统需要更多的空间和资源的时候,MongoDB使我们可以按需方便的扩充系统容量。
分片集群架构
组件 | 说明 |
---|---|
Config Server | 配置服务器,是mongod实例,可以理解为真实数据的元数据,存储了集群的所有节点、分片数据路由等信息。默认需要配置3个Config Server节点。Config Server中存储的信息:所有存取数据的方式,所有shard节点的信息,分片功能的一些配置信息。 |
Mongos | 数据路由,作为与客户端打交道的模块,提供对外应用访问,所有操作均通过mongos执行。一般有多个mongos节点。数据迁移和数据自动平衡。Mongos本身并不持久化数据,Sharded cluster所有的元数据都会存储到Config Server,而用户的数据会分散存储到各个分片shard。Mongos启动后,会从配置服务器加载元数据,开始提供访问服务,将用户的请求正确指向到对应的分片节点上。 |
Mongod | 真正的数据存储位置,存储应用数据记录。一般有多个Mongod节点,以chunk(数据块)为单位存数据,达到数据分片目的。 |
Mongos的路由功能
当客户端连接mongoDB写入数据时,MongoDB Cluster根据分片键设计写入数据到对应的分片节点下的数据块(chunk)里面。
当客户端连接mongoDB查询数据时,MongoS根据数据分布自动路由至指定分片节点返回数据。
分片集群中的数据分布
(1)使用chunk(数据块)来把数据存储不同的分片(shard)
(2)集群搭建完成之后,默认开启一个chunk(数据块),大小是64M,chunk(数据块)的大小容量可以通过配置chunksize修改。
(3)存储数据的容量只要超过64M,chunk(数据块)会进行平均分裂,如果单位时间存储需求很大,设置更大的chunk(数据块)
(4)chunk(数据块)会被自动均衡迁移到各个Shard分片节点下面。
Chunk(数据块)是什么
就是一个存储数据的单位,是为了方便分割(分裂)数据和迁移数据的。在一个分片节点内部,MongoDB会把数据分为多个chunk,每个chunk代表这个分片节点内部存储的一部分数据,单个 chunk数据块内文档数不能超过250000。
chunk的产生,会有以下两个用途:
Splitting(切割,分裂):当一个chunk的大小超过配置项chunksize指定大小时,MongoDB的后台进程会把这个chunk(数据块)切分成更小的chunk,从而避免单个chunk(数据块)过大的情况。
Balancing(迁移):在MongoDB中,后台进程balancer负责chunk(数据块)的迁移,从而均衡各个分片节点的负载,系统初始化时默认每一个分片服务器只有1个chunk数据块,配置项chunksize用于限制每个数据块的容量,默认为64M。我们在生产部署中可以根据业务设置合适的chunksize容器即可。mongoDB会自动拆分和迁移chunk数据块。
chunkSize对分裂及迁移的影响
MongoDB 默认的 chunkSize 为64MB,如无特殊需求,建议保持默认值或者100~200M左右;chunkSize 会直接影响到 chunk(数据块) 分裂、迁移的行为,所以设置适合项目业务的chunksize数值是最好的。chunksize的值通过如果没有特殊的要求,可以设置为通常100 - 300M。
chunk的分裂和迁移非常消耗IO资源。
chunk分裂的时机:在插入和更新时分裂。注意:读数据不会分裂。
chunk 只会分裂,不会合并。
-
小的chunksize:
数据分类多,迁移速度快,数据分布更均匀。数据分裂频繁,路由节点消耗更多资源。
chunkSize 太小,容易出现 jumbo chunk(即分片键shardKey的某个取值出现频率很高,这些文档只能放到一个 chunk 里,无法再分裂)导致无法迁移的情况出现。
-
大的chunksize:
数据分裂少,迁移速度慢,数据块迁移的时候就会比较集中消耗IO资源。
chunkSize 越大,则可能出现 chunk 内文档数太多()而无法迁移
Chunk分裂及迁移
随着数据的增长,其中的数据大小超过了配置的chunksize,默认是64M,则这个chunk就会分裂成两个。数据的增长会让chunk分裂得越来越多。
这时候,各个shard 上的chunk数量就会不平衡。这时候,mongos中的一个组件balancer进程就会执行自动平衡。把chunk从chunk数量最多的shard节点挪动到数量最少的节点。
数据分片
分片键
MongoDB中数据的分片是以集合为基本单位的,集合中的数据通过分片键(Shard key)被分成多部分。其实分片键就是在集合中选一个键(字段),用该键的值作为数据拆分的依据。
所以一个好的分片键对分片至关重要。分片键必须是一个索引,通过sh.shardCollection自动创建索引(前提是此集合不存在相同索引的情况下)。一个自增的分片键对写入和数据均匀分布就不是很好,因为自增的分片键总会在一个分片上写入,后续达到某个阀值可能会写到别的分片。但是按照分片键查询会非常高效。
随机分片键对数据的均匀分布效果很好。注意:尽量避免在多个分片上进行查询。在所有分片上查询,mongos会对结果进行归并排序。
对集合进行分片时,你需要选择一个分片键,分片键是每个文档都必须包含且建立了索引的单个字段或复合字段,MongoDB按照分片键将数据划分到不同的数据块(chunk)中,并将数据块(chunk)均衡地分布到所有分片节点中。
为了按照分片键划分数据块,MongoDB使用基于范围或者 基于哈希的分片方式。
注意:
-
分片键是不可变。
-
分片键必须有索引。
-
分片键大小限制512bytes。
-
分片键用于分片集中的路由查询(mongos)。
-
MongoDB不接受已进行collection级分片的collection上插入无分片
-
不支持没有分片键的文档(也不支持空值插入)
基于范围的分片(range sharding)
分片集群(sharding Cluster)支持将单个集合的数据分散存储在多分片节点上,用户可以指定根据集合内文档的某个字段即分批案件(shard key)来进行按范围分片(range sharding)。
基于哈希的分片(hash sharding)
对于基于哈希的分片,MongoDB计算一个字段的哈希值,并用这个哈希值来创建数据块。在使用基于哈希分片的系统中,拥有”相近”分片键的文档很可能不会存储在同一个数据块中,因此数据的分离性更好一些。
Hash分片与范围分片互补,能将文档随机的分散到各个数据块(chunk)中,充分的扩展写能力,弥补了范围分片的不足,但不能高效的服务范围查询,所有的范围查询要分发到后端所有的分片才能找出满足条件的文档。
docker-compose搭建分片集集群
创建网卡,用于提供给docker同一个服务之间的容器之间通信。
docker network create mongo
mongo-compose.yaml,代码:
version: '3.7' services: shard1: image: mongo:6.0.2 container_name: mongo_shard1 # --shardsvr: 这个参数仅仅只是将默认的27017端口改为27018,如果指定--port参数,可用不需要这个参数 # --directoryperdb:每个数据库使用单独的文件夹 command: mongod --shardsvr --directoryperdb --replSet shard1 volumes: - ./data/mongo/localtime:/etc/localtime - ./data/mongo/shard1:/data/db restart: always privileged: true mem_limit: 16000000000 networks: - mongo shard2: image: mongo:6.0.2 container_name: mongo_shard2 command: mongod --shardsvr --directoryperdb --replSet shard2 volumes: - ./data/mongo/localtime:/etc/localtime - ./data/mongo/shard2:/data/db restart: always privileged: true mem_limit: 16000000000 networks: - mongo shard3: image: mongo:6.0.2 container_name: mongo_shard3 command: mongod --shardsvr --directoryperdb --replSet shard3 volumes: - ./data/mongo/localtime:/etc/localtime - ./data/mongo/shard3:/data/db restart: always privileged: true mem_limit: 16000000000 networks: - mongo config1: image: mongo:6.0.2 container_name: mongo_config1 # --configsvr: 这个参数仅仅是将默认端口由27017改为27019, 如果指定--port可不添加该参数 command: mongod --configsvr --replSet mongo-config restart: always privileged: true volumes: - ./data/mongo/localtime:/etc/localtime - ./data/mongo/config1:/data/configdb networks: - mongo config2: image: mongo:6.0.2 container_name: mongo_config2 command: mongod --configsvr --replSet mongo-config restart: always privileged: true volumes: - ./data/mongo/localtime:/etc/localtime - ./data/mongo/config2:/data/configdb networks: - mongo config3: image: mongo:6.0.2 container_name: mongo_config3 command: mongod --configsvr --replSet mongo-config restart: always privileged: true volumes: - ./data/mongo/localtime:/etc/localtime - ./data/mongo/config3:/data/configdb networks: - mongo mongos: image: mongo:6.0.2 container_name: mongo_mongos # mongo3.6版默认绑定IP为127.0.0.1,此处绑定0.0.0.0是允许其他容器或主机可以访问 command: mongos --configdb mongo-config/config1:27019,config2:27019,config3:27019 --bind_ip 0.0.0.0 --port 27017 ports: - 27017:27017 volumes: - ./data/mongo/localtime:/etc/localtime depends_on: - config1 - config2 - config3 networks: - mongo networks: mongo: external: trueView Code
run.sh,代码:
#!/bin/sh # docker-compose启动mongodb分片容器组服务 docker-compose -f mongo-compose.yaml up -d # 睡眠半分钟,等待mongodb所有容器起来之后将它们配置加入分片 sleep 30s # 操作config1,配置config副本集,将config容器组作为config角色,此时config1作为config副本集里的主节点 docker-compose -f mongo-compose.yaml exec config1 bash -c "echo 'rs.initiate({_id: \"mongo-config\",configsvr: true, members: [{ _id : 0, host : \"config1:27019\" },{ _id : 1, host : \"config2:27019\" }, { _id : 2, host : \"config3:27019\" }]})' | mongosh --port 27019" # 操作shard1、shard2、shard3,将shard容器组作为shard角色。 docker-compose -f mongo-compose.yaml exec shard1 bash -c "echo 'rs.initiate({_id: \"shard1\",members: [{ _id : 0, host : \"shard1:27018\" }]})' | mongosh --port 27018" docker-compose -f mongo-compose.yaml exec shard2 bash -c "echo 'rs.initiate({_id: \"shard2\",members: [{ _id : 0, host : \"shard2:27018\" }]})' | mongosh --port 27018" docker-compose -f mongo-compose.yaml exec shard3 bash -c "echo 'rs.initiate({_id: \"shard3\",members: [{ _id : 0, host : \"shard3:27018\" }]})' | mongosh --port 27018" # 将shard1、shard2、shard3加入分片集群组。 docker-compose -f mongo-compose.yaml exec mongos bash -c "echo 'sh.addShard(\"shard1/shard1:27018\")' | mongosh" docker-compose -f mongo-compose.yaml exec mongos bash -c "echo 'sh.addShard(\"shard2/shard2:27018\")' | mongosh" docker-compose -f mongo-compose.yaml exec mongos bash -c "echo 'sh.addShard(\"shard3/shard3:27018\")' | mongosh"
运行
sudo rm -rf data/mongo mkdir -p data/mongo sudo cp /etc/localtime ./data/mongo/localtime sh run.sh
上面仅仅借助docker-compose实现分片集的部署机制。真正的运维环境中,Mongodb集群应该分布在不同的机器,同时,Mongodb库默认是不会将你的集合进行分片的,是需要手动配置的,如果不配置,数据都会全部存放在主节点,不会均匀分配到各个分片。
举例:
// 注意:分片的片键必须是创建了索引的单个字段或创建了联合索引的字段 docker-compose -f mongo-compose.yaml exec mongos mongosh // 先让当前库支持分片,school是举例数据库,可以填写实际要分片的库名。 sh.enableSharding("school") // 把当前集合加入分片,递增片键(timestamp已经作为索引了),student表示要分片的集合,可以填写实际要分片的集合名称。 sh.shardCollection('school.student',{timestamp:1}) // 刷新路由 db.adminCommand("flushRouterConfig") // 让当前分片支持均衡调度, 当某个shard中的chunk达到一定的数量,则balancer会自动的将部分chunk块迁移到其它shard中,保证所有的shard中拥有的 chunk 块数量基本一致。 sh.enableBalancing("school.student") // 开启均衡调度 sh.startBalancer() // 查看详细分片信息 sh.status({"verbose":1}) // 查看该集合的分片数据信息 use school db.student.getShardDistribution()
标签:mongo,数据,MongoDB,chunk,分片,data,节点 From: https://www.cnblogs.com/erhuoyuan/p/17416957.html