erlang mysql driver_erlang_mysql_driver 源码分析2
pool模型
探究erlang_mysql_driver對同一時(shí)刻大量請求的支持
mysql:fetch 和 mysql_conn
今天看到網(wǎng)絡(luò)上的一篇文章,說erlang_mysql_driver的連接池實(shí)際上是沒有意義的。
大概意思是,我們使用mysql:fetch去執(zhí)行sql語句,mysql:fetch會call一條消息到mysql_dispatcher進(jìn)程中。所以當(dāng)我們同一時(shí)刻大量調(diào)用mysql:fetch的時(shí)候,mysql_dispatcher中就會有多條call消息在阻塞在消息隊(duì)列里,那么后面調(diào)用的進(jìn)程必須等待,每個(gè)請求需要等待上一個(gè)請求執(zhí)行結(jié)束后才能開始執(zhí)行,所以雖然mysql_dispatcher背后有多個(gè)連接進(jìn)程(mysql_conn)但是他們并沒有起到并發(fā)使用的作用。
乍一看,好像挺有道理的。但是我又覺得不對勁,畢竟作者不至于挖個(gè)這么大的坑吧,于是測試了一下。
同一時(shí)刻,spawn 10萬個(gè)進(jìn)程,每個(gè)進(jìn)程都調(diào)用mysql:fetch進(jìn)行數(shù)據(jù)庫查詢。
按上面的說法,那么這個(gè)時(shí)候應(yīng)該會有大量的消息阻塞在mysql_dispatcher中,測試結(jié)果卻是mysql_dispatcher的消息隊(duì)列很快就處理完了。也就是mysql_dispatcher很快就把這些消息分發(fā)給了mysql_conn,這里我建立9個(gè)mysql_conn進(jìn)程。然后大部分的消息(10萬個(gè)請求)被堆積在9個(gè)mysql_conn進(jìn)程的消息隊(duì)列中,而且每個(gè)mysql_conn收到的消息是平均的。
證明我們的mysql_dispatcher還是能夠順利完成任務(wù)的,而且可以看出mysql_dispatcher處理這些消息肯定只有簡單的分發(fā)消息,沒有涉及數(shù)據(jù)io過程的。
gen_server:call gen_server:reply
那么上面講到mysql:fetch不是調(diào)用了gen_server:call嗎,而gen_server:call確實(shí)是會阻塞的。但是這里阻塞的是調(diào)用者的進(jìn)程,也就是我spawn出來的那些進(jìn)程。而mysql_dispatcher對于這些消息的處理是非常快的,沒有涉及到數(shù)據(jù)的io過程。
fetch_queries(PoolId, From, State, QueryList) ->
with_next_conn(
PoolId, State,
fun(Conn, State1) ->
Pid = Conn#conn.pid,
mysql_conn:fetch(Pid, QueryList, From),
{noreply, State1}
end).
mysql_dispatcher僅僅將消息轉(zhuǎn)發(fā)給合適的mysql_conn,然后返回{noreply, NewState}。看好了,這里是noreply,所以業(yè)務(wù)進(jìn)程調(diào)用mysql:fetch并不能在這里獲得返回,這個(gè)時(shí)候業(yè)務(wù)進(jìn)程還屬于繼續(xù)阻塞狀態(tài)。
那么mysql:fetch的返回結(jié)果是從哪里得到?
mysql:fetch獲得的返回結(jié)果是通過mysql_conn 使用gen_server:reply返回給調(diào)用進(jìn)程的。
%% GenSrvFrom is either a gen_server:call/3 From term(),
%% or a pid if no gen_server was used to make the query
send_reply(GenSrvFrom, Res) when is_pid(GenSrvFrom) ->
%% The query was not sent using gen_server mechanisms
GenSrvFrom ! {fetch_result, self(), Res};
send_reply(GenSrvFrom, Res) ->
gen_server:reply(GenSrvFrom, Res).
這里可能會有一個(gè)疑問就是,mysql_conn如何找到mysql:fetch 的調(diào)用進(jìn)程并且正確地將值返回給他,如果在調(diào)用進(jìn)程等待的返回值期間,先收到其他返回值怎么辦?
關(guān)于這個(gè)問題,要查詢官方文檔上關(guān)于gen_server:call的說法。當(dāng)使用gen_server:call向某一指定的進(jìn)程發(fā)送call消息的時(shí)候,收到消息的一方是這樣處理的 :Module:handle_call(Request, From, State)。
讓我們再看下文檔,From is a tuple {Pid, Tag} where pid is the client and Tag is a unique tag.
如果收到handle_call的一方,使用{reply, Reply, State}返回,那么Reply will be given back to From as the return value of call/2,3。但是問題來了,我們的mysql_dispatcher并沒有使用常規(guī)手段,他直接返回{noreply, NewState}。那么mysql:fetch的調(diào)用不是收不到返回值了,不要急,文檔說了 if the function returns {noreply, NewState}, Any reply to From must be given explicitly using gen_server:reply/2。
問題又來了,難道m(xù)ysql_dispatcher沒有使用gen_server:reply?確實(shí)沒有!但是他把From直接傳遞給了mysql_conn,最終是mysql_conn查詢結(jié)束后使用gen_server:reply,把結(jié)果最終返回給了阻塞在mysql:fetch中的業(yè)務(wù)進(jìn)程
所以,在erlang_mysql_driver的連接池中一開始建立多個(gè)連接,在面對大量請求的時(shí)候,確實(shí)是有幫助的,可以多個(gè)連接同時(shí)執(zhí)行,最終的io壓力會放在這幾個(gè)連接進(jìn)程上,mysql_dispatcher頂多就是要維護(hù)的進(jìn)程池有點(diǎn)大罷了。
大并發(fā)執(zhí)行fetch是否會有大量timeout 報(bào)錯(cuò)?
這個(gè)是題外話了,在測試的時(shí)候遇到的問題。因?yàn)楫吘怪挥?0個(gè)連接來處理10萬個(gè)請求,那么后面的幾萬個(gè)請求肯定要排隊(duì)到好久之后的。這個(gè)時(shí)間一旦超過了設(shè)置的timeout時(shí)間,那么就會有timeout報(bào)錯(cuò)。 然而測試開始的時(shí)候,我沒有看到timeout報(bào)錯(cuò),一直很疑惑。后來發(fā)現(xiàn)是timeout報(bào)錯(cuò)導(dǎo)致業(yè)務(wù)進(jìn)程直接掛了,已經(jīng)沒法打印報(bào)錯(cuò)出來了。 在上面的測試中,只有9個(gè)mysql_conn,同一時(shí)刻卻要處理10萬條sql。那么肯定會有其他大量的調(diào)用一直處于阻塞狀態(tài)的,我們使用mysql:fetch(PoolId, Query)的形式查詢,而mysql:fetch其實(shí)是封裝了gen_server call,這個(gè)方法默認(rèn)的timeout時(shí)間是5秒。如果在5秒內(nèi)沒有收到返回值,就會扔出一個(gè)timeout的錯(cuò)誤。而如果不去catch這個(gè)錯(cuò)誤,進(jìn)程就直接掛了,那么錯(cuò)誤也打印不出來了。 在測試中,使用mysql:fetch(PoolId, Query)的調(diào)用,剛開始的進(jìn)程能收到返回值,但是5秒后,進(jìn)程就只能收到timeout報(bào)錯(cuò)了。 另外 使用mysql:fetch(PoolId, Query, infinity)的調(diào)用,進(jìn)程會一直等待,測試表明雖然有很多的sql查詢請求,每個(gè)mysql_conn都收到了1萬左右的消息,但是最終都能執(zhí)行并返回結(jié)果。
總結(jié)
以上是生活随笔為你收集整理的erlang mysql driver_erlang_mysql_driver 源码分析2的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python解析html模块_Pytho
- 下一篇: mysql bin日志备份_mysql之