driver是mongodb非常重要的组成部分,通过不同的配置实现secondary访问;读写分离,动态感知集群容灾切换等功能。mongodb目前已经覆盖了大部分的开发语言,常见的java到go,可以参考官方连接mongodb drivers。这篇文章我们以java版本为例介绍mongodb的drivers实现逻辑和协议,选择java的原因为语言阅读门槛低,配合ide,进行debug追踪也非常容易,其次java版本实现的功能非常完善。主要讲解的是replicaset模式,因为shard模式有mongos作为代理,没有充分体现driver的路由逻辑。
本篇讲解了以下三个部分:
driver初始化
driver请求路由
driver容灾处理
首先来看下java初始话配置demo:
还有另外一种通过uri初始化的方法:
两种方式的实现原理么有本质区别,driver会将uri拆解为第一种的参数设置方式。
uri方式的有点是在复杂的系统中更通用,不同编程语言系统可以共享一套初始化配置;如果有多套异构的系统访问mongodb,建议采用uri的方式。
builder方式更灵活,对参数的设置非常细,并且代码清晰易读。
仔细的读者会注意到,address地址变量叫做seed,而不是mongodb集群上的primary,secondary,或者其他名称。seed是mongodb访问地址的一种抽象概念,可以是mongodb集群中的任意一个节点,如果是shardcluster模式,则应该是mongos地址。这样的好处是使用者不需要关系集群的状态改变,更不用随着集群状态改变而修改配置。比如,增加了一个新的节点,再或者发生了primary与secondary之间的角色切换,但seed却保持不变,只要是集群中的任意地址即可。然而driver在处理请求时是需要明确primary,secondary等角色的地址关系。
所谓的初始化,就是对集群状态的第一次获取,并且构建driver内部使用的servermonitor线程,集群中的每个节点,都对应有一个servermonitor。初始状态下会以seed为起点开始,servermonitor主要有几下个动作:
首先检车连接状态,如果是不可用的,需要重新建立链接,并执行action a动作:
<code>ismaster</code> 这里的作用相当于ping一下,在这个action中没有特别的作用
<code>buildinfo</code> 获取server的版本信息,目的是做版本之间的兼容性
<code>auth</code> 如果配置有鉴权属性,会执行此步骤,目前默认的是sasl,可以参考rfc,scram方式一般需要三次的rpc交互。
<code>getlasterror</code> 确定上面的请求没有出错,整个action结束
连接ready后,开始心跳,并且间隔性的检查,默认每10秒钟,可配置,但最小不能低于500毫秒
<code>ismaster</code> 与action a的命令一样,但这里获取到的信息非常重要
检查上一步刚刚获得的server description与之前的是否一致,如果没有发生任何改变,状态稳定,则该轮心跳检查结束。不一致,则产生changeeven事件,调用acton c。
如果有必要,则继续执行onchanged listener:
更新server hosts列表,如果列表中之间不存在该地址,则新创建连接,以新地址构造servermonitor线程
如果当前节点是primary,还要更新electionid等字段
在action c动作结束后,每个server都产生了一个server montior线程,并且driver也同步到了集群状态,primary地址,electionid等信息。一旦集群信息发生改变,心跳线程也会随时发现并做出相应的修改。
servermonitor是独立的线程,只保证心跳检查,不处理真正的用户请求。用户的请求是通过其他的连接完成,那对连接的管理是有个套connectionpool机制,每个连接有一套独立的connectionpool。初始化后connectionpool是空的,等请求需要时会在建立连接。
请求线程从pool中拿到connection后,会在请求线程里处理请求,结束后再放回给connectionpool。不同的实现语言,这里处理的逻辑并不一样,不属于mongodb约定的协议范围内。
可以参考官方文档,ismaster,在mongoshell中执行会得到以下结果,非常重要的是ismaster和hosts字段,这两个字段完整的描述了集群的状态信息。action b.2的判断,依赖ismaster的是否是true.
从整个流程上可以看到,ismaster命令存在冗余,一个连接的初始化逻辑,至少需要发送2次,并且对同一个server,不同的连接还是要发送,很多情况不是必要的,可以简化,提高连接建立的速度。
通过对mongodb driver for java的分析,我们已经很清楚了初始化的行为动作,包括平时运行时的大概情况,其他语言的实现也大同小异,跑不出这个流程。希望对读者日后处理客户端初始化问题时有所帮助。
了解原理后我们回头来思考下,怎么使用才是最佳实践,主要几点:
从整个流程上看,mongodb driver初始化的过程很‘重’,要交互很多次,尤其是使用php的同学注意了,不建议采用短连接的方式,请将mongodb的连接持久化下来。
每个driver都至少会有一个monitor连接,而且是不会回收的。所以,规划连接数时,需要关注到这点。
提高请求线程并发量的同时,尝试同步提高连接池上限,对性能会有一定帮助。但请注意,不是越多越好,过多的线程会导致线程调度消耗过多的资源。而且请配置连接的idle time,让其自动回收长期不用的连接,避免连接泄漏。
最重要的一点,学会用<code>ismaster</code>命令排查问题,driver请求无法访问时,尝试在mongo shell中执行<code>ismaster</code>,确保返回的集群信息都是正确的。并且ismaster是不需要鉴权的,所以,保护好你的mongodb实例,不要出现的公网上。
配置多个seed地址是,避免其中一个不可用。
......
待续,driver请求路由