小试牛刀-万亿级数据库 MongoDB 集群性能数十倍提升及机房多活容灾实践

Mongo学习整理

开头

为什么开始写这篇文章,是因为最近准备开始梳理一下mongo数据库相关的知识点,之前也有做笔记的习惯,但是都是在自己的电脑上写一些笔记,没有分享出来,对于知识的一些片面理解和一些错误得不到反馈,对自己的提升和知识面正确性都有很大的弊端,所以打算通过写一些开源的文章来解决这些问题。

使用Mongo的原因

最近在做公司一个瞬时并发和写入比较多的项目,百万级别吧。公司目前使用的是Mysql进行分库分表,数据保存两个礼拜,个人认为该系统使用mysql分库分表没有较大必要,也增加了系统复杂度,也觉得有点大材小用。所以基于自己以往对于mongo使用的经验以及根据网站上大佬的经验,准备进行一波系统重构,在引入之前需要考虑一下情况:

  1. 容灾备份问题
  2. 性能优化问题

容灾备份问题

  1. 对于所有类型的数据库来说,容灾备份能力是很重要的一个功能,因为一个公司的核心就是数据,数据的丢失和安全问题对一个公司也是致命的,mysql,mongo也如此。

  2. 通过对业界大佬关于mongo相关文章的阅读,学习到了mongo的容灾备份方案大致有以下几个。

    方案一:同城三机房多活方案(1mongod+1mongod+1mongod 方式)
    同城三机房多活方案

  • 每个机房代理至少部署 2 个,保证业务访问代理高可用

  • 每个分片的副本分布在不同机房中

  • 如果某机房异常,并且该机房节点为主节点,借助 mongodb 天然的高可用机制,其他机房 2 个 mongod 实例会自动选举一个新节点为主节点。

  • 客户端配置 nearest 就近访问,保证读走本机房节点。

  • 弊端:如果是异地机房,B 机房和 C 机房写存在跨机房写场景。如果 A B C 为同城机房,则没用该弊端,同城机房时延可以忽略

    方案二:同城两机房多活方案(2mongod+2mongod+1arbiter 模式)
    同城两机房多活方案

  • 每个机房代理至少部署 2 个,保证业务访问代理高可用

  • 每个分片的副本分布在不同机房中,仲裁节点arbiter单独分配在一个机房

  • 如果机房 A 挂掉,则会在 B 机房 mongod 中重新选举一个新的主节点。arbiter 选举节点不消耗资源

  • 客户端配置 nearest 参数,保证读走本机房节点

  • 弊端:如果是异地机房,B 机房和 C 机房写存在跨机房写场景。如果 A B 为同城机房,则没用该弊端,同城机房时延可以忽略。

    方案三:异地三机房多活方案(1mongod+1mongod+1mongod 方式)-解决跨机房写

异地三机房多活方案

  • 每个机房代理通过打标签的方式,代理转发数据到主节点在本机房的分片上去。
  • A 机房数据通过标签识别转发到分片 shard-1,B 机房数据转发到分片 shard-2,C 机房数据转发到分片 shard-3

性能优化问题

1.mongodb 默认线程模型(一个链接一个线程)
mongodb 默认线程模型
说明:

  • listener 线程负责接受所有的客户端链接
  • listener 线程每接收到一个新的客户端链接就创建一个线程,该线程只负责处理该链接请求处理。

该网络线程模型缺陷:

  • 一个链接创建一个线程,如果 10 万个链接,那么就需要 10 万个线程,系统负责、内存消耗也会很多
  • 当链接关闭的时候,线程销毁,频繁的线程创建和消耗进一步增加系统负载

典型案例:

  • mysql 默认方式、mongodb 同步线程模型配置,适用于请求处理比较耗时的场景,如数据库服务

2.mongodb 默认线程模型(动态线程模型:单队列方式)
mongodb 默认线程模型
说明:

  • 该模型把一次请求转换为多个任务:mongodb 数据读操作(网络 IO)、db 层数据访问(磁盘 IO)。
  • 任务入队到全局队列,线程池中的线程从队列中获取任务执行。
  • 同一个请求访问被拆分为多个任务,大部分情况下通过递归调用同一个请求的多个任务会由同一个线程处理。
  • 当任务太多,系统压力大的时候,线程池中线程数动态增加;当任务减少,系统压力减少的时候,线程池中线程数动态减少

该网络线程模型缺陷:

  • 线程池获取任务执行,有全局锁竞争,这里就会成为系统瓶颈

典型案例:

  • mongodb 动态 adaptive 线程模型,适用于请求处理比较耗时的场景,如数据库服务

注意,前方高能预警,牛逼的来了,救护车,嘟嘟嘟!!!!

3.mongodb 优化后线程模型(动态线程模型-多队列方式)

mongodb 优化后线程模型
说明:

  • 把一个全局队列拆分为多个队列,任务入队的时候按照 session 链接 hash 散列到各自的队列,工作线程获取获取任务的时候,同理通过同样的 hash 算法去对应的队列获取任务,通过这种方式减少锁竞争,同时提升整体性能。

典型案例:

  • mongodb 内核多队列 adaptive 线程模型优化,特定场景性能有很好的提升,适用于请求处理比较耗时的场景,如数据库服务。

现状:

  • 臣妾达不到,需要修改mongo源码。

优化实践

背景
  • 数据量千亿级
  • 前期写多读少,后期读多写少
  • 高峰期读写流量百万级
  • 数据增长快,不定期扩容
  • 同城多活集群
优化策略 1:部署及使用方式优化
  • 预分片,写入负载均衡。
  • WriteConcern:{ w: “majority”},写大部分节点成功才返回客户端 OK
  • 读写分离,读从优先。
  • enableMajorityReadConcern 关闭,有性能损耗。
优化策略 2:存储引擎 cache 淘汰策略优化
  • wiredtiger 存储引擎 cache 淘汰策略优化后配置:
    wiredtiger 存储引擎 cache 淘汰策略
  • 总体思想:evict 线程尽早淘汰脏页 page 到磁盘,增加 evict 淘汰线程数加快脏数据淘汰,避免用户请求线程进行脏数据淘汰
优化策略 3:存储引擎 checkpoint 优化

存储引擎 checkpoint 检测点,把当前存储引擎脏数据全部记录到磁盘。触发条件如下:

  • 固定周期做一次 checkpoint 快照,默认 60s
  • 增量 journal 日志达到 2G

少部分实例存在如下现象:一会儿磁盘 IO 几乎空闲 0%,一会儿磁盘 IO 短暂性 100%。进行如下优化后可以缓解该问题:

 checkpoint=(wait=30,log_size=1GB)

该优化总体思路:缩短 checkpoint 周期,减少 checkpoint 期间积压的脏数据,缓解磁盘 IO 高问题。

优化策略 4:sharding 集群 system.session 优化
  • 代理缓存所有客户端的链接信息到内存中,并定期更新到 config 库的 system.sessions 表中。
  • 大流量大数据量集群客户端链接众多,大量更新 sessions 表,最终主分片性能下降引起整个集群性能瞬间数倍下降。
    sharding 集群 system.session 优化
    优化方法:
  • config 库的 system.sessions 表启用分片功能。
  • mongos 定期更新优化为散列到不同时间点进行更新。
  • 之前代理集中式更新单个分片,优化为散列到不同时间点更新多个分片。
  • 该优化后 system.sessions 表更新引起的瞬间性能数倍降低和大量慢日志问题得到了解决。
优化策略 5:tcmalloc 内存优化
  • db.serverStatus().tcmalloc 监控发现部分 mongod 实例 pageheap、内存碎片等消耗过高。通过系统调用分析得出:内存碎片率、pageheap 过高,会引起分配内存过程变慢,引起集群性能严重下降

该优化总体思路:

  • 借助 gperftools 三方库中 tcmalloc 内存管理模块,实时动态调整 tcmalloc 内存 Release Rate,尽早释放内存,避免存储引擎获取 cache 过程阻塞变慢
优化策略 6:根据业务优化存储结构

业务背景:

  • 集群存储离线数据
  • 集群总数据量万亿级
  • 前期主要为数据写入,要求尽快全部写入集群
  • 后期主要是读流量,单次查询数据条数比较多,要求快速返回
  • 每隔一定时间周期(周为单位)会有持续性大量写入
1.{  
2.    "_id": ObjectId("5fh2ebd18856960dbac31abc"),  
3.    "characteristic": "xxxx",  
4.    "key1": "***",  
5.    ......  
6.    "keyn": "***",  
7.}  
  • 以上为单条数据的数据模型,该集群总数据量亿级。
  • 数十万条数据拥有同样的 characteristic 特性,总特性数总计数百万个。
  • 一次性查询数十个 characteristic 很慢。
  • 瓶颈点: 一次性查询数十个 characteristic 特征条件的数据,每个特征拥有数百万数据,一次查询总计千万行数据。由于数据量很大,每行数据几乎全在磁盘,一次查询需要千万次 IO 操作,查询成为瓶颈。

注意,前方高能预警,牛逼的来了,救护车,嘟嘟嘟!!!!

  • 第一轮数据存储模型优化:
1.{  
2.    "_id": ObjectId("5f29ebd18856960dbac31abc"),  
3.    "characteristic": "xxxx"  
4.    "group": [  
5.           {  
6.            "key1": "***"  
7.            ......  
8.            "keyn": "***"  
9.        },   #该characteristic下第一条数据  
10.            ......  
11.        {  
12.            "key1": "***"  
13.            ......  
14.            "keyn": "***"  
15.        } #该characteristic下第n条数据  
16.    ]  
17.} 
  • 该数据模型把相同 characteristic 特性的数十万数据合并到为一条数据,减少磁盘 IO 操作,整个读性能会有近百倍提升。

  • 瓶颈点:该轮优化解决了读瓶颈,却引入了新的写瓶颈。

  • 通过 $addToSet 方式向 group 数组中去重追加数据,数据长度越来越长,磁盘 IO 压力越来越大、写性能成为新的瓶颈。

  • 第二轮数据存储模型优化:

1.{  
2.    "_id": ObjectId("5f29ebd18856960dbac31abc"),  
3.    "characteristic": "xxxx",  
4.    "hashNum": num,     
5.    "group": [  
6.           {  
7.            "key1": "***",  
8.            ......  
9.            "keyn": "***",  
10.        },   #该characteristic下第一条数据  
11.            ......  
12.        {  
13.            "key1": "***",  
14.            ......  
15.            "keyn": "***",  
16.        } #该characteristic下第n条数据  
17.    ]  
}  
  • 如上,把同一个 characteristic 特征的数十万/数百万数据散列为 500 份,这样合并后 group 数组中也就只包含数百条数据信息,这样合并后单条数据过大、mongodb 单条数据 64M 限制问题、磁盘 IO 过高等瓶颈问题都可以得到解决
总体数据模型优化思路:通过合理的数据合并操作来减少网络 IO、磁盘 IO、mongodb 内核处理时间,最终使读和写达到平衡
如果公司不在乎成本,那再上百台 SSD 服务器,飞起,万亿级别数据。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值