MongoDB报表示例方案选择
背景介绍
在我们的生产环境中,我们使用复制集。为了分担数据库服务器的业务压力,我们将数据库分割成不同的复制集。
我们在MongoDB复制集上运行应用程序,有时我们需要报告。一般目的是获取用户行为分析,还有其他商业定制的指标数据。借助搜索引擎的查询需求,使用Solr从oplog.rs中获取增量数据,更新产品信息的索引。
这些报表查询和搜索引擎的查询需求尽量不影响网上业务的正常运行,所以不能直接在生产数据库上运行报表。经过开发和运维方面的讨论,在项目初期,为了不影响生产任务,计划砍掉报表任务。
我们来谈谈把产生和语句混淆的问题
Workingset是MongoDB在任何时间间隔读写的整个数据库的子集。生产环境中的活动用户 *** 作文档数据, *** 作系统将它们保存在物理内存中。
注意:不要让你的工作集增长超过内存!您可以使用MongoDB的监控服务云管理器来监控您的实例。如果确实出现了这个问题,你需要把它分成几块,所以容量规划很重要,可以是一个独立的话题。即使数据库大小是可用内存的几百倍或几千倍,如果你合理规划架构,前期优化索引,MongoDB也能高效运行。工作集之外的数据将与磁盘保持一致。当用户空空闲时,其 *** 作的文档将不再使用,占用的内存将用于新的活跃用户的内存请求。
报表应用会查询大量数据,一般不会重复访问相同的数据。每个报告应用程序可以完全访问不同数据集。这意味着需要不断地为新的文档读取请求提供内存。如果您在同一个实例上运行报表应用程序和生产应用程序,报表应用程序将与您的生产应用程序竞争内存,不断请求活动用户的数据,而您的生产应用程序将不断加载它(getmore)。数据库服务器性能会波动。
会有很多聚合 *** 作比如count,aggregate,mapReduce等。在报表应用程序中,这对于MongoDB来说效率很低,所以将它从生产任务中分离出来是一个很好实践。
使用独占报告实例的复制集
MongoDB复制集具有在线持久性,并通过将数据复制到集中的所有节点来为客户端提供无缝故障转移。包含一个用于写入的主节点,而其余节点是只读副本。当条件需要时,选择哪个节点为主节点。复制集应该包含奇数个成员以帮助快速选举。
基本无法判断无法到达的机器是否宕机,可能是被网络分区了。因此,如果复制集中的大多数节点脱机(即3个成员中有2个脱机),即使仍有一个健康的主节点,它也会降级为只读副本。如果做不到这一点,可能会导致多个机器在一个网络分区中定义自己为主节点,会出现多个主节点,造成可怕的数据不一致。
因此,一个复制集至少包含3个成员,为机器故障提供容错能力。
在MongoDB的官方文档中,建议将报表的查询限制在独占节点。报告基本不用写,只是统计最后的一致性数据。如果提取的数据有第二次或分层延迟,则不允许每日报告。如果在您的计数中丢失了一些 *** 作,这将导致报告数据不准确。
您可以在MongoDB复制集环境中构建一个专用的报表节点。该方案包括与读取偏好设置相关的隐藏复制集成员或标签集。第一种方法更简单,第二种方法更灵活。
下图是使用专用节点来提供报告要求的体系结构图:
隐藏成员方案
参考:https://docs.MongoDB.com/manual/tutorial/configure-a-hidden-replica-set-member/
隐藏成员是复制集的一部分,但它不能是主成员,并且对客户端应用程序不可见。隐藏成员可以在选举中投票。
复制集的隐藏成员配置为优先级:0,以防止它们被选为主要成员。Sethidden:true,即使它们将读取首选项指定为辅助,也会阻止客户端连接到复制集以向其路由读取 *** 作。
要从隐藏成员中读取数据,只能通过直接连接隐藏成员来访问,并指定slave_ok,而不能通过MongoReplicaSetClient类。
隐藏成员设置
您可以使用mongoshell隐藏带有复制集的成员:
$ 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/
成员更复杂,是将报告查询路由到专用节点以使用标签和阅读首选项的更灵活的方法。
将成员设置为优先级:0以防止其当选为主成员,但不要将其设置为隐藏。分配标签使用:报告:
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的辅助成员,如果没有可用的主,就要从根本上阻止它继续运行。实际 *** 作中,如果发现没有master,就应该抛出异常,并在扩展代码中处理。我们还要监控状态,比如:reporting_system.ok()。当发现异常时,执行分支处理。
好处和注意事项
与隐藏成员相比,使用标签和阅读首选项带来了一些灵活性。
易于添加报告实例
因为您的连接代码是可定义的,而不是分配给一个特殊的主机。您可以添加更多节点作为报表实例,只需添加并标记它们,如下所示:
PRIMARY> rs.add({_id:3, host:"xucy.local:30017", priority:0, tags:{'use':'reporting'}})您的原始代码将利用新的报表实例,并且复制集将继续运行,而不会触发选举和断开与客户端的连接。
可以跳过或删除报告实例
当您希望将当前使用的报表实例提供给其他应用程序时,可以在必要时移动或移除报表标记。像这样的重新配置将触发选举并重新连接所有客户端,这是可以接受的。注意:这是反向方法。通过增加共同生产的可用范例,我们分发生产成员阅读副本。
有些驱动程序需要手动同步
检查您的驱动程序文档,例如,Rubydriver(像1.9.2)不会刷新副本集的视图,除非客户端像这样用refresh_mode::sync显式初始化。
Solr生成全文索引
MongoDB复制集的简单配置和易用性是我喜欢MongoDB的原因之一。对于MongoDB报表实例,无论使用隐藏成员还是标记成员,开发和部署都非常简单。我们还使用Solr的报告实例在生产环境中生成全文索引。
Solr是一个独立的企业搜索应用服务器,它提供了一个类似于Web-service的API接口。用户可以通过http请求向搜索引擎服务器提交一定格式的XML文件,生成索引;还可以通过HttpGet *** 作进行搜索请求,得到XML格式的返回结果。
读取报表实例的方案选择
初步方案是使用mongo-connector将MongoDB集成到Solr中,实现增量索引。(http://ultrasql.blog.51cto.com/9591438/1696083/)
mongo-connector用于将MongoDB数据同步到其他系统组件,例如,它可以将数据同步到Solr、ElasticSearch或其他MongoDB集群。其实现原理是基于MongoDB的副本集复制模式,通过分析oplog日志文件来达到最终同步的目的。请参考安装启动过程的官方文档。
因为单进程版本的原因,当时对我们来说效率不高,如果能用多核性能代替可能会更好。
MongoDB可定制游标
MongoDB有一个特性叫做可定制游标,类似于tail-f命令。您对封闭的集合执行查询 *** 作。当 *** 作完成时,您可以继续从返回的数据游标中读出新添加的数据。
当索引在具有高写入的上限集合上不可用时,可以使用可定制游标。例如,MongoDB复制使用可定制的游标来获取主服务器的尾部 *** 作日志。
考虑以下与可定制游标相关的行为:
僵尸光标id为0。
DBQuery。Option.awaitData
使用TailableCursor时,该参数会在数据被读出时短时间阻塞数据,然后再次读取并返回。
跟踪 *** 作日志的示例:
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初始化全部的同步数据,以生成索引并记录同步时间。然后通过可定制的游标读取oplog.rs,比较最后一条记录的同步时间。如果是新的变更,通过新的进程异步获取日志中记录的最新数据,并更新到Solr文档中。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)