MongoDB报表实例方案选型

MongoDB报表实例方案选型python


背景介绍git


在咱们的生产环境使用的是复制集,为了将数据库服务器的业务压力分摊,咱们将数据库拆分到了不一样的复制集上运行。github


咱们在MongoDB复制集上运行应用程序,有时候有报表需求,常规用途是得到用户行为的分析,还有其余商业定制指标数据;有搜索引擎的查询需求,使用Solr从oplog.rs获取增量数据更新产品信息的索引。sql


这些报表查询和搜索引擎的查询需求,尽可能不能影响到线上的业务正常运行,所以不能直接在生产数据库上运行报表。通过开发和运维讨论以后,在项目成立之初,计划隔断报表任务以至不会影响到生产任务。mongodb


来谈谈混淆生产和报表的问题shell


工做集(workingset),是MongoDB在任何的时间间隔读取和写入的整个数据库的一个子集。生产环境中的活跃用户操做文档数据,操做系统将它们保持在物理内存中。数据库


注意:不要让你的工做集增加大过内存!可使用MongoDB的监控服务Cloud Manager监控你的实例。若是确实出现这个问题,你须要分片,所以容量规划是很重要的,能够做为独立的专题来说。即便数据库大小是可用内存的数百或数千倍,若是你在前期合理规划了架构并优化了索引,MongoDB一样也能高效运行。在工做集外的数据会保持和磁盘上一致,当用户空闲时,他们操做的文档将会再也不使用,所占用的内存用于新的活跃用户的内存请求。json


报表应用会查询大量的数据,通常不会重复访问相同的数据,每一个报表应用可能彻底访问不一样的数据集合。这意味着须要持续提供内存给新的文档读取请求。若是你将报表应用和生产应用放在相同的实例上运行,报表应用将会与你的生产应用争夺内存,持续不断地请求活跃用户的数据,而你的生产应用持续不断的加载它(getmore)。那么数据库服务器性能将发生波动。ruby


报表应用,将会有大量count、aggregate、mapReduce等聚合操做,这些操做对于MongoDB来讲效率不高,所以将它与生产任务分开是一个好的作法。服务器


使用专属报表实例的复制集


MongoDB复制集具备在线持久性,经过复制数据到一个集合中的全部节点,并对客户端提供无缝的故障转移。包含一个主节点提供写,而剩下的是只读副本。当条件须要的时候选举决定哪一个节点是主。复制集应该包含一个奇数成员帮助快速选举。


判断不可达的机器是否宕机基本上没法判断,有可能被网络被分区了。所以若是复制集中的大多数节点下线了(也就是说,3个成员中的2个下线),即便一个健康的主节点保留,它会降级为一个只读的副本。不这么作可能致使多个机器在一个网络分区的状况下定义它们本身为主节点,出现多个主节点,致使可怕的数据不一致。


所以一个复制集包含至少3个成员,提供一个机器失败的错误容忍。


在MongoDB官方的文档中,推荐限制报表查询到专属节点。报表基本不须要写操做,而是统计最终一致性数据。若是提取的数据有秒级或分级延时,每日的报表是不容许的。若是你的计数统计丢失了一些操做,这将致使报表数据不许确。


你能够在MongoDB复制集环境构建专属的报表节点,方案有隐藏的复制集成员hidden member或者读偏好read preference设置相关的标签集合tag sets。第一种方法更简单,第二种方法更灵活。


下图是使用专属节点提供报表需求的架构图:

clip_p_w_picpath002



隐藏成员方案


参考:https://docs.mongodb.com/manual/tutorial/configure-a-hidden-replica-set-member/


隐藏成员是复制集的一部分,可是不能成为主,而且对客户端应用程序不可见。隐藏成员能够在选举中投票。


一个复制集的隐藏成员被配置为priority: 0,是为了阻止它们被选举为主。设置hidden: true,即便他们指定了一个读偏好为secondary,也会阻止客户端链接到复制集路由读操做到它。


从一个隐藏成员读数据,你只能经过直连该隐藏成员访问,并指定slave_ok,而不能经过MongoReplicaSetClient类。


隐藏成员设置


你可使用mongo shell来隐藏一个存在复制集的成员:

$ mongo admin -uxucy -p
PRIMARY> conf = rs.config()
{ "_id" : "test", "version" : 21, "members" : [ { "_id" : 0, "host" : "xucy.local:27017", }, { "_id" : 1, "host" : "xucy.local:28017", }, { "_id" : 2, "host" : "xucy.local:29017", } ] }
PRIMARY> conf.members[1].priority = 0
PRIMARY> conf.members[1].hidden = true
PRIMARY> conf.version += 1
PRIMARY> rs.reconfig(conf)


xucy.local:28017如今隐藏了,它将继续复制操做和像往常同样在选举中投票,可是链接到复制集的客户端将不会从它读取,即便xucy.local:29017下线。


Ruby版的报表应用链接代码示例:

require 'mongo'
reporting = Mongo::MongoClient.new("xucy.local", "28017", slave_ok: true)
reporting['my_application']['users'].aggregate(...)


限制说明


使用隐藏的成员是一个最简单的方式,配置实例用于专属的工做负载,像报表和搜索引擎访问,然而使用上有一些限制须要说明的。


隐藏成员不能在紧急状况下读取


带有2个普通和1个隐藏成员在一个复制集中,对于写的错误容忍等价于一个常规的3个成员的集合。然而,你失去两个节点,你的生产应用将不能优雅的降级到只读模式,由于你的隐藏成员将不容许复制集客户端读取。若是你只是喜欢隐藏成员访问简单,土豪方案是使用一个5成员(带有一个隐藏成员)的复制集。


对于复制集的包装代码不能被使用


不少团队建立应用定制的包装代码时,使用MongoDB驱动提供的复制集链接访问方法,添加复制集链接的基本信息给客户端。由于你须要使用独立链接到你的报表实例,你不能重用它。



标签成员方案


参考:https://docs.mongodb.com/manual/tutorial/configure-replica-set-tag-sets/


标签成员,更加复杂,可是,是更灵活的方法,用于路由报表查询到一个专属节点去使用标签和读偏好。


设置一个成员为priority: 0,阻止它被选举为主,可是不设置它为隐藏,分配一个标签use: reporting:

PRIMARY> conf = rs.config()
{ "_id" : "test", "version" : 21, "members" : [ { "_id" : 0, "host" : "xucy.local:27017", }, { "_id" : 1, "host" : "xucy.local:28017", }, { "_id" : 2, "host" : "xucy.local:29017", } ] }
PRIMARY> conf.members[1].priority = 0
PRIMARY> conf.members[1].tags = { "use": "reporting" }
PRIMARY> conf.version += 1
PRIMARY> rs.reconfig(conf)


在这种状况下,xucy.local:28017毫不会成为主。然而,当其余两个机器变得不可达,你的应用还能处理读请求到报表服务器。它会继续运行,不会致使你的报表应用在这样一个事件期间暂停。


Python版报表应用链接代码示例:

from pymongo import MongoReplicaSetClient
from pymongo.read_preferences import ReadPreference
rep_set = MongoReplicaSetClient( 'xucy.local:27017,xucy.local:28017,xucy.local:29017', replicaSet = 'test', read_preference = ReadPreference.SECONDARY, tag_sets = [{'use':'reporting'}] )
rep_set.my_application.users.aggregate(...)


对于报表应用来讲,在主可用的状况下,确保尽可能不要在剩下的惟一的辅助成员上运行报表应用,由于这样将报表和生产混合在一块儿了。


以上只发送报表查询到标记有use: reporting的辅助成员,而且若是没有可用的主,咱们应该从根本上阻止继续运行。在实践中,若是你发现没有主,你应该抛出异常并在你的扩展代码中处理它们。还要作好状态的监控,如:reporting_system.ok()。当发现异常时进行分支处理。


益处和考虑


使用标签和读偏好相对隐藏成员来讲,带来必定的灵活性。


容易添加报表实例


由于你的链接代码是可定义的,而不是指定到一个专门的主机。你能够添加更多节点为报表实例,只须要添加并标记他们,像这样:

PRIMARY> rs.add({_id:3, host:"xucy.local:30017", priority:0, tags:{'use':'reporting'}})


你原来的代码将会利用到新的报表实例,而且复制集将继续运行,不用触发选举和从客户端断开链接。


报表实例能够被跳过或删除


当你想将目前使用的报表实例提供给其余应用时,报表标记在必要时能够被移动,或者移除。像这样的一个从新配置将会触发选举,并重连全部客户端,这是能够接受的。注意:这是一个逆向的方法,经过增长经常使用生产可用实例,分发生产读到副本成员。


一些驱动须要手工同步


检查你的驱动文档,例如,Ruby驱动(像1.9.2),不会刷新副本集的视图,除非客户端像这样使用refresh_mode: :sync显式初始化。



Solr生成全文索引


MongoDB复制集配置简单、容易上手是我喜欢MongoDB的缘由之一。对于MongoDB报表实例,不管你使用隐藏成员仍是标签成员,开发和部署都很是简单。咱们在生产环境也将报表实例用于Solr生成全文索引。


Solr是一个独立的企业级搜索应用服务器,它对外提供相似于Web-service的API接口。用户能够经过http请求,向搜索引擎服务器提交必定格式的XML文件,生成索引;也能够经过Http Get操做提出查找请求,并获得XML格式的返回结果。


读取报表实例的方案选型


期初的方案是使用mongo-connector集成MongoDB到Solr实现增量索引。(http://ultrasql.blog.51cto.com/9591438/1696083/


mongo-connctor是一款用于同步MongoDB数据到其余系统组件,好比它能同步数据到Solr、ElasticSearch或者其余MongoDB集群中去。它的实现原理是依据MongoDB的Replica Set复制模式,经过分析oplog日志文件达到最终的同步目的。安装配置启动过程可参考 官方文档


因为是单进程版本的,效率对咱们当时来讲不高,若是能改为利用多核性能的话可能好些。


MongoDB Tailable Cursors


MongoDB 有一个叫 Tailable Cursors的特性,它相似于tail -f 命令,你在一个Capped Collection上面执行查询操做,当操做完成后,你能够不关闭返回的数据Cursor,并持续地从中读出新加入的数据。


在高写入的Capped Collection上,索引不可用时,可以使用Tailable Cursors。例如,MongoDB复制使用了Tailable Cursors来获取Primary的尾oplog日志。


考虑如下与Tailable Cursors相关的行为:

  • Tailable Cursors不使用索引,并以天然排序返回文档。

  • 由于Tailable Cursors不使用索引,查询的初始扫描很是耗性能;可是,游标初始化完后,随后获取到的新增长的文档是很快速的。

  • Tailable Cursors若是遇到如下状况之一将会僵死或无效:

    • 查询无匹配结果。

    • 游标在集合尾部返回文档,随后应用程序删除了该文档。

僵死的游标id为0。


DBQuery.Option.awaitData


在使用TailableCursor时,此参数会在数据读尽时先阻塞一小段时间后再读取一次并进行返回。


跟踪oplog的示例:

use local
var cursor = db.oplog.rs.find({"op" : "u", "ns" : "MyDB.Product"},{"ts": 1, "o2._id": 1}).addOption(DBQuery.Option.tailable).addOption(DBQuery.Option.awaitData);
while(cursor.hasNext()){
var doc = cursor.next();
printjson(doc);
};


2.6版的游标方法:

cursor.addOption()

https://docs.mongodb.com/v2.6/reference/method/cursor.addOption/


3.2版的游标方法:

cursor.tailable()

https://docs.mongodb.com/manual/reference/method/cursor.tailable/


咱们根据该特性,开发了Java应用程序,先初始化全量同步数据到Solr生成索引、记录同步时间。而后经过Tailable Cursors读取oplog.rs对比上次记录的同步时间,若是是新的变动,经过新的进程异步获取日志里记录的最新数据更新到Solr的文档里。

clip_p_w_picpath004

相关文章
相关标签/搜索