奕名小惊随笔之二 —— 谈谈给Mongo买保险

小贴士:
在MongoDB中,知不知道为何通常搭复制集推荐至少要选1主2从,而不是1主1从?
事实上,道理很简单,避免单点故障。有人说了1主1从不是就是为了避免单点故障吗?何必多此一举?乍一看是这么回事,但是细细想一下,有哪套系统不需要停机维护,利用MongoDB的副本集可以进行滚动维护,1主1从这时就有点悬了,从节点维护时,不就出现单点故障了吗?所以从严格意义上讲,1主1从不是一个最佳选择。

如何快速建立副本集?命令行?脚本?显然不是,正解当然是企业版MongoDB OpsManager,这才是自动化运维利器!
OpsMgr

随笔正文

该不该买保险?

本人是很讨厌保险的,原因是自己数学虽还不错,但是斗不过精算师啊!当然啦,这不是根本原因,真要高收益回报,买保险是不行的。讨厌保险源自国内卖保险的销售实在太烂,完全是连蒙带骗太不靠谱。但是香港AIA买过一次保险后,香港本地保险代理人的职业素养还是让我对保险的态度有所改观的,所以要买保险真心该到香港去买,加上本人长期看淡人民币,从投资角度来说也该去香港买,毕竟港币是挂钩美元的。

Mongo里的保险

话归正传,继续偶的科普随笔。今天谈的是MongoDB的保险,不是广告推销AIA保险。所以,首先要牢记一点,在MongoDB里面缺省情况下是基本上是可以理解为不买保险的,所以无论你用什么MMAP还是WT引擎往数据库里塞数据,数据库一旦发生异常,就存在倒霉蛋会丢数据 。为什么?想想非关系型数据库为何而生,最初是为了处理大量数据的同时还要确保速度,所以在高速处理的指引下设计的缺省无保险原则也就顺理成章。
可是,场景铺开了,并非每个场景的数据都是只要量不要质的。所以必须赋予使用者一个选择权,至于说你买不买保险是你的事情,这才民主么!在MongoDB中,其中的一个民主权就是WriteConcern,另外一个是他的兄弟ReadConern。

“写保险”

先来谈谈WriteConcern,直译太别扭,所以本人就把他叫做“写保险”吧。

先需要解释一下数据进入MongoDB的过程:首先写操作是会进入到服务器内存中的,内存中有两类结构,一个是数据库缓存,以WiredTiger为例,叫WT Cache,定期会和磁盘同步,确保数据的持久性;另一个结构是journal,可以理解为先行日志,记录到底写操作更新了什么。只要journal被同步到磁盘上,那么万一数据库崩了,起码可以通过journal恢复数据。所以简单来说,journal是个备胎。
缺省情况下如果不做任何配置修改,是w=1,j=0。什么意思呢?实际上代表买了基本没有保险,或者说买了个超级基础保险,w=1表示数据被写到了1台服务器的内存中,而且确保在内存中的操作是成功的,这时数据库就返回成功标志,应用认为写操作完成。注意到此时数据是在内存中,未必被同步到磁盘上;同时j=0表示写操作确认是不用管journal是否被同步到磁盘中的。换而言之,备胎机制是不生效的,异常情况下不能找备胎来哭诉。

既然出事了没有保障,为何叫做初级保险呢?因为毕竟MongoDB还是保证了你每个写操作是有一个确认的动作的,只要数据库正常运作,数据一定会被同步到磁盘中,只是时间问题而已。事实上,还可以更加生猛,不买保险:w=0, j=0。一个劲的写,不管写是不是成功。有人问了,这个貌似完全没有用?事实上确实有,场景可以用,比如网络页面的计数器,日志记录等等,数据丢了就丢了么,无伤大雅,反过来以计数器为例如果每次加1的动作都要确认,万一来个网络延迟,完全得不偿失。

一般而言,用MongoDB,你还是需要买个基本险的,也就是w=1, j=1,这样每次写成功代表了journal一定被同步到了磁盘上,起码出问题时数据可以恢复。

那如果我们现在有个推荐1主2从的复制集时候,该如何买保险呢?显然这时候买一份保险就不够了,继续w=1,j=1的问题在于如果主节点彻底挂掉,从节点变主节点时如果有一部分数据还没同步到这个新的主节点时,那么就会有数据丢失现象。所以,起码我们要买2份保险,w=2,j=1,这时数据会被确保在主节点并同步另外一个从节点上后才认为写操作完成,显然主节点崩掉时,至少有一个从节点还有刚才最新的数据,它自然会被升级成新主节点。值得注意的是,这个同步是只到内存中的,换而言之并不保证从节点的journal是否已经被保存到磁盘上。所以真的遇到倒霉蛋,还是有可能丢数据的,当然同时挂掉多台服务器是超小概率事件。

更多实战中,是会把这份保险变成一个加强版的,w=’majority’,好比民主投票时搞定多数人就ok了。当然喽,特别谨小慎微的可以考虑买个w=3 (all),让所有副本集都齐步走,这下就绝对安全可靠了。

平衡点

当然保险不是免费的,给最多保障的一定是花费你最多的(也就是最耗时的)。站在数据库的角度来说,w=0基本上是面向无连接的,完全没有开销,而w=3 (all)显然时间开销最大,需要等待网络同步,到底花几个钱买哪类保险是仁者见仁,智者见智的问题。

分期付款?

说完了保险品种,我们来看看如何买法?我们知道买保险可以买一次性的,也可以买1年的,还可以买一生的,这个在MongoDB中亦是如此。你可以针对一个连接设置保险系数,也可以针对一个Collection设置,还可以针对一个复制集来设置,灵活度绝对没有问题。以一个连接来看,下面是一个例子供参考:

XXX.MongoClient (host=”mongodb://localhost:27017”, replicaSet=”danielrs”, w=3, wtimeout=1000, j=True)

组合险

最后谈谈组合保险,使用MongoDB更多时候是在和MongoDB的驱动打交道,也就是写代码调用API。由于代码可以非常灵活处理你想要的,所以可以在速度和可靠性间取个平衡,于是可以插入数据的时候,每隔一段时间做个可靠性保证,既确保速度,又顾及数据的持久性。下面就是一个简单的例子,每200次数据插入确保数据插入是majority入库,其他就不用上保险了。
code1

最后提一句,可以利用标签(Tag),为服务器打上标签,w参数后跟上,来确保写入操作被写入到对应的标签机器上,这可以实现数据定向写入。

好吧,关于“写保险”应该差不多了,有空再谈谈ReadConcern“读保险”吧。

结束语

“保险”是一种投资,但如何寻找平衡点是艺术,生活中如此,Mongo中亦如此。

顺便说一句,写完了才发现有张大神写过类似的,要看升级版的请参阅“MongoDB writeConcern原理解析”。

随笔链接:
奕名小惊随笔之一 —— 我眼中的Mongo
奕名小惊随笔之二 —— 谈谈给Mongo买保险
奕名小惊随笔之三 —— 走近 “View” Mongo (MongoDB 3.4新特性之一)
奕名小惊随笔之四 —— 互联互通 2.0(MongoDB 3.4新特性之二)