Golang+MongoDB 从基础到放弃 进阶(二)

前言

阅读本文需要查看前文

查询分析 explain

  • mongodb中的分析只需要执行语句的后面添加.explain()
db.meituan.find({node_id:"2"}).explain()

返回的内容(json)并不适于查看,可以借助compass工具,可视化效果更佳
如下图

  • Documents Examined 查询到的文档个数 后面会提示索引使用情况
  • COLLSCAN 表示未命中索引,进行了全表扫描
    在这里插入图片描述

如果命中索引,且查询字段多于索引,则会提示 Query used the following index: 且显示如下:

  • IXSCAN 查询命中了索引
  • FETCH 查询了文档
  • 如果查询字段就是索引字段,则会提示:Query covered by index: 是效率最高的查询
    在这里插入图片描述

副本集 Replica Sets

概念

mongodb中的副本集(Replica Sets)是一组维护相同数据集的mongodb服务。提供如下特征:

  • 保障数据的安全性
  • 数据高可用性 (24*7)
  • 灾难恢复
  • 无需停机维护(如备份,重建索引,压缩)
  • 分布式读取数据

和mysql主从的区别
mongodb中的备份集没有固定的主节点,在主节点挂掉后,会从其他的从节点中再选出一个主节点。

两种类型三种角色

两种类型
1 主节点(Primary) 可读写
2 从节点 (Secondary) 数据的冗余备份 可读可选举

三种角色
1 主要成员(Primary) 也就是主节点
2 副本成员(Replicate)数据备份 可读,是默认的从节点类型
3 仲裁者(Arbiter)不保留任何数据的副本,只具备投票的作用。当然,一个节点可以同时是Replicate和Arbiter

选举

如下情况会进行选举

  • Secondary节点权重比Primary节点高时,发起替换选举;
  • Secondary节点发现集群中没有Primary时,发起选举;
    • 主节点故障
    • 主节点不可达(默认心跳为10s)
    • 人工干预 (rs.stepDown(600))
  • Primary节点不能访问到大部分(Majority)成员时主动降级;

心跳:
MongoDB复制集成员会向自己之外的所有成员发送心跳并处理响应信息,因此每个节点都维护着从该节点POV看到的其他所有节点的状态信息。节点根据自己的集群状态信息判断是否需要更换新的Primary。

选举过程大致如下:
1 发起选举
发起选举的节点需要首先做一些条件判断,比如节点位于备选节点列表中、POV包含复制集Majority等,真实情况的条件判断更加复杂。然后将自己标记为选举过程中,并发起投票请求。
2 投票
投票发起者向集群成员发起Elect请求,成员在收到请求后经过会一系列检查,如果通过检查则为发起者投一票。一轮选举中每个成员最多投一票,在PV0中用30秒“选举锁”避免为其他发起者重复投票,这导致了如果新选举的Primary挂掉,可能30秒内不会有新的Primary选举产生;在PV1中通过为投票引入单调递增的Term解决重复投票的问题。

如果投票发起者获得超过半数的投票,则选举通过成为Primary节点,否则重新发起投票

注意:

  • MongoDB选举需要获得大多数投票才能通过,在一轮选举中两个节点得票相同则重新选举,为避免陷入无限重复选举,MongoDB建议复制集的成员个数为奇数个,当Secondary节点个数为偶数时,可以增加一个Arbiter节点,。
  • PV0版本中,所有成员都可以投否决票,一个否决票会将得票数减少10000,所以一般可以认为只要有成员反对,则该节点不能成为Primary。PV1版本取消了否决票。
  • 选举过程中,复制集没有主节点,所有成员都是只读状态。

分片集 shared

当MongoDB存储海量的数据时,一台机器可能不足以存储数据,也可能不足以提供可接受的读写吞吐量。这时,我们就可以通过在多台机器上分割数据,使得数据库系统能存储和处理更多的数据。
分片集的设计使得mongodb能够非常容易的水平扩展

组成

1 数据分片(Shards)

  • 用来保存数据,保证数据的高可用性和一致性。可以是一个单独的mongod实例,也可以是一个副本集。在生产环境下Shard一般是一个Replica Set,以防止该数据片的单点故障。可以将所有shard的副本集放在一个服务器多个mongodb实例中。
  • sharding中的集合可以是分片也可以不分片,每个db创建时都有一个默认的primary shard(首选分片),未分片的集合会存在其db各自的primary shard中的。例如你创建了一个test.test集合,那么这个集合可能只会在shard1分片集存在。

2 配置服务器(Config servers)

  • 保存集群的元数据(metadata),包含各个Shard的路由规则。3.2版本以后config server可以配置为replica set(CSRS),3.4以后config server必须配置为rs。
  • config server的rs不能有arbiter(3.2.9版本是这样,其他版本未测试),生产上建议config server的rs至少要有3个副本集成员。
    MongoDB是在collection级别实现的水平分片。

3 查询路由(Query Routers)

  • 路由就是mongos的实例,客户端直接连接mongos,由mongos把读写请求路由到指定的Shard上去。
  • 一个Sharding集群,可以有一个mongos,也可以如上图所示为每个App Server配置一个mongos以减轻路由压力。
  • 注意这里的mongos并不要配置为rs,因为只是个路由,并不存储数据,配置多个mongos的意思是配置多个单独的mongos实例。

分片设置

登录mongos服务器:
mongo --port=27019
use admin
sh.addShard("shard1/192.168.20.70:27017,192.168.20.71:27017,192.168.20.72:27017");
sh.addShard("shard2/192.168.20.70:27020,192.168.20.71:27020,192.168.20.72:27020");
sh.addShard("shard3/192.168.20.70:27021,192.168.20.71:27021,192.168.20.72:27021");
然后在mongos上为具体的数据库配置sharding:
sh.enableSharding("test") --允许test数据库进行sharding
sh.shardCollection("test.t",{id:"hashed"}) --对test.t集合以id列为shard key进行hashed sharding
通过db.t.getIndexes()可以看到自动为id列创建了索引。
sh.status()
查看集群状态,分片

片键

  • shard key在sharding搭建完毕后是不能修改的,一个collection上只能有一个shard key。
  • shard key上必须有索引(可以是以shard key开头的联合索引),如果没有mongodb会为shard key创建索引。如果是已经存在的collection那么必须手动为shard key创建索引。
  • 在sharding的collection中只有_id和shard key前缀的索引可以是unique index,其他索引只能是普通索引。如果一个普通key上有unique index那么你不能以其他key为shard key对collection进行sharding。
  • shard key的选择将会影响整个集群的效率,可扩展性和性能。而且也会影响你所能选择的分片策略。

其他详见:https://docs.mongodb.com/v3.2/core/sharding-shard-key/

分片集带来的问题

  • 数据量较少时不建议使用sharding,毕竟读写都要经过一层路由会有性能损耗,直接表现就是ips和qps会降低。
  • sharding集群不支持一些常规的单实例方法,如group(),可以使用mapReduce()或者aggregate()中的group来替代,因此建议从一开始学习就直接使用aggregate(),这种写法较为简单明了,且统一化易于识别。
  • .对于没有用到shard key的查询,路由进行全集群广播(broadcast operation),对每个shard都查一遍进行scatter/gather,此时效率会很低。
  • 生产上使用副本集或sharding时,要考虑到安全认证的问题,集群节点间要指定keyfile启动(指定keyfile后auth默认会开启),如果集群环境下只开启auth认证不配置keyfile,replica节点之间的同步就会失败。

聚合 aggregate

MongoDB 中聚合(aggregate)主要用于处理数据(诸如统计平均值,求和等),并返回计算后的数据结果。

语法

db.COLLECTION_NAME.aggregate(AGGREGATE_OPERATION)

大致有如下操作:

  • $project:修改输入文档的结构。可以用来重命名、增加或删除域,也可以用于创建计算结果以及嵌套文档。
  • m a t c h : 用 于 过 滤 数 据 , 只 输 出 符 合 条 件 的 文 档 。 match:用于过滤数据,只输出符合条件的文档。 matchmatch使用MongoDB的标准查询操作。
  • $limit:用来限制MongoDB聚合管道返回的文档数。
  • $skip:在聚合管道中跳过指定数量的文档,并返回余下的文档。
  • $unwind:将文档中的某一个数组类型字段拆分成多条,每条包含数组中的一个值。
  • $group:将集合中的文档分组,可用于统计结果。
  • $sort:将输入文档排序后输出。
  • $geoNear:输出接近某一地理位置的有序文档。

示例

//按照tid字段进行分组,并统计个数
> db.test.aggregate([{$group:{_id:"$tid",sum:{$sum:1}}}])
{ _id: 6, sum: 1 }
{ _id: 4, sum: 1 }
{ _id: 3, sum: 1 }
{ _id: 5, sum: 2 }

// 查找tid范围1到5,并且只显示时间和id
db.meituan.aggregate([
	{$match:{tid:{$gte:1,$lt:5}}},
	{$project:{create_at:1,tid:1}}
	])

{ _id: '2021080701002',
  create_at: 2021-08-07T03:47:23.433Z,
  tid: 3 }
{ _id: ObjectId("610e0448370fe159b81b7e06"),
  create_at: 2021-08-07T03:55:52.087Z,
  tid: 4 }

事务

MongoDB 单文档原生支持原子性,也具备事务的特性,但是我们说起事务,通常是指在多文档中的实现,因此,MongoDB 在 4.0 版本支持了多文档事务,4.0 对应于复制集的多表、多行,后续又在 4.2 版本支持了分片集的多表、多行事务操作。
代码

注意:事务只能在副本集和mongos上运行

golang代码实现

  • 事务只能运行在副本集或者mongos上
func MongoSession() error {
	coll := mdb.Database("takeout").Collection("meituan")
	err := mdb.UseSession(context.Background(), func(ctx mongo.SessionContext) error {
		if err := ctx.StartTransaction(); err != nil {
			return err
		}
		_, err := coll.InsertOne(ctx, bson.M{
			"tid": "2021080801001",
		})
		if err == nil{
			err = ctx.CommitTransaction(ctx)
		}
		return err
	})
	return err
}

事务options

在事务开始事允许设置四个参数,如下源码

type TransactionOptions struct {
	ReadConcern *readconcern.ReadConcern
	ReadPreference *readpref.ReadPref
	WriteConcern *writeconcern.WriteConcern
	MaxCommitTime *time.Duration
}

1 ReadPreference
在一个事务操作中使用事务级别的 readPreference 来决定读取时从哪个节点读取。可方便的实现读写分离、就近读取策略。
简单说就是数据从哪读

  • primary 只从主节点读取,默认值。
  • primaryPreferred 优先选择主节点,不可用时选择从节点
  • secondary 只在从节点读取
  • secondaryPreferred 优先在从节点读取,从节点不可用时选择主节点。
  • nearest 选择附近节点

2 ReadConcern
readConcern 决定该节点的哪些数据是可读的。主要保证事务中的隔离性,避免脏读。

  • available:读取所有可用的数据。
  • local:仅读取当前分片的数据。
  • majority:读取在大多数节点上提交完成的数据。
  • snapshot:读取最近快照中的数据。

3 WriteConcern
事务使用事务级别的 writeConcern 来提交写操作,决定一个事务的写入成功与否要看 writeConcern 选项设置了几个节点,默认情况下为 1
主要参数如下:

  • w:0 设置为 0 无需关注写入成功与否。
  • w:1 ~ 任意节点数 自定义节点数设置,复制集中不得大于最大节点数。默认情况下为 1,表示写入到 Primary 节点就开始往客户端发送确认写入成功。
  • w:“majority” 大多数节点成功原则,例如一个复制集 3 个节点,2 个节点成功就认为本次写入成功。
  • w:“all” 所以节点都成功,才认为写入成功。
  • j:true 默认情况 j:false,写操作到达内存算作完成。如果设置为 j:true,写操作只有到达 journal 文件才算成功。
  • wtimeout: 写入超时实践

未完待续。。。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值