百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT技术 > 正文

记一次生产事故:MongoDB数据分布不均的解决方案

wptr33 2025-05-02 13:51 18 浏览

推荐阅读:我为什么放弃MySQL,选择了MongoDB?看到这个数据你就会明白了

事故集合:

可以很明显可以看到我们这个集合的数据严重分布不均匀。

一共有8个分片,面对这个情况我首先想到的是手动拆分数据块,但这不是解决此问题的根本办法。

造成此次生产事故的首要原因就是片键选择上的问题,由于片键选择失误,在数据量级不大的时候数据看起来还是很健康的,但随着数据量的暴涨,问题就慢慢浮出了水面,我们使用的组合片键并不是无规律的,片键内容是线性增长的,这就导致了数据的不正常聚集。由于数据分布不均匀,我们有两个分片的磁盘使用率接近80%,数据还在持续增长,这个问题必须尽快解决。

涉及到此次事故的集合一共有三个,总数据量加起来接近30T,数据总量300亿左右。

下面是我解决此问题的解决方案:

方案一:

第一步:创建一个新的分片表,片键我选择_id做hashed分片,并提前分好了数据块,降低在恢复期间频繁切割数据造成的服务器压力。

sh.shardCollection("loan_his.collection",{_id:"hashed"},false,{numInitialChunks:1024})

第二步:单独连接各个分片将8个分片的数据全量备份:

nohup mongodump -u loan_his -p loan_his --authenticationDatabase loan_his -h ${replset} --db loan_his --collection ${collectionName} --query '{"txdt": { $lte: "2019-07-09"} }' -o ${bak_dir} &>> ${log} &

你可能会问为什么不连接mongos,因为我在连接mongos做数据备份时出现了以下异常:

2019-07-08T16:10:03.886+0800 Failed: error writing data for collection `loan_his.ods_cus_trad` to disk: error reading collection: operation was interrupted

可能是因为集合内的数据坏块吧,此异常信息是我备份了将近70%的数据后突然抛出的异常信息。

除了这个原因,单独备份各个分片的数据后你能够自由控制恢复数据的时间窗口,不会因为恢复单个数据文件时间较长,突发意外情况导致恢复中断从头再来的窘境。能够根据服务器的状态避开高峰期来进行数据恢复。

备份期间我发现了有时候备份出来的总文档数和
db.collection.getShardDistribution() 查看的文档数不一致,我还以为是备份期间出了问题,但我删除当前备份文件后重新备份出来的文档数还是和之前一样。目前不知道是怎么回事,怀疑是坏的数据块引发的我问题,备份出来的数据一般会比原数据量多几万条数据,有时候会少一些。

第三步:恢复数据:

 mongorestore -u loan_his -p loan_his --authenticationDatabase loan_his -h 10.0.156.9:27017 --db loan_his --collection ${collectionName_two} /mongodb/${collectionName}/replset_sh2/loan_his/${collectionName}.bson &>> ${log}

在恢复数据前千万要记得不要创建索引!否则性能极差,速度非常非常慢!在使用mongodump工具备份时,在数据文件的同级目录下会有一个 XXXXX.metadata.json 索引文件,默认会在数据恢复完毕后执行创建索引的操作。

此处有坑需要注意:因为备份出来的数据是由原表备份出来的,那这个索引文件也是原表的索引,由于原表我使用的是组合片键做的分片,所以在原表内会存在一个由片键组成的组合索引,并且不是后台创建的组合索引!!!这意味着如果你使用此索引文件来给新表创建索引,会造成这个集群处于阻塞状态,无法响应任何操作!!直至索引创建完毕。所以你可以将这个索引文件备份到其它目录以作参考,然后将原文件删除就可以了,恢复数据时不会有其它的问题。

如果恢复期间出现了意外情况导致恢复失败,比如节点宕机什么的,不需要担心,重新执行恢复程序,数据文件不会重复增加,因为备份出来的数据文件包含mongodb自带的 Objectld对象_id ,导入时,如果已存在此ID,将不会插入数据。注意:在不同集合是允许出现相同ID的,所以在使用方案二恢复数据时,新产生的数据不能通过新表A备份出来汇入新表C,需要通过原始数据文件重新导入。

第四步:创建索引:

待所有数据恢复完毕后再创建索引,一定要记得后台创建!!!你也可以将索引拆分,一个一个的来。如果觉得此操作对业务影响较大,请看本文最后的解决方案。

mongo 10.0.156.2:27017/loan_his -uloan_his -ploan_his -eval 'db.getSiblingDB("loan_his").runCommand({createIndexes: "collection",indexes: [{"v":2,"key":{"_id":1},"name":"_id_","ns":"loan_his.collection"},{"v":2,"key":{"opnode":1.0,"txdt":1.0,"acct":1.0,"crdno":1.0},"name":"opnode_1_txdt_1_acct_1_crdno_1","ns":"loan_his.collection"},{"v":2,"key":{"txdt":1.0,"opnode":1.0,"acct":1.0,"crdno":1.0,"pbknum":1.0},"name":"txdt_1_opnode_1_acct_1_crdno_1_pbknum_1","ns":"loan_his.collection","background":true},{"v":2,"key":{"acct":1.0,"txdt":1.0,"opnode":1.0},"name":"acct_1_txdt_1_opnode_1","ns":"loan_his.collection","background":true},{"v":2,"key":{"crdno":1.0,"txdt":1.0,"opnode":1.0},"name":"crdno_1_txdt_1_opnode_1","ns":"loan_his.collection","background":true},{"v":2,"key":{"pbknum":1.0,"txdt":1.0,"opnode":1.0},"name":"pbknum_1_txdt_1_opnode_1","ns":"loan_his.collection","background":true}]})'

停止失控索引:

一旦你触发一个索引,简单的重启服务并不能解决这个问题,因为MongoDB会继续重启前的建索引的工作。如果之前你运行后台建索引任务,在服务重启后它会变成前台运行的任务。在这种情况下,重启会让问题变得更糟糕。MongoDB提供了选项“noIndexBuildRetry”,它会指示MongoDB重启后不再继续没建完的索引。如果不小心在前台创建了索引导致集群不可用,可以使用--noIndexBuildRetry 参数重启各个分片来停止索引的创建过程,只用重启主节点就可以了。如果是在后台创建索引,重启时记得加上--noIndexBuildRetry,否则重启后创建索引的线程会重新被唤醒,并由后台创建变为前台创建,导致整个集群不可用。

mongod -f $CONFIGFILE --noIndexBuildRetry

此方案迁移期间不用通知业务系统做变更,把数据迁移完毕后,通知业务系统将表名变更,弊端就是在你迁移的过程中数据还是会持续增长的,问题分片的磁盘容量会越来越少。

方案二:

为了避免在迁移期间数据仍在增长,导致数据还没迁移完毕磁盘就爆满的情况,可以选择停止往旧表B内写入数据,创建一个健康的新表A,新的数据往新表A内写,具体的查询方案需要应用系统的配合。然后将旧表B的数据迁移至新表C中,最终将新表A的数据汇入新表C , 完成数据迁移。此次迁移数据耗时共9个月!!!片键一定要慎重选择,因为我们使用的MongoDB是3.4.7版本的,不支持修改片键,最新版本支持片键的修改。

接下来介绍数据量较大时如何构建索引--减少业务最少影响

在数据量较大或请求量较大,直接建立索引对性能有显著影响时,可以利用复制集(数据量较大时一般为线上环境,使用复制集为必然选择或者使用分片.)中部分机器宕机不影响复制集工作的特性,继而建立索引。

(1)首先把 secondary server 停止,再注释 --replSet 参数,并且更改 MongoDB port 之后重新启动 MongoDB,这时候 MongoDB 将进入 standalone 模式;

(2).在 standalone 模式下运行命令 ensureIndex 建立索引,使用 foreground 方式运行也可以,建议使用background方式运行;

(3)建立索引完毕之后关闭 secondary server 按正常方式启动;

(4)根据上述 1~3 的步骤轮流为 secondary 建立索引,最后把 primary server 临时转换为 secondary server,同样按 1~3 的方法建立索引,再把其转换为 primary server。

日志内容大致如下:

2019-09-24T18:51:39.003+0800 I - [conn33] Index Build: 838416900/876543270 95%
2019-09-24T20:10:08.360+0800 I INDEX [conn33] done building bottom layer, going to commit
2019-09-24T20:10:26.001+0800 I - [conn33] Index: (2/3) BTree Bottom Up Progress: 11684400/876543270 1%
done building bottom layer, going to commit

相关推荐

SQL轻松入门(5):窗口函数(sql语录中加窗口函数的执行)

01前言标题中有2个字让我在初次接触窗口函数时,真真切切明白了何谓”高级”?说来也是一番辛酸史!话说,我见识了窗口函数的强大后,便磨拳擦掌的要试验一番,结果在查询中输入语句,返回的结果却是报错,Wh...

28个SQL常用的DeepSeek提示词指令,码住直接套用

自从DeepSeek出现后,极大地提升了大家平时的工作效率,特别是对于一些想从事数据行业的小白,只需要掌握DeepSeek的提问技巧,SQL相关的问题也不再是个门槛。...

从零开始学SQL进阶,数据分析师必备SQL取数技巧,建议收藏

上一节给大家讲到SQL取数的一些基本内容,包含SQL简单查询与高级查询,需要复习相关知识的同学可以跳转至上一节,本节给大家讲解SQL的进阶应用,在实际过程中用途比较多的子查询与窗口函数,下面一起学习。...

SQL_OVER语法(sql语句over什么含义)

OVER的定义OVER用于为行定义一个窗口,它对一组值进行操作,不需要使用GROUPBY子句对数据进行分组,能够在同一行中同时返回基础行的列和聚合列。...

SQL窗口函数知多少?(sql窗口怎么执行)

我们在日常工作中是否经常会遇到需要排名的情况,比如:每个部门按业绩来排名,每人按绩效排名,对部门销售业绩前N名的进行奖励等。面对这类需求,我们就需要使用sql的高级功能——窗口函数。...

如何学习并掌握 SQL 数据库基础:从零散查表到高效数据提取

无论是职场数据分析、产品运营,还是做副业项目,掌握SQL(StructuredQueryLanguage)意味着你能直接从数据库中提取、分析、整合数据,而不再依赖他人拉数,节省大量沟通成本,让你...

SQL窗口函数(sql窗口函数执行顺序)

背景在数据分析中,经常会遇到按某某条件来排名、并找出排名的前几名,用日常SQL的GROUPBY,ORDERBY来实现特别的麻烦,有时甚至实现不了,这个时候SQL窗口函数就能发挥巨大作用了,窗...

sqlserver删除重复数据只保留一条,使用ROW_NUMER()与Partition By

1.使用场景:公司的小程序需要实现一个功能:在原有小程序上,有一个优惠券活动表。存储着活动产品数据,但因为之前没有做约束,导致数据的不唯一,这会使打开产品详情页时,可能会出现随机显示任意活动问题。...

SQL面试经典问题(一)(sql经典面试题及答案)

以下是三个精心挑选的经典SQL面试问题及其详细解决方案,涵盖了数据分析、排序限制和数据清理等常见场景。这些问题旨在考察SQL的核心技能,适用于初学者到高级开发者的面试准备。每个问题均包含清晰的...

SQL:求连续N天的登陆人员之通用解答

前几天发了一个微头条:...

SQL四大排序函数神技(sql中的排序是什么语句)

在日常SQL开发中,排序操作无处不在。当大家需要排序时,是否只会想到ORDERBY?今天,我们就来揭秘SQL中四个强大却常被忽略的排序函数:ROW_NUMBER()、RANK()、DENSE_RAN...

四、mysql窗口函数之row_number()函数的使用

1、窗口函数之row_number()使用背景窗口函数中,排序函数rank(),dense_rank()虽说都是排序函数,但是各有用处,假如像上章节说的“同组同分”两条数据,我们不想“班级名次”出现“...

ROW_NUMBER()函数(rownumber函数与rank区别)

ROW_NUMBER()是SQL中的一个窗口函数(WindowFunction)...

Dify「模板转换」节点终极指南:动态文本生成进阶技巧(附代码)Jinja2引擎解析

这篇文章是关于Dify「模板转换」节点的终极指南,解析了基于Jinja2模板引擎的动态文本生成技巧,涵盖多源文本整合、知识检索结构化、动态API构建及个性化内容生成等六大应用场景,助力开发者高效利用模...

Python 最常用的语句、函数有哪些?

1.#coding=utf-8①代码中有中文字符,最好在代码前面加#coding=utf-8②pycharm不加可能不会报错,但是代码最终是会放到服务器上,放到服务器上的时候运行可能会报错。③...