MongoDB 介绍与应用详解


作者 | 山寨植树


企鹅杏仁架构师,80 后技术人。虽然不是大牛,但是也对 MongoDB 有过应用,曾经利用MongoDB 作为金融预测模型的数据持久化方案,对此类技术应用有一定的了解。本文参考了大量理论资料、实际案例,结合以往应用经验,希望对读者老爷们有所裨益。


一、MongoDB初步介绍

MongoDB是一个基于文档型数据库,MongoDB中文档数据,使用BSON(一种和JSON类似的)东西作为数据格式。

使用文档作为数据格式有如下好处

  • 数据结构符合大部分程序语言。

  • 可在文档内嵌入子文档(相当于传统数据库的嵌套表)。

  • 动态增减文档或修改文档格式。

二、MongoDB关键特性

  • 高性能:支持使用嵌入数据时,减少系统I/O负担,支持子文档查询

  • 多种查询类型支持,且支持数据聚合查询、文本检索、地址位置查询

  • 高可用、水平扩展:支持副本集与分片

  • 多种存储引擎:WiredTiger , In-Memory

以上来自 https://docs.mongodb.com/manual/introduction/

三、InfoQ上,关于MongoDB应用的访谈摘录

受访者 黄翀,东方航空 PSS 部门架构师。作为工程师先后供职于 PCCW,Sinolife 等公司,有着超过 10 年开发经验,熟悉开源产品如 Spark,MongoDB,Drools 等。

InfoQ:东方航空开始使用 MongoDB,您提到,目前只在一个项目中进行了使用。能否详细介绍下这个项目的背景呢?该项目的配置如何?其数据库有哪些特点,所接受的访问量是什么数量级?

黄翀:该项目是东航 Shopping 项目,为用户提供个性化的航班搜索服务,支持多目的地搜索、基于预算范围的搜索、城市主题的搜索、灵感语义的搜索、实时的低价日历搜索等。同时,灵活组合中转路径,提高 OD 航线覆盖率。

上述的项目背景所带来的技术挑战是:

一次前端搜索,所带来的后端的库存和运价搜索复杂度是原来的几十倍或者上百倍。因此,该项目对系统的性能要求更高。

该项目的配置:采用 3 台 64 核 128G memory ssd 硬盘。

生产环境:3 台服务器组 MongoDB Replica-set 集群。

访问量: 目前推广上线了部分渠道,旅客的查询量每日 600 万次 +,转换成数据库的查询量每日 4500 万次 +。

数据库总数据条数:720 亿条,每日更新次数 2600 万次 +,99% 以上查询效率低于 200ms。

InfoQ:为什么选择在该项目中用 MongoDB?为了保证提供正常或更好的服务,东航从哪些方面进行了评估?具体的评估过程、数据、结果如何?


黄翀:该项目的特点是:高负载高并发。因此,我们在选型数据库时,主要参考技术标准如下:

支持基于内存查询的数据库,减少磁盘 IO 交互。

支持复杂多变数据存储结构与类型。

支持集群架构保证高可用。

支持复杂的 SQL 查询,例如基于预算,来搜索满足条件的航班;或者基于航班时刻(上下午),来搜索满足条件的航班等。

综合以上标准进行评估,最后我们选择了 MongoDB。

项目开始之初就依次评估过 Greenplum,Oracle,MySQL,MongoDB 等技术,有 RDBMS(ACID),有 Nosql(cap);既考虑 key-value 型,也考虑要支持范围复杂搜索。既考虑到数据总量对性能影响,也考虑到我们是查询性能要求高,写入性能要求不是不高但是次要。既考虑到实际开发难度学习曲线,也考虑到运维成本。

最后,我们又比较了性能、开发难易程度等诸多因素,最后选择了 MongoDB。

InfoQ:Spark 和 MongoDB 的结合,是否完美匹配了东航的需求?在实时性方面,这样的结合有什么优势?在目前的使用过程中表现如何,有没有体现出优势或者暴露出不足?

黄翀:Spark 作为一个基于内存计算的引擎目前已进入东航的视野,我们进行了 POC 测试及使用场景验证,证明其对提高我们性能是有帮助的,下一步我们会考虑在生产上应用 Spark+MongoDB 的技术。

我们在运价计算场景验证了这项技术的优势。本来每次请求都要计算运价,现在我们将预先计算好的结果都存入 MongoDB,将计算性能问题转化为查询性能问题。基于我们在 MongoDB 在优化查询方面的经验,简化了问题本身。

优势显而易见,不足在于 Spark 的 map 方法不适应一些自编程 map,造成一些复杂中转程运价的计算难于在 Spark 中实现。

MongoDB 是操作性功能完备数据存储方案,能覆盖众多场景。在云中部署,结合 Docker 等一众运维工具,会提高运维效率,节约运维成本。

以上来自 https://www.infoq.cn/article/2016/09/mongodb-china-eastern-airlines

四、MongoDB VS HBASE

1、数据库

Mongodb bson文档型数据库,整个数据都存在磁盘中

hbase是列式数据库,集群部署时每个familycolumn保存在单独的hdfs文件中。

二级索引:Mongodb支持二级索引,而hbase本身不支持二级索引。

查询功能:

Mongodb支持集合查找,正则查找,范围查找,支持skip和limit等等,是最像mysql的nosql数据库,

而hbase只支持三种查找:通过单个row key访问,通过row key的range,全表扫描

更新方式:

mongodb的update是update-in-place,也就是原地更新,除非原地容纳不下更新后的数据记录。可动态增减字段,并修改原有单条记录的字段类型

hbase的修改和添加都是同一个命令:put,如果put传入的row key已经存在就更新原记录,实际上hbase内部也不是更新,

它只是将这一份数据已不同的版本保存下来而已,hbase默认的保存版本的历史数量是3。且不可增减字段

对MapReduce的支持:

mongodb和hbase都支持mapreduce,不过mongodb的mapreduce支持不够强大,如果没有使用mongodb分片,mapreduce实际上不是并行执行的

mongodb同时提供map-reduce的轻量方式,aggregation,参考 https://docs.mongodb.com/manual/aggregation/

负载均衡:

mongodb支持shard分片,hbase根据row key自动负载均衡,这里shard key和row key的选取尽量用非递增的字段,尽量用分布均衡的字段

读写效率不同:

mongodb的读效率比写高,hbase默认适合写多读少的情况

思路不同: 

hbase采用的LSM思想(Log-Structured Merge-Tree),就是将对数据的更改hold在内存中,达到指定的threadhold后将该批更改merge后批量写入到磁盘,这样将单个写变成了批量写,大大提高了写入速度,不过这样的话读的时候就费劲了,需要merge disk上的数据和memory中的修改数据,这显然降低了读的性能。

mongodb采用的是mapfile+Journal思想,如果记录不在内存,先加载到内存,然后在内存中更改后记录日志,然后隔一段时间批量的写入data文件,这样对内存的要求较高,至少需要容纳下热点数据和索引。

五、MongoDB的先天缺陷

  • 不支持事务(4.0之前的版本不支持事务,4.0开始支持在副本集上的事务,官方宣称会在4.2版本,支持在分片集上实现事务)

  • 写入性能差,2.4开始的版本,默认是安全写入,确保不丢失数据,之前版本写入处理方式同redis(当然自从有了SSD,大家懂的)

  • 磁盘、内存消耗较大

  • 部署副本集,至少需要三个节点,其必须有个裁判节点

  • 使用分片时,单个分片必须有副本集,否则一旦分片损坏,会引起数据丢失

  • 国内生态不佳,熟悉者较少,大牛更少

六、什么场景下该应用用MongoDB

  • 弱一致性场合

  • 读取频繁的场合

  • 数据量较大,必须分片的场合

  • 不规则数据,且频繁更新数据类型的场合

  • 需要可以动态追加数据字段或修改数据字段类型的场合

注意:在选择NOSQL方案时,尽可能抛弃技术栈洁癖,基于C/C++栈开发的成熟基础设施在内存管理和可靠性上有着天然优势。

七、MongoDB应用场景举例

MongoDB在国内的经典案例,有两个比较典型:1.视觉中国的图片与数据存储应用。2.上文提到的东方航空的运价查询系统。

这里例举一下东方航空的运价查询系统。

东方航空的挑战

东方航空作为国内的3大行之一,每天有1000多个航班,服务26万多乘客。过去,顾客在网站上订购机票,平均资料库查询200次就会下单订购机票,但是现在平均要查询1.2万次才会发生一次订购行为,同样的订单量,查询量却成长百倍。按照50%直销率这个目标计算,东航的运价系统要支持每天16亿的运价请求。

思路:空间换时间

当前的运价是通过实时计算的,按照当时的计算能力,需要对已有系统进行100多倍的扩容。另一个常用的思路,就是采用空间换时间的方式。与其对每一次的运价请求进行耗时300ms的运算,不如事先把所有可能的票价查询组合穷举出来并进行批量计算,然后把结果存入MongoDB里面。当需要查询运价时,直接按照 出发+目的地+日期的方式做一个快速的DB查询,响应时间应该可以做到几十毫秒。

那为什么要用MongoDB?因为我们要处理的数据量庞大无比。按照1000多个航班,365天,26个仓位,100多渠道以及数个不同的航程类型,我们要实时存取的运价记录有数十亿条之多。这个已经远远超出常规RDBMS可以承受的范围。

MongoDB基于内存缓存的数据管理方式决定了对并发读写的响应可以做到很低延迟,水平扩展的方式可以通过多台节点同时并发处理海量请求。
事实上,全球最大的航空分销商,管理者全世界95%航空库存的Amadeus也正是使用MongoDB作为其1000多亿运价缓存的存储方案。

Spark + MongoDB 方案

MongoDB可以用来做我们海量运价数据的存储方案,在大规模并行计算方案上,此案例中用到了Spark技术。

关于Spark,看这儿 http://spark.apache.org/,虽然他不是流式计算引擎,却可以解决不同持久化方案之间数据计算、存储、迁移的问题

我们知道MongoDB可以用来做我们海量运价数据的存储方案,在大规模并行计算方案上,就可以用到崭新的Spark技术。

这里是一个运价系统的架构图。

左边是发起航班查询请求的客户端,首先会有API服务器进行预处理。

一般航班请求会分为库存查询和运价查询。

库存查询会直接到东航已有的库存系统(Seat Inventory),同样是实现在MongoDB上面的。

在确定库存后根据库存结果再从Fare Cache系统内查询相应的运价。

Spark集群则是另外一套计算集群,通过Spark MongoDB连接套件和MongoDB Fare Cache集群连接。

Spark 计算任务会定期触发(如每天一次或者每4小时一次),这个任务会对所有的可能的运价组合进行全量计算,然后存入MongoDB,以供查询使用。

右半边则把原来实时运算的集群换成了Spark+MongoDB。Spark负责批量计算一年内所有航班所有仓位的所有价格,并以高并发的形式存储到MongoDB里面。

每秒钟处理的运价可以达到数万条。
当来自客户端的运价查询达到服务端以后,服务端直接就向MongoDB发出按照日期,出发到达机场为条件的mongo查询。

批处理计算流程


这里是Spark计算任务的流程图。需要计算的任务,也就是所有日期航班仓位的组合,事先已经存放到MongoDB里面。
任务递交到master,然后预先加载所需参考数据,broadcast就是把这些在内存里的数据复制到每一个Spark计算节点的JVM,然后所有计算节点多线程并发执行,从Mongodb里取出需要计算的仓位,调用东航自己的运价逻辑,得出结果以后,并保存回MongoDB。

处理能力和响应时间比较

这里是一个在东航POC的简单测试结果。从吞吐量的角度,新的API服务器单节点就可以处理3400个并发的运价请求。在显著提高了并发的同时,响应延迟则降低了10几倍,平均10ms就可以返回运价结果。按照这个性能,6台 API服务器就可以应付将来每天16亿的运价查询。


八、关于MongoDB的存储引擎

聊到数据库,不得不聊一下相关的存储MongoDB在发展过程中,使用过几种不同的存储引擎

  • MMAP

  • MMAPv1

  • WiredTiger

  • In-Memory

好奇的人一定会问,MongoDB目前使用的是哪一种存储引擎呢?其实在mongodb 2.4~2.6的年代,当时的存储引擎默认是

MMAP, WiredTiger尚未发布。在3.0之后的版本,存储引擎,默认使用WiredTiger,并且MMAP升级到了MMAP-V1,

4.0之后,MMAPv1已经被宣布废弃,In-Memory是纯内存型数据库付费企业用户才能使用的。


具体的不同可以参考下图

此图来源于网上,比较旧,但是描述基本准确,并且,在4.0之后,官方也曾泄漏出,有支持HDFS等存储引擎的计划。

已知的存储引擎在使用上有何不同呢?

MMAP/MMAPv1 存储引擎它是基于内存映射文件来实现的,它擅长使用大容量插入,读取,更新工作负载,是出了名的耗内存,占资源,它会自动占有机器的全部可用内存来缓存数据,但是这个过程是动态的,当其它的进程要使用内存的时候,MMAPV1也会把Cache的内存分配给其它的进程 ,它采用的是操作系统的虚拟内存系统来管理自己的内存的。

早期使用过MongoDB的同学都知道,MongoDB的32位版本,基本是聊胜于无,64位版本,装有MongoDB实例的主机,所有可用内存都会被MongoDB占用,用于存放缓存数据,同时缓存被载入内存,是个渐进过程,这给早期的运维和架构同学造成困扰,因为这样使数据库的运维显得怪异,但是这些特性也使数据库服务提升了数百倍甚至千倍的性能,因为当所有查询都使用内存数据时,大部分检索都是在内存中进行,速度就远远胜于扫描磁盘数据了。SSD磁盘普及前,这个特性解救了许多受性能困扰的设计者。

能找到的内部内部原理图是这样

实际上每个数据库,会根据NS,再进行划分,都划分了不同的范围,并预留了可用空间。将这些文件映射入内存后,

内存空间消耗自然有别于Mysql,Oracle之类的传统数据库,而这些文件本身,即映射了内寸中的数据结构

到了3.0之后,WiredTiger引擎开始,官方是这么描述的

  • 文档级别的并发控制-乐观锁机制

  • 检查点(Checkpoint)

  • 预先记录日志

  • WiredTiger 利用系统内存资源缓存两部分数据

  • 调整WiredTiger内部缓存的大小

  • 数据压缩

  • Disk空间回收

其中文档级别的锁、DISK空间回收,可调整内存缓存大小、数据压缩,是WirdTiger相当重要的特性。

MMAPv1的时代,锁是集合级别的,相当于对单个数据库进行锁这样的话,就带来了问题,实际场景,基本都是对文档的操作,

因此使用集合层面的锁,并不满足实际需求,DISK空间回收基本是痴人说明,因为数据的存储空间都是预分配的,一但分配完毕,

以往的存储引擎并没有提供回收机制。并且,数据落盘时,也不进行压缩,而且新的存储引擎,支持数据压缩和空间回收的话

可以大大减少运维压力

关于In-Memoroy,这个能找到的资料并不多,因为国内使用MongoDB商业服务的厂商也是屈指可数。找到如下关于In—Memory的介绍

  • 使用的是文件级别的并发控制同一个时间多个写操作能够同时操作一个集合中的不同文档,当多个写操作修改同一个文件的时候,必须以序列化的方式进行,也就是说文档在被修改的时候,其它的操作只能等待。

  • 将Data,Index,Oplog等存储到内存中,通过参数 --InMemorySizeDb设置占用的 内存量,默认是50%,单位是GB。

  • 不会持久化数据的存储,数据只保存在内存当中,读写操作都在内存中完成,不会把数据写到数据文件,如果Mongo停止或者关机,内存中的数据全部丢失。

  • 虽然不做持久化的操作,但是它会记录Oplog,该Oplog是存储在内存中的集合,MongoDb通过replication将Primary成员中的Oplog推送到其它的副本集中,这样在其它的副本中就可以重做Oplog中记录的操作,从而将Primary中的数据持久化存储。

九、关于集群和分布式

MongoDB的主要集群方式

  • 副本集

  • 分片集

关于副本集,一个复制集至少需要这几个成员:一个 主节点 ,一个 从节点 ,和一个 投票节点 。但是在大多数情况

请抛弃传统上使用MySql,Oracle等数据库时,送形成的master slaver概念。MongoDB的副本集并不是Master Slaver,

因为主节点故障时,可由投票产生新的主节点,并且,部署时可以给节点设置不同权重

实际上超大量的数据,必然会使用分片集,因为副本集要支撑亿级别以上数据的高频率查询也是有难度的,因为写入后的数据需要同步到副本集,而每个副本集是一个近似于全量的备份,这种显然是不合适的,MongoDB的分片方案,解决了这个文同。

MongoDB的分片集,只要选择好合理的分片健,同时运维上避免数据块在实例间的频繁整理移动,即可成为一个相易用的分片方案。

因为目前MySQL在分片集群上,是弱项,白皮书上的方案,基本脱离实际方案,而开源方案也有应用场景限制,而优秀的收费方案也并不是铺天盖地,使用商业数据库的,还需要考虑业务方是否能负担相应的成本。

十、写在结尾

以上是关于MongoDB的一点点案例介绍,在应用场景上非常典型。各位在做基础设施选型时,应对大查询量,且偏向数据库特性的方案是,

MongoDB是一个较优秀的选项,但是对于事务有高度需求的,请勿使用,数据量、数据增量不大的,请谨慎使用。

以上内容资料参考

  • Spark Mongo:https://docs.mongodb.com/spark-connector/master/

  • 东航航班查询解决方案:

    http://www.mongoing.com/tj/mongodb_shanghai_spark

  • MongoDB手册:https://docs.mongodb.com/manual/introduction/

  • MongoDB Aggregation:

        https://docs.mongodb.com/manual/aggregation/

  • MongoDB存储引擎介绍 https://zhuanlan.zhihu.com/p/58706342

  • MongoDB WiredTiger

    https://docs.mongodb.com/manual/core/wiredtiger/




全文完



以下文章您可能也会感兴趣:




我们正在招聘 Java 工程师,欢迎有兴趣的同学投递简历到 rd-hr@xingren.com 。




杏仁技术站

长按左侧二维码关注我们,这里有一群热血青年期待着与您相会。




评论