Rabbitmq几点注意的地方


转载自http://tech.techweb.com.cn/thread-438919-1-1.html


1、tcp_accepto.erl,r对于accept采用的是异步方式,利用prim_inet:async_accept/2方法,此模块没有被文档化,是otp库内部使用,通常来说没必要使用这一模块,gen_tcp:accept/1已经足够,不过rabbitmq是广播程序,因此采用了异步方式。使用async_accept,需要打patch,以使得socket好像我们从gen_tcp:accept/1得到的一样:


handle_info({inet_async, LSock, Ref, {ok, Sock}},
State = #state{callback={M,F,A}, sock=LSock, ref=Ref}) -> / f( ?( U- ]) S7 W! t( O: g7 t
%%这里做了patch * q, ^' @7 Z; s9 ?# ?
%% patch up the socket so it looks like one we got from + M0 B8 r& @6 ~
%% gen_tcp:accept/1 0 j4 bc# `( d! V8 f8 v4 s+ p! c
{ok, Mod} = inet_db:lookup_socket(LSock),3 h/ m0 D# ]( wR9 [
inet_db:register_socket(Sock, Mod),
' l' x+ L; X8 I
try
%% report 8 D+ P$ f3 L5 jN
{Address, Port} = inet_op(fun () -> inet:sockname(LSock) end), 0 W) Yu9 h4 d( @4 {' @4 R
{PeerAddress, PeerPort} = inet_op(fun () -> inet:peername(Sock) end),
error_logger:info_msg("accepted TCP connection on ~s:~p from ~s:~p~n",
[inet_parse:ntoa(Address), Port, ' W) f! }' s( F1 |0 T- b
inet_parse:ntoa(PeerAddress), PeerPort]),
%% 调用回调模块,将Sock作为附加参数
apply(M, F, A ++ [Sock])
catch {inet_error, Reason} -> _9 U6 B- \7 `+ Q0 l' I, z
gen_tcp:close(Sock), . [4 g: ~6 z0 K
error_logger:error_msg("unable to accept TCP connection: ~p~n", : R4 d' l* `" n1 ]5 j% ]
[Reason])
end,

%% 继续发起异步调用 / r+ q# f7 H7 R% ]# H3 u3 U
case prim_inet:async_accept(LSock, -1) of 0 j8 p5 h- @8 N5 O% ?: Z
{ok, NRef} -> {noreply, State#state{ref=NRef}}; + Y6 z: f1 ^; j0 R% ]: Y( c
Error -> {stop, {cannot_accept, Error}, none} ) o9 p+ Q6 p- B( d/ V6 F
end;
%%处理错误情况
handle_info({inet_async, LSock, Ref, {error, closed}}, + N' g3 F3 u* ]. r2 n/ cU* m/ z
State=#state{sock=LSock, ref=Ref}) ->
%% It would be wrong to attempt to restart the acceptor when we
%% know this will fail.
{stop, normal, State};
, ~) S* z% ?. s7 _5 b
2、 rabbitmq内部是使用了多个并发acceptor,这在高并发下、大量连接情况下有效率优势, 类似java现在的nio框架采用多个reactor类似,查看tcp_listener.erl:
/ P6 h2 ]8 b+ k6 ]: S
init({IPAddress, Port, SocketOpts, ( N9 l$ N2 cl* pY- F( r
ConcurrentAcceptorCount, AcceptorSup, / ~$ H" D$ T7 `& q- e/ _
{M,F,A} = OnStartup, OnShutdown, Label}) ->
process_flag(trap_exit, true), 9 s; ^0 G* R+ @2 E
case gen_tcp:listen(Port, SocketOpts ++ [{ip, IPAddress}, 9 G* F4 m) k0 q$ X
{active, false}]) of
{ok, LSock} -> * B3 g4 _9 ]- G3 C- a$ E
%%创建ConcurrentAcceptorCount个并发acceptor
lists:foreach(fun (_) ->8 K& K. U$ f6 O) D4 b. ?n) z
{ok, _APid} = supervisor:start_child(
AcceptorSup, [LSock])y3 @! Z{" D& V5 U4 s6 t, c& o" m
end,3 L1 ]) E+ b1 L4 c1 a9 w7 w
lists:duplicate(ConcurrentAcceptorCount, dummy)),

{ok, {LIPAddress, LPort}} = inet:sockname(LSock), ) d% [& j# e* ^3 o
error_logger:info_msg("started ~s on ~s:~p~n", # ]+ |/ B" v" ?0 r" j. X- N
[Label, inet_parse:ntoa(LIPAddress), LPort]),
%%调用初始化回调函数 / K8 X9 g1 V3 v# b- p1 {
apply(M, F, A ++ [IPAddress, Port]),
{ok, #state{sock = LSock, . S* \! t7 w+ |) V! _
on_startup = OnStartup, on_shutdown = OnShutdown, : {- E) N8 P/ j# K0 J. ?" T+ m
label = Label}}; ) t- j. M; L: m# Y! r
{error, Reason} -> / g# `3 D& B: c
error_logger:error_msg(
"failed to start ~s on ~s:~p - ~p~n", & Y5 I- u2 u1 U0 t; V
[Label, inet_parse:ntoa(IPAddress), Port, Reason]), 8 ~" V0 o7 e; j3 ?+ S7 i4 G' j) J
{stop, {cannot_listen, IPAddress, Port, Reason}} 2 [U( \. `8 d$ N: s. u
end.

这里有一个 技巧,如果要循环N次执行某个函数F,可以通过lists:foreach结合lists:duplicate(N,dummy)来处理。
# R8 O- B. ]1 P
lists:foreach(fun(_)-> F() end,lists:duplicate(N,dummy)).
0 t. X6 {( m9 _! }/ C1 Ul
3、 simple_one_for_one策略的使用,可以看到对于tcp_client_sup和tcp_acceptor_sup都采用了simple_one_for_one策略,而非普通的one_fo_one,这是为什么呢? ! B2 l3 d6 V5 z8 p# H- o; E
这牵扯到simple_one_for_one的几个特点: 2 ]* c# a) [8 r
1)simple_one_for_one内部保存child是使用dict,而其他策略是使用list,因此simple_one_for_one更适合child频繁创建销毁、需要大量child进程的情况,具体来说例如网络连接的频繁接入断开。
2)使用了simple_one_for_one后,无法调用terminate_child/2 delete_child/2 restart_child/2 ( l$ `: ]' s- U) ]- C1 `1 d; S

3)start_child/2 对于simple_one_for_one来说,不必传入完整的child spect,传入参数list,会自动进行 参数合并在一个地方定义好child spec之后,其他地方只要start_child传入参数即可启动child进程,简化child都是同一类型进程情况下的编程
4 Q2 L- K; O" Q- N# o9 ^
在 rabbitmq中,tcp_acceptor_sup的子进程都是tcp_acceptor进程,在tcp_listener中是启动了 ConcurrentAcceptorCount个tcp_acceptor子进程,通过supervisor:start_child/2方法:
' `! k: D& ^! f# ~
%%创建ConcurrentAcceptorCount个并发acceptor
lists:foreach(fun (_) -> 1 E4 Dv/ N% [" H9 S7 ?7 s5 B
{ok, _APid} = supervisor:start_child(
AcceptorSup, [LSock])

end,
lists:duplicate(ConcurrentAcceptorCount, dummy)),
! T( L' ?! @& um% |~5 j+ a' S. w
注意到,这里调用的start_child只传入了 LSock一个参数,另一个参数CallBack是在定义child spec的时候传入的,参见tcp_acceptor_sup.erl:
init(Callback) -> " l5 Z/ s8 c9 \2 H( z% w
{ok, {{simple_one_for_one, 10, 10},
[{tcp_acceptor, {tcp_acceptor, start_link, [ Callback]}, # F$ F* M) E8 g/ J
transient, brutal_kill, worker, [tcp_acceptor]}]}}. ! O; |3 {. _; E% N0 c* L. Z
# k7 [3 Z7 r3 E7 v# R' y
Erlang内部自动为simple_one_for_one做了 参数合并,最后调用的是tcp_acceptor的init/2: * [5 D6 v( k8 W7 |" |

init({ Callback, LSock}) -> 1 F% g( \/ S- v1 L' u6 ?4 R
case prim_inet:async_accept(LSock, -1) of
{ok, Ref} -> {ok, #state{callback=Callback, sock=LSock, ref=Ref}}; " U) N5 }9 b& Z# L$ m8 B- {
Error -> {stop, {cannot_accept, Error}}
end. 2 h! b& s7 d5 J) @& \! ?

对于tcp_client_sup的情况类似,tcp_client_sup监控的子进程都是rabbit_reader类型,在 rabbit_networking.erl中启动tcp_listenner传入的处理connect事件的回调方法是是 rabbit_networking:start_client/1:

start_tcp_listener(Host, Port) -> 7 [# P/ `# Y' D) i0 w. ~
start_listener(Host, Port, "TCP Listener", 3 y3 T2 \4 k8 h9 W( ^
%回调的MFA ( o( E) {' O0 Q
{ ?MODULE, start_client, []}). 6 h1 h; c4 K+ F7 H8 @9 z# t

start_client(Sock) -> * ]; N: h+ Q, _1 F9 O5 S
{ok, Child} = supervisor:start_child(rabbit_tcp_client_sup, []),
ok = rabbit_net:controlling_process(Sock, Child),
Child ! {go, Sock},
Child. 9 Oh; t1 i1 e% \: p" n# e

start_client调用了supervisor:start_child/2来动态启动rabbit_reader进程
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值