erlang mysql driver_erlang_mysql_driver 源码分析2
pool模型
探究erlang_mysql_driver對同一時刻大量請求的支持
mysql:fetch 和 mysql_conn
今天看到網絡上的一篇文章,說erlang_mysql_driver的連接池實際上是沒有意義的。
大概意思是,我們使用mysql:fetch去執行sql語句,mysql:fetch會call一條消息到mysql_dispatcher進程中。所以當我們同一時刻大量調用mysql:fetch的時候,mysql_dispatcher中就會有多條call消息在阻塞在消息隊列里,那么后面調用的進程必須等待,每個請求需要等待上一個請求執行結束后才能開始執行,所以雖然mysql_dispatcher背后有多個連接進程(mysql_conn)但是他們并沒有起到并發使用的作用。
乍一看,好像挺有道理的。但是我又覺得不對勁,畢竟作者不至于挖個這么大的坑吧,于是測試了一下。
同一時刻,spawn 10萬個進程,每個進程都調用mysql:fetch進行數據庫查詢。
按上面的說法,那么這個時候應該會有大量的消息阻塞在mysql_dispatcher中,測試結果卻是mysql_dispatcher的消息隊列很快就處理完了。也就是mysql_dispatcher很快就把這些消息分發給了mysql_conn,這里我建立9個mysql_conn進程。然后大部分的消息(10萬個請求)被堆積在9個mysql_conn進程的消息隊列中,而且每個mysql_conn收到的消息是平均的。
證明我們的mysql_dispatcher還是能夠順利完成任務的,而且可以看出mysql_dispatcher處理這些消息肯定只有簡單的分發消息,沒有涉及數據io過程的。
gen_server:call gen_server:reply
那么上面講到mysql:fetch不是調用了gen_server:call嗎,而gen_server:call確實是會阻塞的。但是這里阻塞的是調用者的進程,也就是我spawn出來的那些進程。而mysql_dispatcher對于這些消息的處理是非常快的,沒有涉及到數據的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僅僅將消息轉發給合適的mysql_conn,然后返回{noreply, NewState}。看好了,這里是noreply,所以業務進程調用mysql:fetch并不能在這里獲得返回,這個時候業務進程還屬于繼續阻塞狀態。
那么mysql:fetch的返回結果是從哪里得到?
mysql:fetch獲得的返回結果是通過mysql_conn 使用gen_server:reply返回給調用進程的。
%% 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).
這里可能會有一個疑問就是,mysql_conn如何找到mysql:fetch 的調用進程并且正確地將值返回給他,如果在調用進程等待的返回值期間,先收到其他返回值怎么辦?
關于這個問題,要查詢官方文檔上關于gen_server:call的說法。當使用gen_server:call向某一指定的進程發送call消息的時候,收到消息的一方是這樣處理的 :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并沒有使用常規手段,他直接返回{noreply, NewState}。那么mysql:fetch的調用不是收不到返回值了,不要急,文檔說了 if the function returns {noreply, NewState}, Any reply to From must be given explicitly using gen_server:reply/2。
問題又來了,難道mysql_dispatcher沒有使用gen_server:reply?確實沒有!但是他把From直接傳遞給了mysql_conn,最終是mysql_conn查詢結束后使用gen_server:reply,把結果最終返回給了阻塞在mysql:fetch中的業務進程
所以,在erlang_mysql_driver的連接池中一開始建立多個連接,在面對大量請求的時候,確實是有幫助的,可以多個連接同時執行,最終的io壓力會放在這幾個連接進程上,mysql_dispatcher頂多就是要維護的進程池有點大罷了。
大并發執行fetch是否會有大量timeout 報錯?
這個是題外話了,在測試的時候遇到的問題。因為畢竟只有10個連接來處理10萬個請求,那么后面的幾萬個請求肯定要排隊到好久之后的。這個時間一旦超過了設置的timeout時間,那么就會有timeout報錯。 然而測試開始的時候,我沒有看到timeout報錯,一直很疑惑。后來發現是timeout報錯導致業務進程直接掛了,已經沒法打印報錯出來了。 在上面的測試中,只有9個mysql_conn,同一時刻卻要處理10萬條sql。那么肯定會有其他大量的調用一直處于阻塞狀態的,我們使用mysql:fetch(PoolId, Query)的形式查詢,而mysql:fetch其實是封裝了gen_server call,這個方法默認的timeout時間是5秒。如果在5秒內沒有收到返回值,就會扔出一個timeout的錯誤。而如果不去catch這個錯誤,進程就直接掛了,那么錯誤也打印不出來了。 在測試中,使用mysql:fetch(PoolId, Query)的調用,剛開始的進程能收到返回值,但是5秒后,進程就只能收到timeout報錯了。 另外 使用mysql:fetch(PoolId, Query, infinity)的調用,進程會一直等待,測試表明雖然有很多的sql查詢請求,每個mysql_conn都收到了1萬左右的消息,但是最終都能執行并返回結果。
總結
以上是生活随笔為你收集整理的erlang mysql driver_erlang_mysql_driver 源码分析2的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python解析html模块_Pytho
- 下一篇: mysql bin日志备份_mysql之