高阶面试-mongodb

07-14 1049阅读

mongodb的特点,为什么使用他

nosql数据库,前端到后端到数据库,都是json,开发者友好。没有太多约束,可以快速实现需求迭代。

高阶面试-mongodb
(图片来源网络,侵删)

天生分布式,高可用,处理海量高并发的数据应用。

无模式,数据模型发生变更,不需要强制更新表结构

除了CRUD,还有aggregation,可以做分析报表;有gridfs做分布式文件存储

各种致敬,索引、慢查询、explain等,无缝迁移

地理空间索引,适合移动端业务

工具多,mtools搭建集群、mongostat、mongotop等

分布式高可用

涉及到 选举、复制、故障转移、数据均衡、复制延迟等

选举

需要强一致性,一般都是采用raft算法的实现,比如redis的选举、zk的选举等

流程:

各种原因触发选举startElectSelfIfEligible --> 预选举dryRun --> 选举realElection --> 投自己票voteForMyself --> 处理投票结果onVoteRequestComplete --> 处理选举获胜的结果processWinElection --> 状态变更restartHeartbeats_inlock --> 进入追赶模式catchupState --> 判断是否追上abort_inlock --> 收尾模式drainComplete

根据什么确认可以当leader?

心跳(其他节点可能率先完成选主)、任期(term)、opLog时间戳

为啥多了个预选举,防止 网络异常导致的 分区下自己投自己选了很多次term增加很多,然后网络恢复还要多一次选举的情况。

其他优化:

  • 多了个chainingAllowed 链式复制,节点同步数据可以选距离自己最近的节点复制数据,怎么判断最近,心跳延时最小
  • 支持投票优先级,氪金玩家体验更好
  • 选举策略,可以PSS PSA,也就是arbiter等
  • 脑裂的避免,primary 在选举超时时间内没收到大多数节点的应答会自动退位成secondary

    复制

    采用opLog同步数据,这里的oplog是一个特殊的固定集合,当主节点上的一个写操作完成后,会向oplog集合写入一条对应的日志,而备节点则通过这个oplog不断拉取到新的日志,在本地进行回放以达到数据同步的目的。由于日志会不断增加,因此oplog被设计为固定大小的集合,它本身就是一个特殊的固定集合(capped collection),当oplog的容量达到上限时,旧的日志会被滚动删除

    怎么实现的?

    一个环状的队列,新文档在写入时会被插入队列的末尾,如果队列已满,那么之前的文档就会被新写入的文档所覆盖

    类似disraptor

    备节点便可以通过轮询的方式进行拉取,这里会用到可持续追踪的游标(tailable cursor)技术

    其实类似kafka的LEO同步

    每个备节点都分别维护了自己的一个offset,也就是从主节点拉取的最后一条日志的optime,在执行同步时就通过这个optime向主节点的oplog集合发起查询。

    每一条oplog记录都描述了一次数据的原子性变更,对于oplog来说,必须保证是幂等性的

    如何实现

    执行$inc操作,每次都会产生新的结果。这些非幂等的更新命令在oplog中通常会被转换为$set操作

    可能问题?

    如果备节点的复制不够快,就无法跟上主节点的步伐,从而产生复制延迟(replication lag)问题。

    如何解决的

    调整 writeConcern 和 readConcern 设置。适当降低 writeConcern 的级别可以减少写入时的延迟,但要权衡数据安全性。启用压缩,如启用 snappy 或 zlib 压缩来减少网络传输的数据量。优化 oplog 大小,确保其足够大以容纳高峰期的写操作。

    在开始时,备节点仍然需要向主节点获得一份全量的数据用于建立基本快照,这个过程就称为初始化同步(initial sync)

    如何实现

    ● 备节点记录当前的同步optime=t1(来自主节点的同步时间戳),进入STARTUP2状态。

    ● 从主节点上复制所有非local数据库的集合数据,同时创建这些集合上的索引。

    在这个过程中,备节点会开启另外一个线程,将集合复制过程中的增量oplog(t1之后产生)也复制到本地。

    ● 将拉取到t1之后的增量oplog进行回放,在完成之前,节点一直处于RECOVERING状态,此时是不可读的。

    ● oplog回放结束后,恢复SECONDARY状态,进入正常的增量同步流程。

    故障转移

    自动选举,然后同步

    对于业务方,副本集发生主备切换的情况下,会出现短暂的无主节点,无法接受业务写操作,

    数据均衡

    创建索引失败MongoError: too many namespaces/collections

    涉及到存储引擎

    mongodb我们用的3.0,存储引擎使用的MMAPv1,采用内存映射文件管理数据,缺点是锁粒度是集合级别,并发性能受影响;缺少数据压缩,磁盘利用率低

    2015年3.2版本开始wiredTiger称为默认存储引擎

    storage.mmapv1.nsSize ,

    mongodb mmapv1 存储引擎的 namespace size 有大小限制,默认 16M,大概 24,000 表和索引在一个 db 里

    线上的 库的一个 db有 5000 左右个 collection,每个表了 里差不多有 1~10 个索引

    namespace啥意思?就是mongo的collection

    mongodb的OOM

    背景

    线上平台出现mongodb的oom,高可用架构重新选举之后选举完又OOM,mong重启达到分钟级别,多个节点被OOM后,不能很快拉起服务,对业务产生很大影响。

    分析

    表面看,有几个问题,1是128个G的内存,还会OOM,肯定有优化空间;2是为啥启动这么慢

    通过表分析,有很多大表,其中一个巨大的表占用了110G,且有频繁的读写,原因是有很多冷数据,未做冷热分离

    优化

    1. 删除部分历史遗留数据,如有些bak表
    2. mongodb的优化,mongodb3.2以后采用wiredTiger引擎,虽然不是内存数据库,但是为了提高读写效率,会最大化利用内存。修改evict的配置 db.adminCommand({setParameter: 1, wiredTigerEngineRuntimeConfig: "eviction=(threads_min=1,threads_max=8)"}) 原先最小线程是4,改为1,减少不必要的线程开销,减少IO抖动

    深挖

    mongo的内存使用

    理想情况,mongodb可以提供近似内存的读写性能,wiredTiger有两级缓存,第一层是操作系统的页面缓存,第二层则是引擎提供的内部缓存

    ![[Pasted image 20240713071010.png]]

    数据读取的流程:

    • 数据库发起buffer IO读操作,由操作系统将磁盘数据页加载到文件系统的页缓存区。
    • 引擎层读取页缓存区的数据,进行解压后存放到内部缓存区。
    • 在内存中完成匹配查询,将结果返回给应用

      如果数据已经被存储在内部缓存中,MongoDB则可以发挥最佳的读性能。稍差的情况是内部缓存中找不到,但数据仍然被存储在操作系统的页缓存中,此时需要花费一些数据解压缩的开销。为了尽可能保证业务查询的“热数据”能快速被访问,其内部缓存的默认大小达到了内存的一半,对应参数wiredTigerCacheSize指定的。

      数据写入的流程:

      先在内存中记录这些变更,之后通过CheckPoint机制将变化的数据写入磁盘

      带来问题:可靠性
      解决方案:

      checkpoint检查点机制,类似RDB,建立checkpoint的时候,会在内存建立所有数据的一致性快照,是通过MVCC保证,然后持久化快照,默认1min一次,成功后内存中的修改才会真正保存。

      journal日志 WAL机制,预写,顺序写,会将每个写操作的redo日志写入journal缓冲区,频繁地将日志持久化到磁盘上。一般100ms一次。如果journal日志达到100MB,或者应用程序指定journal为true,也会触发。

      本质:存量(快照)+增量(journal)

      实际写入的完整流程
      • 应用写数据CUD
      • mongo从内部缓存获取当前记录的page,如果不存在从磁盘加载 buffer IO
      • wiredTiger开始执行写事务,修改的数据写入page的一个更新记录表,原来的记录保持不变
      • 如果开启journal日志,写入的同时会写journal日志也就是redo log,不超过100ms将日志写磁盘
      • mongo每60s执行一次checkpoint,将内存的修改真正刷盘
        然后就是脏页 dirty page

        需要说到缓存页的管理

        page管理也是B+树,当叶子节点产生数据写入,更新记录会写入节点的一块独立区域,此时该节点被标记为脏页。其中insert和update是单独的跳表,分别存插入和修改操作,如果存在修改,读取的时候会从跳表做合并查找。

        checkpoint的时候,block manager发起reconncilication过程,将内存页转换为磁盘页的格式,checkpoint线程会遍历内存中全部页并找到所有脏页进行持久化,一般用copy-onwrite保证读写分离。

        对于脏页不是就地更新,而是产生新节点,每次都产生一个新的根节点,持久化完成,再淘汰不用的节点。

        reconciliation的触发

        • checkpoint
        • 缓存的page超过最大值(存在大量修改),产生分裂,触发evict
        • 缓存的脏数据比例达到阈值,触发缓存淘汰evict
          缓存淘汰

          wiredTiger基于LRU实现缓存的淘汰,通常由后台evict线程负责,如果内存很紧张,用户线程也会加入,读写卡顿。

          淘汰策略:

          ![[Pasted image 20240713074054.png]]

          从官网上看的

          数据压缩,

          • 集合采用块压缩,默认采用谷歌开源的snappy
          • 索引用前缀压缩 prefix compression
          • journal日志也是snappy压缩

            压缩算法可以调整,storage.wiredTiger.collectionConfig.blockCompressor mongodb4.2开始支持Zstd,facebook开源的较低的CPU消耗实现更高压缩比

            用内存做什么

            • mongo的数据读写
            • mongo连接线程
            • 管理操作如创建索引、数据备份

              内部缓存增大后,内存中允许驻留的脏数据也会更多,导致磁盘IO抖动问题更加明显

              mongodb cursor not found

              背景

              使用云厂商提供的mongodb分片集群,client–>slb–>mongos,数据量大的时候报错:[AllExceptionsFilter] CaughtException: MongoError: Cursor not found (namespace: 'v7common.users', id: 3392892230983559305).

              分析

              游标失效了,通过slb请求mongos,策略的原因,每次查的mongos不一样,游标在一个mongos打开,后续请求路由到另一个mongos了,导致游标丢失。

              优化

              SLB采用会话保持,让每个客户端的会话被路由到自己对应的mongos实例

              mongo的cursor

              从应用层面看,游标类似一个指针,看mongodriver,是个迭代器,对于find结果进行遍历,其实是通过MongoCursor对象操作。真实的实现,做了优化,每次获取一批数据放到内存。具体细节:

              • 第一次提交查询,才会携带查询条件、排序分页等参数,如果一次查询不完,通过getMore操作用cursorId进行分批拉取
              • 调用next方法,其实是获取缓存的一条数据,当缓存遍历完毕自动获取下一批
              • 每次拉取的条数由batchSize参数决定
              • 如果没有batchSize,默认首次find返回最多101条数据,后续getMore没有限制的化,默认返回不超过16MB的数据

                游标有个超时时间,默认10min,那不应该cursor not found的啊

                需要了解mongos的原理

                mongos查询路由,是分片集群的访问入口,从config server获取元数据并加载,然后提供访问服务将用户请求路由到对应分片。

                从不同的slb过来的被认为不同的连接,导致游标失败

                连接池偶发断开

                背景

                华为云ELB,mongo连接池偶发断开

                ![[Pasted image 20240713102322.png]]

                一段时间后(大概一分钟)还能自动恢复

                ![[Pasted image 20240713102337.png]]

                2021-11-16 12:46:11.938 nacos [http-nio-8089-exec-4] WARN  org.mongodb.driver.connection - Got socket exception on connection [connectionId{localValue:95, serverValue:9552231}] to 172.16.3.182:7211. All connections to 172.16.3.182:7211 will be closed.
                2021-11-16 12:46:11.939 nacos [http-nio-8089-exec-4] INFO  org.mongodb.driver.connection - Closed connection [connectionId{localValue:95, serverValue:9552231}] to 172.16.3.182:7211 because there was a socket exception raised by this connection.
                2021-11-16 12:46:11.939 nacos [http-nio-8089-exec-4] INFO  org.mongodb.driver.cluster - No server chosen by ReadPreferenceServerSelector{readPreference=primary} from cluster description ClusterDescription{type=SHARDED, connectionMode=MULTIPLE, serverDescriptions=[ServerDescription{address=172.16.3.182:7211, type=UNKNOWN, state=CONNECTING}]}. Waiting for 30000 ms before timing out
                2021-11-16 12:46:11.940 nacos [http-nio-8089-exec-4] INFO  org.mongodb.driver.connection - Closed connection [connectionId{localValue:94, serverValue:9552230}] to 172.16.3.182:7211 because there was a socket exception raised on another connection from this pool.
                

                ![[Pasted image 20240713102409.png]]

                mongo日志也有报错

                ![[Pasted image 20240713102424.png]]

                分析

                ELB导致的mongos的问题,云不是真正的高可靠,打破大厂迷信。

                mongo的常用工具

                mongostat

                /opt/mongodb3.2.13/bin/mongostat -h 10.152.206.81 --port 7210 -u xxx -p 'xxx' --authenticationDatabase=admin --discover

                ![[Pasted image 20240713070357.png]]

                一般用来查看QPS、内存占用、连接数等,里面vsize是虚拟内存 res是物理内存使用量

                另外就是

                • CRUD的速率是否有波动,超出预期
                • connect 连接数是否太多
                • dirty 百分比是否较高,如果持续高于10%说明磁盘IO存在瓶颈
                • repl 状态是否异常,如果 RTR正常,如果REC等异常值需要修复

                  mongotop

                  ``

                  用来查看热点表,

                  • 是否存在非预期的热点表,其实就是看是否有慢查询
                  • 热点表的操作耗时是否过高,业务高峰一般比较高一点

                    可以设置 -n 100 2 就是间隔2s,总共输出100次

                    迁移

                    采用 mongoshake

                    mongodb相关优化

                    mongo查询数据以后,应该使用project只返回需要使用的字段,否则在数据量、字段比较多的时候,查询效率会显著下降

                    索引:覆盖索引

VPS购买请点击我

文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。

目录[+]