直播分享| 腾讯云 MongoDB 智能诊断及性能优化实践
本次直播分享主要分为五个部分展开:
第一部分:主要介绍 MongoDB 的核心优势;
第二部分:主要总结云上 MongoDB 用户常见的一些问题;
第三部分:介绍腾讯云 MongoDB 智能索引推荐实现流程及其实现原理;
第四部分:介绍腾讯云 MongoDB 内核 SQL 限流功能及其实现;
第五部分:主要分享腾讯云 DBbrain for MongoDB 的两个典型诊断案例
MongoDB 有哪些核心优势?
MongoDB 是一个基于分布式文件存储的数据库,由 C++ 语言编写。首先,我们来看下它有哪些核心优势,下面列举几个:
分布式
MongoDB 是开源的分布式数据库,可以解决传统数据库存储容量上的瓶颈问题,用户不必再提前考虑分库分表等操作。同时,MongoDB 也是一个天然高可用的数据库,比如在一主两从的工作模式下,当主节点意外宕机,从节点就会接替主节点的工作,整个过程无须依赖任何第三方组件。
lschema-free
MongoDB 的表结构相对自由,添加字段方便快捷,相比于传统数据库在一张大表里添加字段,运维成本被大大降低。
高性能
MongoDB 早期使用 MMAPv1 存储引擎,后来替换为 WiredTiger 存储引擎,它支持行级粒度锁、热点数据缓存等特性,这给 MongoDB 带来了高性能、低延迟、高吞吐等能力。
高压缩比
在默认配置下,MongoDB 使用 snappy 压缩算法,可达到平均2到4倍的文本数据压缩能力,如果使用 zlib 压缩算法则可以提升到3至7倍,但是 zlib 对性能有一定影响,因此线上通常使用默认配置即可。经测试,在默认配置的情况下,同样一份数据写入 MongoDB、MySQL、ES 的真实磁盘消耗比约为1 : 3 : 6。
完善的客户端访问策略
MongoDB 支持五种均衡访问策略:
primary:读主节点,当主节点异常时,可能造成短期内的业务异常。
primaryPreferred:主节点优先,当主节点异常时可以读从节点。
secondary:读从节点,把流量平均衡分配给多个从节点,实现负载均衡。
secondaryPreferred:从节点优先,如果从节点异常,则读取主节点。
nearest:就近访问,在多机房场景下,就近访问可以避免出现跨机房访问的情况。
腾讯云 MongoDB 核心优势
腾讯云 MongoDB 当前已服务于游戏、电商、社交、教育、新闻资讯、金融、物联网、软件服务、汽车出行、音视频等多个行业。
相比用户自建 MongoDB 数据库,腾讯云 MongoDB 在智能运维、可用性、安全性、性能等方面更具优势。同时通过 DBbrain 提供一站式监控的诊断分析,并且能给出相应的优化建议,同时也集成了官方的常用工具,让用户使用更方便。
此外,腾讯云 MongoDB 还在内核里做了一些定制化的开发,比如解决表数量达到百万级别时的性能问题、提供 SQL 限流功能减少流量过大导致的集群不可用问题。安全方面,腾讯云 MongoDB 可以将数据恢复到7天内的任意时间点,并且提供24小时的专业支持服务。除此之外,也天然集成了云上高可用、高性能等通用能力。
云上 MongoDB 集群常见问题
3.1. 分片集群使用问题
云上分片集群经常遇到的问题如上,主要包括大表不启用分片、分片方式不是最优、片建选择不当、高峰期没有设置 balance 窗口集群抖动。
大数据量大流量表不启用分片
有些用户有错误认识,就是从副本集切到多分片集群,认为啥也不做,集群天然性能就是副本集好几倍,存储容量默认是副本集几倍。
分片集群如果不启用分片功能,数据和流量默认都会到主分片,也就是分片集群中的一个分片,因此大数据量、大流量集群切记启用分片功能。
分片方式
通常情况,如果用户主要是点查,例如按照订单id查询,则可以选择 hash 分片方式,这样除了保证读取性能同时,同时确保数据离散写入不同分片,保证了写入性能并避免了数据不均衡引起的大量 moveChunk 操作。
如果用户查询主要是范围查询,一般建议采用范围分片方式。
如果既有大量点查,又有大量范围类查询,为了避免范围查询引起的所有分片广播查询,因此建议范围分片,这样点查和普通范围查询都可以从一个分片获取数据。
片建选择
分片集群片建通常选择高频类查询字段作为片建字段,同时注意 insert、update 等写入操作一定要带上片建字段,否则 mongos 会返回异常信息,因为不知道该去写操作那个分片的数据。
balance 窗口设置
分片集群抖动很多都和 moveChunk 相关,moveChunk 操作会增加锁、资源消耗,同时涉及路由刷新等流程,因此建议分片集群设置 balance 窗口期,尽量业务低峰期进行balance 操作。
分片方式和片建选择比较特殊,和业务使用方式关系密切,因此需要提取评估,确保读写性能最优。
3.2. 索引问题
索引问题注意包括索引操作过程问题和索引内容问题,下面进行详细说明。
索引操作过程不合理
以副本集添加索引为例,createIndex 创建索引成功实际上在主节点成功后就返回了,从节点还没有添加索引成功。如果用户做了读写分离,并且从节点压力比较大,这时候可能从节点执行索引时间会更长,如果用户主节点执行成功后,createindex 返回立马又添加其他索引,这时候可能存在多个索引在从节点执行的情况,这样从节点压力会很大。
此外,用户如果添加一个索引“成功”,这时候从节点实际上还在执行该索引,用户立马删除该表某个索引,这时候从节点会不可访问,因为删除索引会添加 MODE_X 排它锁。
如果业务非常核心,不允许任何抖动,还可以使用通过滚动添加索引方式来添加索引,具体操作步骤详见:
https://www.mongodb.com/docs/manual/tutorial/build-indexes-on-replica-sets/
https://www.mongodb.com/docs/manual/tutorial/build-indexes-on-sharded-clusters/
MongoDB 智能索引推荐实现
智能索引推荐主要是基于索引规则和代价估算来实现的,整体架构如下:
智能索引推荐分为四个模块:
agent模块:实时收集 mongod 节点日志,然后输出日志到kafka模块。
kafka模块:存储 mongo 节点收集的日志信息。
日志分类模块:kafka 有用慢日志收集,并进行分类处理。
代价估算模块:模拟 MongoDB 内核执行计划过程,进行候选索引代价估算。系统整体的工作流程如下:每个 MongoDB 节点上布署有 agent 节点,它会实时采集所有有用的日志并存储到 Kafka 里,日志分类处理模块会从 Kafka 里把需要的日志提取出来进行分类,然后把分类好的日志交给索引代价计算模块进行计算,最终得到最优索引。
其中,agent 模块和 kafka 模块的逻辑相对简单,这里重点介绍日志分类模块和代价估算模块。
4.1. 日志分类模块的实现步骤
第一步:提取有效的慢日志。
并不是所有的慢查询日志都需要处理,只需提取存在索引问题的慢查询,诸如索引不是最优、全表扫描,这类日志才需要提取。如果判断索引不是最优?
答案是对比走索引时候数据扫描的行数与实际返回数据的行数,如果差值较大,就判断这个索引不是最优的,需要进一步优化。
第二步:根据 filter 对 SQL 分类。
同一个库表中会有很多查询,查询条件不尽相同,属于同一类的 SQL 需要满足几个条件,即库、表、命令、查询条件完全相同。前三个条件容易区分,比如同库同表情况下,查询条件(包括 find 、update 、delete 等)相同算一类,而查询条件相同的前提是查询关键字要相同且操作符属于同一类,同时要忽略查询字段顺序。
日志聚合处理
定期从 DB 中获取分类好的 SQL 信息交给代价估算模块进行处理。
4.2. 索引代价计算模块处理流程
抽象语法树生成及分解:从日志分类处理模块中获取对应 SQL,抽象语法树,同时进行分解。
根据索引规则生成候选索引:根据最优候选索引规则生成侯选索引的流程,可以参考腾讯云数据库公众号发布的文章:《云上MongoDB常见索引问题及最优索引规则大全》一文。
对候选索引进行代价估算:从源集群随机抽样数据,对候选索引进行代价估算,最终得出最优索引。下面重点介绍候选索引代价计算过程。
4.3. 候选索引代价计算
代价计算主要步骤:
随机采样少量数据:从线上实例随机采样少量数据,为了尽量减少对线上集群影响,优先采集隐藏节点,如果没有隐藏节点则采集从节点。同时控制采样数据量和采样频率,尽力减少对线上集群影响。
获取每个字段区分度:根据采样的数据获取查询条件对应字段的区分度。
根据裁剪后的子树按照索引规则生成候选索引:这里可以参考腾讯云数据库公众号输出的 MongoDB 索引规则大全。
每个候选索引代价计算:对每个候选索引模拟内核执行计划流程确定计算代价成本。
假设有 [{work:1, city:1, province:1}, {city:1, province:1, age:1}]) 这个候选索引,其代价计算过程如下图所示:
上面的候选索引对应的执行计划流程是:如果查询选择该候选索引执行,其执行计划首先进入 index scan stage,然后进入 OR stage,OR stage 执行完成后就会开始做fetch操作,最后就会得出整个流程扫描了多少行数据、得到了多少行数据,以及整个流程的执行时间。
腾讯云的代价估算是由一个旁路模块实现的,实现难度较大,需要对整个内核执行计划有较透彻的理解。所以对于自研用户,如果研发人力有限,可以采样数据到新的 MongoDB 集群,根据候选索引规则,同时借助内核已有的能力进行字段区分度、候选索引代价计算,最终得出执行这个索引扫描了多少行、返回了多少行、执行了多长时间,最终也可以得到最优索引。
智能索引推荐当前已经服务化,逐步会开放给用户使用,如果大家有兴趣可以去体验。索引推荐基本上能在半小时内发现实例上存在的索引问题,除了推荐最优索引外,还可以把实例上的无用索引和重复索引也找出来,这样能用最少的索引满足用户需求,性能等方面也会更好。
4.4. 腾讯云MongoDB索引推荐总结
快:慢查产生半小时左右推出最优索引
准:推荐索引为候选索引中代价计算最小的索引
稳:采样计算过程对云上集群影响较小,索引添加过程增加保护措施,同一实例同一时刻最多同时添加一个索引。
MongoDB 内核 SQL 限流实现
5.1. 为什么要做SQL限流?
首先,我们先思考这样一个问题:为什么要做SQL限流?
一方面,当流量过大、负载过高,数据库抖动可能造成雪崩时,就可以限制一下流量,保证一些请求能正常返回。另一方面,有些用户为了节约成本,将多个用户的数据写到了同一个实例不同表中,某一时刻可能出现用户新上的接口不对或其它异常情况,导致流量非常高,就会影响这个实例上的其他核心业务,这时就可以通过限流对异常或者不太重要的表做限流处理,保证核心的业务流量能正常访问。此外,还有一些突发扫表,高危操作等都可以通过限流进行限制。
5.2. 内核哪个位置增加限流功能?
那么,我们在内核哪个地方做了SQL限流功能呢?
先捋一下 MongoDB 的整体架构,它是分层的,第一层是网络收发模块,网络收发过后,交由命令处理模块把这些 SQL 解析出来,然后这些 SQL 会进入查询引擎模块、读写模块以及并发控制模块等流程。
5.3. SQL 限流核心实现
我们整个 SQL 限流模块是加在命令处理模块之后的,加在这里有什么好处呢?因为在这里已经拿到了详细的 SQL ,并且在并发控制之前做到 SQL 限流,避免 SQL 限流里面的操作会影响并发控制和数据库读写访问,防止与下层的并发控制模块产生冲突。
内核 SQL 限流的整体流程如下:
首先,在 DBbrain 界面上可以配置策略规则,比如 SQL 类型、并发数,可以配置定时关闭还是手动关闭,定时关闭是指最多运行多少时间,手动关闭就是打开后一直执行,除非人为手动关闭停止。
然后,是根据读写 SQL 关键字,配置完规则,就可以对指定库、表或者指定的 SQL 语句做限流。整个流程是首先在 DBbrain 的控制台下发规则,以分片集群为例,就下发给分片集群的 config server,config server 接收到后把这个规则写到 config server 的一个表里,shard server 的每个 mongod 定期从 config server 获取这些规则并加载到自己内存里,所有的 mongod 节点内存里就会有完整的规则数据存在,当发起一个请求,通过客户端到代理,再到 mongod 节点的时候,进行限流规则匹配,触发限流操作。
至于为什么选择在 mongod 上做限流,而不是在 mongos 上。主要是因为,mongos 上面的流量控制是由客户端基于 IP 做哈希的,可能导致流量不均匀。此外,线上有副本集的集群,也有分片集群,在mongod上做可以实现代码统一。在 mongos 上做限流,因为 mongos 之间是无状态的,无法保证相互的控制达到一定的级别。最后,瓶颈一般都在 mongod 节点上,所以就选择在 mongod 上面做限流。
5.4. SQL 限流规则及规则匹配限流流程
下面继续分享腾讯云 MongoDB SQL 限流的限流规则和规则匹配限流流程。
限流规则:
至于 SQL 限流规则主要包含哪些信息,主要包括 SQL 类型(比如增删改查)、限流时间以及并发数,并发数可以限制某类请求同时访问我们 DB 的并发量,另外就是关键字,可以匹配库,也可以匹配表,甚至可以匹配详细的 SQL,这样就可以实现指定的库、表和某一类型的 SQL 限 流。
请求匹配规则流程:
当一个请求到达 MongoDB 后,具体的处理流程是,先看这个实例是否启用了 SQL 限流功能,如果已启用,则提取用户请求中的库、表和 SQL 关键字信息,下一步和配置的限流规则做匹配,判断这类 SQL 是否有可用的 ticket。
ticket 代表并发控制中的并发数,如果没有可用的 ticket ,比如 ticket 值为0,就直接限制这个请求,返回客户端异常。如果有可用的 ticket ,就把 ticket 值减1,同时访问 DB ,访问到 DB 后就将数据返回给客户端,同时释放当前 ticket ,后面的请求就可以继续复用,这就是整个限流工作流程。
SQL 限流体验如下:
智能诊断案例分享
(路由问题、排它锁问题)
以下内容是在 MongoDB 社区分享的两例典型案例,如果踩坑后果非常比较严重,因此这里单独一节分享。
6.1. 路由异常诊断优化
mongos 1触发 chunk [1-50}从分片2迁移到分片1,整个迁移过程 mongos 1、分片2及分片1都能感知到这个事件,因此他们都有最新的路由信息。然而, mongos 2、 mongos 3和分片0感知不到这个事件,因此还是老的路由信息,认为 chunk [1-50}还是在分片2,实际上数据已经迁移到了分片1。
由于客户端读走从节点,mongos 1收到例如 xx =20的请求后,查询内存中路由信息,数据在分片1,因此从分片1从节点获取数据,由于 chunk [1-50}对应的数据全在分片1,因此可以访问到数据。
由于从节点默认不会做路由版本检测,因此当 mongos 2或者 mongos 3访问 xx =20的数据,该数据路由记录 chunk [1-50}在分片2,由于数据已经从分片2迁移到分片1,分片2实际上已经没有该数据了,因此访问不到数据。
优化方法:
暴力优化方法:db.adminCommand({“flushRouterConfig”:1}) ,强制路由刷新,存在性能抖动。该操作会情况内存缓存的所有路由信息,新请求进来后需要从 config server 获取所有 chunks 信息加载到本地内存,如果 chunks 较多,整个过程 mongos 访问不可用。
腾讯云优雅优化方法:实时检测每个 mongos shardVersion 主版本号,如果存在差异,则版本较低的 mongos 主动访问一次每个分片主节点,确保 mongos 从 config server 获取最新增量路由信息。这个过程只会从 config server 获取差异的 chunks 信息,因此影响较小。
6.2. MODE_X 排它锁检测
很多高危操作会添加排它锁,会引起库表维度,甚至是整节点维度阻塞不可用,例如下面这些操作都会添加 MODE_X 独占锁:
①.表加索引过程中删除索引
②.前台加索引
③.表重命名
④.索引重构
⑤. ……
MODE_X 排他锁检测方法:
定期实时获取 lockInfo ,获取其中的排它锁对应 DDL 操作,实时同步用户。
关于作者:杨亚洲
MongoDB 中文社区成员 ;
现任腾讯云数据库专家工程师 ;
前滴滴出行技术专家;
前 OPPO 文档数据库研发负责人;
一直专注于分布式缓存、高性能服务器、分布式存储、数据库、中间件等相关研发。