mongodb-erlang driver Replica Set Secondary节点分配不均衡的问题

原创 2013年12月05日 16:08:31

服务端使用Mongodb的Replica Set,一个Primary节点,两个Secondary。 开始一直没注意,前天设置了MMS之后发现两个Secondary节点的查询操作分配明显不平衡,其中一个的op数量似乎总是另一个的2倍。

一开始以为是mongodb配置有问题,各种排查后没有发现问题,而且原理上讲RS并不是将Primary作为proxy进行load balance,也就不可能是因为mongodb的配置导致的分配不均,在使用Java的Driver测试后发现,mongodb的Java Driver并无该问题,所以初步可以肯定问题是出现在erlang的driver里。 

为了确定下不是特殊情况,在本机也测试了一下。Primary打开./mongostat, 两个Secondary设置db.setProfilingLevel(2), 单进程测试,每次find_one 200条数据,发现第一个seconday查询数约为第二个的2倍,而且mongostat显示的数据中,command大约有60几。直观感觉难道是200个查询被平均分给了3个节点,主节点又把自己的那1/3数据全部转移给了其中一个从节点?

再来看erlang driver,每次数据操作都调用mongo:do方法:

-spec do (write_mode(), read_mode(), connection() | rs_connection(), db(), action(A)) -> {ok, A} | {failure, failure()}. % IO
%@doc Execute mongo action under given write_mode, read_mode, connection, and db. Return action result or failure.
do (WriteMode, ReadMode, Connection, Database, Action) -> case connection_mode (ReadMode, Connection) of
	{error, Reason} -> {failure, {connection_failure, Reason}};
	{ok, Conn} ->
%%		io:format("Mongo conn:~p~n", [Conn]),
		PrevContext = get (mongo_action_context),
		put (mongo_action_context, #context {write_mode = WriteMode, read_mode = ReadMode, dbconn = {Database, Conn}}),
		try Action() of
			Result -> {ok, Result}
		catch
			throw: E = {connection_failure, _, _} -> {failure, E};
			throw: E = not_master -> {failure, E};
			throw: E = unauthorized -> {failure, E};
			throw: E = {write_failure, _, _} -> {failure, E};
			throw: E = {cursor_expired, _} -> {failure, E}
		after
			case PrevContext of undefined -> erase (mongo_action_context); _ -> put (mongo_action_context, PrevContext) end
		end end.

-spec connection_mode (read_mode(), connection() | rs_connection()) -> {ok, connection()} | {error, reason()}. % IO
%@doc For rs_connection return appropriate primary or secondary connection
connection_mode (_, Conn = {connection, _, _, _}) -> {ok, Conn};
connection_mode (master, RsConn = {rs_connection, _, _, _}) -> mongo_replset:primary (RsConn);
connection_mode (slave_ok, RsConn = {rs_connection, _, _, _}) -> mongo_replset:secondary_ok (RsConn).

通过connection_mode (ReadMode, Connection)获得与数据库的连接,当ReadMode为slave_ok时,调用mongo_replset:secondary_ok (RsConn).

再来看mongo_replset:secondary_ok/1,

-spec secondary_ok (rs_connection()) -> err_or(connection()). % IO
%@doc Return connection to a current secondary in replica set or primary if none
secondary_ok (ReplConn) -> try
                {_Conn, Info} = fetch_member_info (ReplConn),
                Hosts = lists:map (fun mongo_connect:read_host/1, bson:at (hosts, Info)),
                R = random:uniform (length (Hosts)) - 1,
                secondary_ok_conn (ReplConn, rotate (R, Hosts))
        of Conn -> {ok, Conn}
        catch Reason -> {error, Reason} end.

Hosts自然是得到的主从节点信息,形如:

   [{"192.168.17.102",27017},
    {"192.168.17.102",27018},
    {"192.168.17.102",27019}]

这时注意下面,使用了一个随机变量R,看到这里肯定想让到是用户从Hosts中随机取出一个Secondary节点进行读操作。既然这样,那么是不是random:uniform()每次产生的随机数不随机?简单测试发先并非该问题,真正的原因在

secondary_ok_conn (ReplConn, rotate (R, Hosts))

首先看一下secondary_ok_conn:

-spec secondary_ok_conn (rs_connection(), [host()]) -> connection(). % EIO
%@doc Return connection to a live secondaries in replica set, or primary if none
secondary_ok_conn (ReplConn, Hosts) -> try
                until_success (Hosts, fun (Host) ->
                        {Conn, Info} = connect_member (ReplConn, Host),
                        case bson:at (secondary, Info) of true -> Conn; false -> throw (not_secondary) end end)
        catch _ -> primary_conn (2, ReplConn, fetch_member_info (ReplConn)) end.

该函数调用until_success/2,用于依次遍历Hosts列表,直到找到一个节点是secondary为止。当然每次遍历的时候先要connect_member/2得到连接信息,再判断是否为主从节点。这也就解释了为什么primary节点的mongostat数据中有60+的操作(200的1/3)。

而这里的Hosts,就是rotate (R, Hosts)后得到的新的Hosts:

rotate (N, List) ->
        {Front, Back} = lists:split (N, List),
        Back ++ Front.

不难看出,该函数用于打乱Hosts中各Host的顺序,从而生成新的随机顺序的Hosts列表。配合这样secondary_ok_conn/2函数,达到随机选取一个secondary的目的。


但是该方法存在一个问题,举个例子来说,List=[1,2,3], 对于随机的N(0/1/2),该方法只能产生3中随机组合:

[1,2,3]
[3,1,2]
[2,3,1]

每种组合产生的概率为1/3。这里假设1,2为两个secondary,3为primary。 之前说过until_success/2用于从列表中找到第一个secondary节点,忽略主节点。也就是说,如果遇到第二种Hosts组合,我们选取的节点必然是1。如此来看,最终选择到1的概率为2/3, 2的概率为1/3. 所以就出现了开头所说的情况。


临时写了一个rotate_new替换原来的rotate,用于等概率生成[1,2,3,...N]的所有组合之一,效率有些低,好在这下概率恢复到50%。明天看看再写个效率高点的方法:

rotate_new(RotateHosts, [Host]) ->
    [Host | RotateHosts];
rotate_new(RotateHosts, Hosts) ->
    R = random:uniform(length(Hosts)),
    Host = lists:nth(R, Hosts),

    rotate_new([Host | RotateHosts], Hosts--[Host]).


其他一些random sort的方法整理了下:http://blog.csdn.net/huang1196/article/details/17218295

测试发现3个Hosts的情况下这种方法虽然用了--,但是效率还是很高的。




题外话:erlang的driver有1年多没更新了,之前就发现一个严重的bug,就是mongodb_app.erl中有个方法:

next_requestid()-> ets:update_counter (?MODULE, requestid_counter, 1).



对每一个request,都生成一个requestid,每次+1.  

然后注意mongo_protocal里对RequestId的使用:

-define (put_header (Opcode), ?put_int32 (RequestId), ?put_int32 (0), ?put_int32 (Opcode)).
-define (get_header (Opcode, ResponseTo), ?get_int32 (_RequestId), ?get_int32 (ResponseTo), ?get_int32 (Opcode)).



所有的RequestId默认是int32的。但是一般企业级的产品,访问量在一段时间内很容易突破int32的范围,也就是update_counter的时候,requestid超过了int32取值范围,导致所有数据查询失效。

目前的解决方法也很简单,直接使用ets:update_counter (?MODULE, requestid_counter, {Pos, 1, 214748364, 0})替换原来的代码,这里214748364是随便设置的一个小于int32的值,超过该值计数器自动置0.


版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

Mongodb3.4.2 Replica Set主节点及备份节点裁判节点

1.创建三个节点目录如下图: 节点一: 节点二: 节点三: 2.对上面的三个节点分别在cfg文件下新增配置文件 节点一配置文件: logpath=D:\data1\logs\mongod.log db...

mongodb集群环境搭建Replica Set

mongodb的集群搭建方式主要有三种,主从模式,Replica set模式,sharding模式, 三种模式各有优劣,适用于不同的场合,属Replica set应用最为广泛,主从模式现在用的较少,s...

mongodb的副本集Replica Set

mongodb不推荐主从复制,推荐建立副本集(Replica Set)来保证1个服务挂了,可以有其他服务顶上,程序正常运行,几个服务的数据都是一样的,后台自动同步 【要搭建一个稳定的mong...
  • sd0902
  • sd0902
  • 2014-03-19 16:11
  • 10272

Bluemix的三台虚拟机做MongoDB Replica Set

之前在线上环境搭建过一款ps4的游戏,用的就是这个架构,最近在复习MongoDB, 就充分利用一下Bluemix的免费虚拟机,在做一次,并且将过程自动化。 服务器环境: Hostname I...

mongodb分片集群(sharding with replica set)配置

一共有4台机器,各自挂接一个存储,希望实现:尽量节约存储高可用性存储大量数据配置方案:每一台机器做一个分片的主数据库每一台机器做一个分片的后备数据库每一台机器做一个分片的仲裁服务两个两个一组交叉作对方...

mongodb replica set 多服务器 高可用 配置 详解

-- mongodb replicaset的搭建 1 download the install package wget  http://fastdl.mongodb.org/linux/mongod...

Mongodb集群搭建之 --Replica Set

MongoDB是时下流行的NoSql数据库,它的存储方式是文档式存储,并不是Key-Value形式。关于Mongodb的特点,这里就不多介绍了,大家可以去看看官方说明:http://docs.mong...

第七章:MongoDB管理维护Replica Sets(读写分离&故障转移&增删节点)

一 . 读写分离 1. 登录主库: ./mongo 192.168.56.88:27017  插入一条数据:  testrs:PRIMARY> db.person.insert({"name"...

MongoDB管理与开发精要《红丸出品》21.4.3 管理维护Replica Sets之增减节点

21.4.3增减节点 MongoDB Replica Sets不仅提供高可用性的解决方案,它也同时提供负载均衡的解决方案,增减Replica Sets节点在实际应用中非常普遍,例如当应用的读压力暴增...

@ Replica set 多服务器 高可用 配置 (添加删除节点方法)

一: 配置所有节点/etc/hosts 下  二: 配置好时间同步服务器 /etc/ntp.conf ,  /etc/sysconfig/ntpd 文件配置 三: 配置单独的mon...
  • lmocm
  • lmocm
  • 2014-07-14 15:47
  • 635
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)