楼主: jieforest

MongoDb Architecture

[复制链接]
论坛徽章:
277
马上加薪
日期:2014-02-19 11:55:14马上有对象
日期:2014-02-19 11:55:14马上有钱
日期:2014-02-19 11:55:14马上有房
日期:2014-02-19 11:55:14马上有车
日期:2014-02-19 11:55:14马上有车
日期:2014-02-18 16:41:112014年新春福章
日期:2014-02-18 16:41:11版主9段
日期:2012-11-25 02:21:03ITPUB年度最佳版主
日期:2014-02-19 10:05:27现任管理团队成员
日期:2011-05-07 01:45:08
11#
 楼主| 发表于 2015-4-8 23:35 | 只看该作者
在关系型数据库中,可串行性(Serializability)是一个非常核心的概念。在并发执行时,多个线程对数据库的操作都好像是被按照顺序组织在了一个序列中一个一个的执行。所以每个客户端都可以认为自己是独占数据库的。底层数据库对这个功能的实现是靠锁机制或者多版本技术来提供这种隔离性的。但是这个概念在mongodb(包括很多其他NOSQL)是被无视的。

在mongodb中你读取的每条数据都只能当初他之前得一个快照,也就是说就在你对着你查询到的这条数据发呆的时候,它的本体也就是存在数据库中的样子已经被其他人修改过了。所以说,如果你想根据一条数据在数据库中本来大小的基础上修改它,当你提交修改请求的时候,你要修改的那条数据可能已经改变了。(比如你读到的是10,想加1,提交11的时候,可能它在数据库中已经被其它人改成12了)。如果对于你的应用程序来说这是不可以接受的,你不许在发送修改命令的时候再校验一次,看看是不是还是10。

使用道具 举报

回复
论坛徽章:
277
马上加薪
日期:2014-02-19 11:55:14马上有对象
日期:2014-02-19 11:55:14马上有钱
日期:2014-02-19 11:55:14马上有房
日期:2014-02-19 11:55:14马上有车
日期:2014-02-19 11:55:14马上有车
日期:2014-02-18 16:41:112014年新春福章
日期:2014-02-18 16:41:11版主9段
日期:2012-11-25 02:21:03ITPUB年度最佳版主
日期:2014-02-19 10:05:27现任管理团队成员
日期:2011-05-07 01:45:08
12#
 楼主| 发表于 2015-4-8 23:36 | 只看该作者
在这种模式下,当你方式修改请求的时候回额外发送一个状态用来被服务端校验,从而服务端在执行你的修改请求前会根据你提交的状态进行一次校验,通过了才会执行修改。当然,服务端进行校验修改的2次操作是具有原子性的。在mongodb中,你可以通过调用“findAndModify”方法完成这个操作。
  1. var account = db.bank.findone({id:1234})
  2. var old_bal = account['balance']
  3. var new_bal = old_bal + fund
  4. #状态校验会放到查询条件里面
  5. db.bank.findAndModify({id:1234, balance:old_bal}, {$set:{balance:new_bal}})
  6. #校验上次命令是否执行成功
  7. var success = db.runCommand({getlasterror:1,j:true})
  8. if(!success){
  9.     #重试
  10. }
复制代码

使用道具 举报

回复
论坛徽章:
277
马上加薪
日期:2014-02-19 11:55:14马上有对象
日期:2014-02-19 11:55:14马上有钱
日期:2014-02-19 11:55:14马上有房
日期:2014-02-19 11:55:14马上有车
日期:2014-02-19 11:55:14马上有车
日期:2014-02-18 16:41:112014年新春福章
日期:2014-02-18 16:41:11版主9段
日期:2012-11-25 02:21:03ITPUB年度最佳版主
日期:2014-02-19 10:05:27现任管理团队成员
日期:2011-05-07 01:45:08
13#
 楼主| 发表于 2015-4-8 23:36 | 只看该作者
事物这个概念在mongodb中也是被无视的。mongodb会暴走对于每个document的操作是原子的(也就是说不会出现一条记录只部分更新的情况)。但是如果更新涉及到多个document,mongodb就不会保证对多条document的操作符合原子性了。

所以,对于操作多条记录并保证其原子性的重任就落到了程序开发人员身上。接下来,将描述一个设计模式用来实现这个功能。这个技术不只针对于mongodb,其它可以保证单条记录原子性的NOSQL数据库都可以用它来保证更新多条数据的原子性。

最基础的想法就是创建一个新的document(我们可以叫它事物transaction)将所有你想更新的文档存储到里面。然后在你想更改的文档里面存储transaction文档的id。然后小心的设计一个执行序列,一个个的更改各个要更改的文档和transaction文档,最后我们就可以达到更改多个文档并保持原子性的目的。

使用道具 举报

回复
论坛徽章:
277
马上加薪
日期:2014-02-19 11:55:14马上有对象
日期:2014-02-19 11:55:14马上有钱
日期:2014-02-19 11:55:14马上有房
日期:2014-02-19 11:55:14马上有车
日期:2014-02-19 11:55:14马上有车
日期:2014-02-18 16:41:112014年新春福章
日期:2014-02-18 16:41:11版主9段
日期:2012-11-25 02:21:03ITPUB年度最佳版主
日期:2014-02-19 10:05:27现任管理团队成员
日期:2011-05-07 01:45:08
14#
 楼主| 发表于 2015-4-8 23:37 | 只看该作者
译者注:下面解释下这个图表。

第一步:
创建一个新文档Tran1,并将要更新的文档存到Tran1中。Tran1的status设置为init。这一步操作完成后如果失败了(这里的失败译者认为不是操作数据库失败,应该是应用程序出现了异常,如果是操作数据失败可以用getlasterror获得,然后重新操作一次数据库就可以了。而下面的恢复操作应该是在catch代码块执行的操作)恢复一致性的时候:扫描所有状态为init的transaction走第二步。(这一步创建Tran1其实是一个原子操作,它只是新插入了一条记录)。

第二步:
遍历transaction中的所有doc,添加一个指向transaction的属性。如果这一步完成后应用程序失败,继续扫描状态为init的transaction,然后将改transaction中的doc遍历添加指向该transaction的属性。

第三步:
将transaction的状态标记为pending。如果这一步完成后应用程序失败,扫描所有状态为pending的transaction,查询是否有指向该transaction的doc,如果没有了,说明第四步已经完成,就更改状态为commited。如果还有,就继续执行第四步。

使用道具 举报

回复
论坛徽章:
277
马上加薪
日期:2014-02-19 11:55:14马上有对象
日期:2014-02-19 11:55:14马上有钱
日期:2014-02-19 11:55:14马上有房
日期:2014-02-19 11:55:14马上有车
日期:2014-02-19 11:55:14马上有车
日期:2014-02-18 16:41:112014年新春福章
日期:2014-02-18 16:41:11版主9段
日期:2012-11-25 02:21:03ITPUB年度最佳版主
日期:2014-02-19 10:05:27现任管理团队成员
日期:2011-05-07 01:45:08
15#
 楼主| 发表于 2015-4-8 23:37 | 只看该作者
第四步:
对transaction的doc分别执行更新操作(当然这里每个doc的更新是能保证原子性的)。如果这一步执行的时候出现失败的情况,就扫描状态为pending的transaction,如果还有doc执行该transaction,就继续执行第四步,如果没有了(就是说都修改完成了)就删除掉这个transaction。译者认为也可以将transaction的状态标记为committed。

第五步:
当所有doc指向该transaction的时候(就是说都修改完成了),修改transaction的状态为committed。这个时候这个transaction已经执行完毕,并且保证了修改多条的原子性。

mongodb的官方网站上也有一个类似的技术可以解决mongodb没有transaction的问题,这两个的思想概念基本一样,实现方法有略微不同。

使用道具 举报

回复
论坛徽章:
277
马上加薪
日期:2014-02-19 11:55:14马上有对象
日期:2014-02-19 11:55:14马上有钱
日期:2014-02-19 11:55:14马上有房
日期:2014-02-19 11:55:14马上有车
日期:2014-02-19 11:55:14马上有车
日期:2014-02-18 16:41:112014年新春福章
日期:2014-02-18 16:41:11版主9段
日期:2012-11-25 02:21:03ITPUB年度最佳版主
日期:2014-02-19 10:05:27现任管理团队成员
日期:2011-05-07 01:45:08
16#
 楼主| 发表于 2015-4-11 00:30 | 只看该作者
副本集模型(Replication Model)

mongodb通过replica set 来实现其高可用性。replica set是一种在多个物理机器上通过数据冗余备份的方式来实现高可用性的。通常包括一个主库多个从库。为了保证数据一致性,所有的修改操作(包括插入、更新、删除)都操作主库,然后这些更新会异步更新到其它从库。

使用道具 举报

回复
论坛徽章:
277
马上加薪
日期:2014-02-19 11:55:14马上有对象
日期:2014-02-19 11:55:14马上有钱
日期:2014-02-19 11:55:14马上有房
日期:2014-02-19 11:55:14马上有车
日期:2014-02-19 11:55:14马上有车
日期:2014-02-18 16:41:112014年新春福章
日期:2014-02-18 16:41:11版主9段
日期:2012-11-25 02:21:03ITPUB年度最佳版主
日期:2014-02-19 10:05:27现任管理团队成员
日期:2011-05-07 01:45:08
17#
 楼主| 发表于 2015-4-11 00:30 | 只看该作者
在replica set(也就是所有的主从数据库)中,成员之间相互关联,并且相互监听别人的心跳。如果有一个从库挂了,其它库就会感应不到该库的心跳信号,从而就会从副本集中删掉它。当这个从库将来恢复的时候,他可以重新加入集群,并且从主库那里同步它挂掉这段时间的数据更新。当然,如果它挂掉的时间太长了,主库的更新log已经不能完全记录这段时间发生的数据修改,就只能重新加载所有的主库数据到这个从库了,就好像这个从库是新加入的一样。(译者注:主库会维护一个oplog来记录数据修改操作,但是这个log的大小是有限的,如果写满了就会覆盖掉最旧的log,我们这边的dba叫做写圈)。

上面说的是从库挂掉,如果主库挂掉,情况就会更糟糕一些。为了防止主库挂掉后的尴尬,一个主库选举协议会一直在各个成员之间运行着,当主库挂掉,会最快的根据这个协议选举一个新的主库。各个成员选举投票的时候考虑的因素挺多,比如节点更新时间,网络等等。当一个成员获得了大多数投票,一个新的主库就产生了。需要注意的是如果主库挂掉的时候还在进行异步复制主库的操作,新选举的主库将不会获得到这些更新的信息,也就是说,会丢失掉这些信息。

使用道具 举报

回复
论坛徽章:
277
马上加薪
日期:2014-02-19 11:55:14马上有对象
日期:2014-02-19 11:55:14马上有钱
日期:2014-02-19 11:55:14马上有房
日期:2014-02-19 11:55:14马上有车
日期:2014-02-19 11:55:14马上有车
日期:2014-02-18 16:41:112014年新春福章
日期:2014-02-18 16:41:11版主9段
日期:2012-11-25 02:21:03ITPUB年度最佳版主
日期:2014-02-19 10:05:27现任管理团队成员
日期:2011-05-07 01:45:08
18#
 楼主| 发表于 2015-4-11 00:30 | 只看该作者
mongodb客户端lib会提供访问mongodb的api。在最开始,客户端会连接副本集中的一些成员(根据一个种子列表),通过向这些成员询问你是不是主库?(ismaster?)最后客户端会生成一个谁是主库谁是从库的快照。这样,当客户端执行修改操作的时候就连接主库,执行查询操作的时候就随机连个从库。同时客户端会周期性的运行ismaster命令来探测是否有新的库加入。当有已经存在的库挂掉后,所有连接它的客户端都会断掉跟它的连接,同时强制更新快照。

这里有一个特殊的从库需要介绍下,他就是延时从库(slave delay)。它会让数据从主库扩散到它的时间有一个固定的延时(比如2个小时)。它的作用是当出现误删除时,可以用它的数据进行恢复。(译者注:上面讲的oplog只会记录删除的id,不会记录删除的具体内容。刚参加工作那会儿,误删除过一些数据,求救dba,dba发来oplog一看只有删除的记录,没有删除的内容,都是泪..)

对于数据更新操作,客户端会将请求发到主库,默认情况下,当主库写入成功后就会返回。但是,mongodb提供了请求参数来指定当数据扩散到多少个,或者哪个从库的时候返回。具体见上面的一个表。这样客户端可以确认一条更改操作至少扩散到了多少个从库。当然,这个操作要在效率和实时性之间做权衡。

使用道具 举报

回复
论坛徽章:
277
马上加薪
日期:2014-02-19 11:55:14马上有对象
日期:2014-02-19 11:55:14马上有钱
日期:2014-02-19 11:55:14马上有房
日期:2014-02-19 11:55:14马上有车
日期:2014-02-19 11:55:14马上有车
日期:2014-02-18 16:41:112014年新春福章
日期:2014-02-18 16:41:11版主9段
日期:2012-11-25 02:21:03ITPUB年度最佳版主
日期:2014-02-19 10:05:27现任管理团队成员
日期:2011-05-07 01:45:08
19#
 楼主| 发表于 2015-4-11 00:30 | 只看该作者
对于查询请求,默认情况下,客户端可以从主库那里拿到最新的数据。客户端也可以选择指定自己读哪个从库,这个时候必须能容忍从库返回的数据有可能已经过期。查询请求读从库为mongodb提供了负载均衡的功能。需要注意的是,在读从库这种模式下,写完立刻读可能会看不到修改。

对于大部分请求都是查询的应用,从库读可以极大的改善系统性能。客户端驱动会周期性的扫描各个从库,选择最快延迟最低的的从库来执行查询操作。这里需要注意的是读请求只能访问一个从库,mongodb没有群读或者多个节点读这个说法。

relica set的作用是提供数据冗余和读负载均衡。但是对于写请求来说,没有提供负载均衡,所有的写都落到主库上。

relica set的另外一个好处就是成员可以轮流下线做一些耗费性能的操作,比如紧凑操作,索引建立,数据备份等,不会影响线上服务。

使用道具 举报

回复
论坛徽章:
277
马上加薪
日期:2014-02-19 11:55:14马上有对象
日期:2014-02-19 11:55:14马上有钱
日期:2014-02-19 11:55:14马上有房
日期:2014-02-19 11:55:14马上有车
日期:2014-02-19 11:55:14马上有车
日期:2014-02-18 16:41:112014年新春福章
日期:2014-02-18 16:41:11版主9段
日期:2012-11-25 02:21:03ITPUB年度最佳版主
日期:2014-02-19 10:05:27现任管理团队成员
日期:2011-05-07 01:45:08
20#
 楼主| 发表于 2015-4-11 00:31 | 只看该作者
分片模型

为了让写请求均衡,我们可以采用mongodb的分片。分片首先要把一个表根据partition key分成多个chunk(一个chunk包括一个key区间的数据),然后将各个chunk分别分发到各个分片(每个分片都是一个replica set-副本集)。
mongodb的分片让表可以扩充到无限大,这对于大数据场景非常重要。

重申下,在分片模型中,每个collection都要定义一个partition key。key的空间被分成多个存储在各个shard上的chunk,每个chunk都是一段连续的key空间。

使用道具 举报

回复

您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

TOP技术积分榜 社区积分榜 徽章 团队 统计 知识索引树 积分竞拍 文本模式 帮助
  ITPUB首页 | ITPUB论坛 | 数据库技术 | 企业信息化 | 开发技术 | 微软技术 | 软件工程与项目管理 | IBM技术园地 | 行业纵向讨论 | IT招聘 | IT文档
  ChinaUnix | ChinaUnix博客 | ChinaUnix论坛
CopyRight 1999-2011 itpub.net All Right Reserved. 北京盛拓优讯信息技术有限公司版权所有 联系我们 未成年人举报专区 
京ICP备16024965号-8  北京市公安局海淀分局网监中心备案编号:11010802021510 广播电视节目制作经营许可证:编号(京)字第1149号
  
快速回复 返回顶部 返回列表