mongodb-erlang driver Replica Set Secondary节点分配不均衡的问题
2013-12-05 16:08
615 查看
服务端使用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方法:
通过connection_mode (ReadMode, Connection)获得与数据库的连接,当ReadMode为slave_ok时,调用mongo_replset:secondary_ok (RsConn).
再来看mongo_replset:secondary_ok/1,
Hosts自然是得到的主从节点信息,形如:
这时注意下面,使用了一个随机变量R,看到这里肯定想让到是用户从Hosts中随机取出一个Secondary节点进行读操作。既然这样,那么是不是random:uniform()每次产生的随机数不随机?简单测试发先并非该问题,真正的原因在
首先看一下secondary_ok_conn:
该函数调用until_success/2,用于依次遍历Hosts列表,直到找到一个节点是secondary为止。当然每次遍历的时候先要connect_member/2得到连接信息,再判断是否为主从节点。这也就解释了为什么primary节点的mongostat数据中有60+的操作(200的1/3)。
而这里的Hosts,就是rotate (R, Hosts)后得到的新的Hosts:
不难看出,该函数用于打乱Hosts中各Host的顺序,从而生成新的随机顺序的Hosts列表。配合这样secondary_ok_conn/2函数,达到随机选取一个secondary的目的。
但是该方法存在一个问题,举个例子来说,List=[1,2,3], 对于随机的N(0/1/2),该方法只能产生3中随机组合:
每种组合产生的概率为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.
一开始以为是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.
相关文章推荐
- mongodb 三节点Shard Replica set 配置
- mongodb replica set 添加删除节点的2种方法
- MongoDB Replica Set的部署配置以及遇到的问题 linux
- 从MongoDB Replica Set HA 看分布式系统读写一致性问题
- 学习MongoDB(三) Add an Arbiter to Replica Set 集群中加入仲裁节点
- mongodb replica set 添加删除节点的2种方法
- mongodb 2.4 不同server节点的replica set 搭建过程(一)
- mongodb replica set 添加/删除节点方法--http://www.ii123.com/jc/bc/bczh/258948.html
- mongodb 2.4 不同server节点的replica set 搭建过程(二)
- 节点仲裁mongodb replica sets reconfig and conver a Secondary to an Arbiter
- Mongodb高可用架构—Replica Set 集群实战
- SpringBoot获取Redis集群实例有缺少的问题->为Redis集群新增节点分配slot
- MongoDB+shard+replica set
- Windows MongoDB:搭建三节点 Replica Set 环境
- mongodb集群安装,一主二从,replica_set
- mongodb replica set 多服务器 高可用 配置 详解
- MongoDB Replica Set搭建集群
- Mongodb sharding cluster with replica set
- MongoDB Replica Set 副本集
- 关于hadoop中datanode节点不同的dfs.data.dir之间数据均衡问题