客户端映射内存(客户端映射内存不足)

菲律宾亚星国际登录 1 5

  

  本文根据【2016 第七届中国数据库技术大会】现场演讲嘉宾 孙玄老师分享内容整理而成。录音整理及文字编辑IT168@ZYY@老鱼

  讲师简介

  

  ▲孙玄

  58集团高级系统架构师,58集团技术委员会主席&架构组主任,产品技术学院优秀讲师,58同城即时通讯、转转架构算法部负责人,擅长系统架构设计,分布式存储等技术领域。代表58同城多次参加QCon,SDCC,DTCC,Top100,Strata+Hadoop World,WOT等大会嘉宾演讲,并为《程序员》杂志撰稿2篇。 前百度高级工程师,参与社区搜索部多个基础系统的设计与实现。毕业于浙江大学。“架构之美”作者。

  正文

  大家好,我是来自58的孙玄,今天分享的主题是《MongoDB在58同城的应用实践》,主要内容如下所示:

  

  首先介绍一下MongoDB在58的使用情况,从2011年到2014年,58基本使用了三年MongoDB,三年中,整个公司的业务线都在大规模使用MongoDB。在2014年,由于技术选型的改变,58配合业务特点做了一些调整。14年和15年,58基本很少使用MongoDB,但随着赶集和英才对MongoDB的频繁使用,58于今年又开始大规模使用MongoDB。

  就我个人而言,从15年开始真正使用MongoDB,主要应用在业务线上,所以分享的内容主要针对15年之前MongoDB在58的应用案例和实践。随着MongoDB社区的发展,特别是3.0的崛起,可能会略有不足。以上便是此次分享的背景,接下来切入主题。

  

  基本上在2014年底,58比较核心的业务线都在使用MongoDB,包括IM,交友,招聘,信息质量,测试以及赶集和英才。现在最核心的数据存储也是采用MongoDB在做,后期由于技术选型以及业务调整,使用量有所下降。

  

客户端映射内存(客户端映射内存不足)-第1张图片-亚星国际官网

  接下来讲一下选用MongoDB的原因,作为一款NoSQL产品,选择的同时我们会考虑到它的特性,主要有如下几方面:

  一是扩展性,MongoDB提供了两种扩展性,一种是Master—Slave,基于主从复制机制,现在58用的比较多的是Replic Set,实际上就是副本集结构。我们会针对不同的业务项进行垂直拆分。

  二是高性能,MongoDB在3.0之前,整个的存储引擎依赖于MMAP。MMAP是整个的写内存,就是写磁盘。数据写入内存之后,要通过操作系统的MMAP机制,特别做数据层的,如果数据存多份的话,就可能会造成数据不一致的问题。MongoDB在提供高性能的同时,数据只存一份。这种情况下,设计提供高性能的同时,可以很好地解决数据一致性问题。

  除此之外,MongoDB也有其他一些特性,包括丰富的查询,full index支持和Auto-Sharding。另外,做任何选择一定要结合业务逻辑。58原来是分类信息网站,主要的业务特征是并发量比较大,但58和电商的自主交易不同,对事务没那么高要求。在这种场景下,选择MongoDB作为核心存储机制,是非常不错的选择。

  

  MongoDB不像关系型数据库,必须定义Schema,MongoDB比较个性化,对Schema的支持也没有那么严格,可以在表里随意更改结构。但free schema是否真的free吗?比如原来的关系型数据库,需要定义每一列的名称和类型,存储时只需存储真正的数据就可以了,schema本身不需要存储。由于MongoDB没有schema的概念,存储的自由度大了,但整个存储空间会带来一些额外开销,毕竟要存储字段名,value值无法改变,但可以减少字段名的长度,比如age可以考虑用A表示。这时可能会出现可读性问题,我们在业务层做了映射,用A代表age。同时,减小整个数据的字段名,通过上层映射解决可读性问题。

  另外我们还做了数据压缩,其实我们有很多文本数据,文本数据的压缩率还是比较高的,我们对部分业务也采取了数据压缩的方式。在Auto-sharding方面,我们采用库级sharding,collection sharding采用手动sharding。当整个表的行数量比较大时,会进行拆分,把一些比较大的文档切分成小文档,包括这些文档的嵌套存储,都是MongoDB相对于其他关系型数据库而言比较优秀的地方。

  自动生成_id其实就相当于主键的概念,默认的字段长度是12个字节,整个存储空间的占用比较大,我们尽可能根据业务特征,在业务层把该字段填充成我们自己的字段。如果存储用户信息,该字段可以填成UID,因为UID最大是八个字节。一方面可以减小整个存储空间,另一方面,虽说_id可以在MongoDB服务端生成,但我们尽可能把_id生成工作放在业务层或应用层,可以减少MongoDB在服务端生成_id的开销,写入压力比较大时,整个性能的节省非常明显。

  

  接下来是部署层面,每一个分片上是Replica Set,同时开启Sharding功能,基本结构如上图右边所示,每个分片做一个Sharding,在Sharding上有Replica Set的概念。通过这个架构,所有configs直接通过mongos到shards。增加sharding或者在sharding上做增减,实际上对整个应用是比较透明的。这样部署一方面可以很好的满足业务需求,另一方面可以很好地满足内部扩展和故障转移。

  另一个比较隐蔽的话题就是sharding操作,大家可能都比较关心,Auto-Sharding到底靠不靠谱。就我个人理解而言,既然要用Auto-sharding,就要解决sharding key的问题。如果选用单一递增的sharding key,可能会造成写数据全部在最后一片上,最后一片的写压力增大,数据量增大,会造成数据迁移到前面的分区。如果选用随机key,的确可以避免写问题,但如果写随机,读就会出现问题,可能会出现大量随机IO,对一些传统磁盘而言影响是致命的。那如何选取合适的sharding-key呢?先要保证该key在整个大范围内单调递增,这样随机选择时,可以保证相对均匀,不会引发其他问题。

  此外,我们在测试中发现,数据迁移过程中经常会出现一些问题。一旦发生数据迁移,比如从A迁到B片,数据可能同时存在于两片数据上,直至迁移完成,整个数据才会全部存在于B片上。58的业务特点属于中午访问的人很少,这时MongoDB集群的负载比较低,系统会认为此适合进行数据迁移,将会开启Auto-sharding。午饭时间结束之后,访问量就开始逐渐增加。此时,整个迁移尚未完成,不会立即停止,集群的OPS会瞬间从几千掉到几十,这对业务的影响非常大。这时,我们会指定整个sharding的迁移时间,比如从凌晨两点到早上六点这段时间属于业务低峰期,这段时间可以允许sharding进行业务迁移,同时开启数据库级别的分片。这样可以避免Auto-sharding数据迁移带来的问题。

  

  另外,做整个设计特别是业务设计时,一定要了解业务发展场景,比如半年或一年内,大概可以增长到什么样的规模,需要提前做预期。根据业务发展情况,就知道大概需要开多少分片,每一片放多少数据量合适。

  

  做整个规划时,也需要考虑容量性能。至少要保证Index数据和Hot Data全部加载到内存中,这样才可以保证MongoDB的高性能,否则性能压力还是蛮大的。2011年开始使用MongoDB时,数据库内存是32g,后来一路上升至196g,其实随着业务的发展,整个硬件投入成本也是蛮高的。实际上如果内存足够大,整个性能情况还是比较令人满意的。

  

  另外,MongoDB整个数据库是按照文件来存储的,如果有大量表需要删除的话,建议将这些表放到统一的数据库里,将会减少碎片,提高性能。单库单表绝对不是最好的选择,表越多,映射文件越多,从MongoDB的内存管理方式来看,浪费越多;同时,表越多,回写和读取时,无法合并IO资源,大量的随机IO对传统磁盘是致命的;单表数据量大,索引占用高,更新和读取速度慢。

  另外一个是Local库,Local主要存放oplog,oplog到底设多少合适呢?根据58的经验来看,如果更新比较频繁而且存在延时从库,可以将oplog的值设置的稍微大一点,比如20G到50G,如果不存在延时从库,则可以适当放小oplog值。

客户端映射内存(客户端映射内存不足)-第1张图片-亚星国际官网

  

  针对业务场景设计库和表,因为MongoDB实际上是带有嵌入式功能的,比如以人为例,一个人有姓名,性别,年龄和地址,地址本身又是一个复杂的结构,在关系型数据库里,可能需要设置两张表。但在MongoDB里非常简单,把地址做成嵌套文档就可以了。

  

  表设计无非这几种,一对一,一对多和多对多的关系,一对一关系比如用户信息表,实际上就是明显的一对一关系,类似于关系型数据的设计,用uid替换_id,做一个主键就ok了。

  

  一对多的关系比如用户在线消息表,一个人其实可以收到很多消息,这是明显的一对多关系,可以按照关系型数据库来设计,按行扩展;也可以采用MongoDB嵌套方式来做,把收到的消息存在一个文档里,同时MongoDB对一个文档上的每一行会有限制,如果超过16兆,可能会出现更新不成功的情况。

  

客户端映射内存(客户端映射内存不足)-第1张图片-亚星国际官网

  多对多关系如上图示例,整个包括Team表,User表,还有两者之间的关系表。在关系型数据库里,这是三张表,一张表是整个Team表的元数据,另一张表是User表的元数据,同时还有关系表,表示二者的包含关系。在MongoDB里,可以借助嵌套关系来完成这件事。

  

  每次通过Team表中的teammates反查询得到teamid,Teammates需要建立索引,具体设计可以参照上图。当整个表比较大的时候,可以做手工分表,这点与关系型数据库类似。

  

  当数据库中一个表的数量超过了千万量级时,我们会按照单个id进行拆分,比如用户信息表,我们会按照uid进行拆分。比如一些商品表,既可以按照用户来查询,又可以按照整个人来查询,这时候怎么办呢?

  

  首先对整个表进行分表操作,infoid包含uid的信息,对infoid进行水平拆分,既可以按照uid查询,又可以按照infoid查询,可以很好地满足商品信息表的需求,整体思路还是按照水平拆分的方式。

  

  起初,我们做了IM离线消息集合结构,也就是说,当某人不在线时,我可以把消息先存起来,待他上线时,再把整个消息拉过去。在这个过程中考虑到发生物理删除时,更新压力会比较大,我们采用逻辑更新,在表中设置flag字段,flag为0,表示消息未读,flag为1表示消息已读。批量删除已经读取的离线消息,可以直接采用MongoDB的删除命令,非常简单。但当数据量比较大时,比如达到5KW条时,就没那么简单了,因为flag没有索引,我们晚上20点开始部署删除,一直到凌晨依然没有删除完毕,整个过程报警不断,集群的服务质量大幅下降。

  

  原因很简单,因为你要进行删除,实际上就是做了一个全表扫描,扫描以后会把大量冷数据交换到内存,造成内存里全都是冷数据。当数据高峰期上来以后,一定会造成服务能力急剧下降。怎么解决呢?

  

  首先把正在进行的opid kill掉,至少先让它恢复正常,另外可以在业务方面做优化,最好在用户读完以后,直接把整个逻辑删除掉就OK了。其次,对删除脚本进行优化,以前我们用flag删除时,既没有主键也没有索引,我们每天定期从从库把需要删除的数据导出来,转换成对应的主键来做删除,并且通过脚本控制整个删除速度,整个删除就比较可控了。

  

  另外我们发现,一旦大量删除数据,MongoDB会存在大量的数据空间,这些空洞数据同时也会加载到内存中,导致内存有效负荷低,数据不断swap,导致MongoDB数据库性能并没有明显的提升。

  

客户端映射内存(客户端映射内存不足)-第1张图片-亚星国际官网

  这时的解决方案其实很容易想到,MongoDB数据空间的分配以DB为单位,本身提供了在线收缩功能,不以Collection为单位,整个收缩效率并不是很好,因为是online收缩,又会对在线服务造成影响,这时可以采取线下的解决方案。

  

  方案二,收缩数据库,把已有的空洞数据remove掉,重新生成一份空洞数据。先预热从库,把预热从库提升为主库,把之前主库的数据全部删除,重新同步数据,同步完成后,预热此库,把此库提升为主库,完全没有碎片,收缩率达到100%。但这种方式持续时间长,投入维护成本高,如果只有2个副本的情况下, 收缩过程单点存在一定风险。

  

  这时在线上做对比,我们发现,收缩前大概是85G的数据量,收缩之后是30G,大概节省了50G的存储量,整个收缩效果还是蛮好的,通过这种方式来做还是比较好的。

  

  另外讲一下MongoDB的监控,MongoDB提供了很多监控工具,包括mongosniff,mongostat,mongotop,以及命令行监控,还有第三方监控。我们自己怎么做呢?

  

  我们针对MongoDB本身的性能情况,用的比较多的是mongostat,可以反映出整个服务的负载情况,比如insert,query,update以及delete,通过这些数据可以反映出MongoDB的整体性能情况。

  

客户端映射内存(客户端映射内存不足)-第1张图片-亚星国际官网

  这其中有些字段比较重要,locked表示加锁时间占操作时间的百分比,faults表示缺页中断数量,miss代表索引miss的数量,还包括客户端查询排队长度,当前连接数,活跃客户端数量,以及当前时间,都可以通过字段反映出来。

客户端映射内存(客户端映射内存不足)-第1张图片-亚星国际官网

  

  根据经验来说,locked、faults、miss、qr/qw,这些字段的值越小越好,最好都为0,locked最好不要超过10%,faults和miss的原因可能是因为内存不够,内冷数据或索引设置不合理,qr|qw堆积会造成数据库处理慢。

  

  Web自带的控制台监控和MongoDB服务一同开启,可以监控当前MongoDB所有的连接数,各个数据库和collection的访问统计,包括Reads,Writes,Queries等,写锁的状态以及最新的几百行日志文件。

  

  官方2011年发布了MMS监控,MMS属于云监控服务,可视化图形监控。在MMS服务器上配置需要监控的MongoDB信息(ip/port/user/passwd等),在一台能够访问你的MongoDB服务的内网机器上运行其提供的Agent脚本,Agent脚本从MMS服务器获取到你配置的MongoDB信息,Agent脚本连接到相应的MongoDB获取必要的监控数据,Agent脚本将监控数据上传到MMS的服务器,登录MMS网站查看整理过后的监控数据图表。

  

  除此之外,还有第三方监控,因为MongoDB的开源爱好者对它的支持比较多,所以会在常用监控框架上做一些扩展。

  

  以上是MongoDB在58同城的使用情况,包括使用MongoDB的原因,以及针对不同的业务场景如何设计库和表,数据量增大和业务并发时遇到的典型问题及解决方案。

  今天的分享到此结束,谢谢大家!

标签: 客户端映射内存

发表评论 (已有5条评论)

评论列表

2024-12-26 18:35:50

id的信息,对infoid进行水平拆分,既可以按照uid查询,又可以按照infoid查询,可以很好地满足商品信息表的需求,整体思路还是按照水平拆分的方式。     起初,我们做了IM离线消息集合结构,也就是说,当某人不在线时,我可以把消息先存起来,待他上线时,再把整个消息拉过

2024-12-26 21:24:00

。一方面可以减小整个存储空间,另一方面,虽说_id可以在MongoDB服务端生成,但我们尽可能把_id生成工作放在业务层或应用层,可以减少MongoDB在服务端生成

2024-12-27 02:45:05

控,还有第三方监控。我们自己怎么做呢?     我们针对MongoDB本身的性能情况,用的比较多的是mongostat,可以反映出整个服务的负载情况,比如insert,query,update以及delete,通过这些数

2024-12-27 04:51:05

一直到凌晨依然没有删除完毕,整个过程报警不断,集群的服务质量大幅下降。     原因很简单,因为你要进行删除,实际上就是做了一个全表扫描,扫描以后会把大量冷数据交换到内存,造成内存里全都是冷数据。当数据高峰期上来以后,一定会造成服务能力急剧下降。怎么解决

2024-12-27 01:24:04

没有schema的概念,存储的自由度大了,但整个存储空间会带来一些额外开销,毕竟要存储字段名,value值无法改变,但可以减少字段名的长度,比如age可以考虑用A表示。