莊周夢蝶

          生活、程序、未來
             :: 首頁 ::  ::  :: 聚合  :: 管理

              最近在鋒爺的建議下開始讀rabbitmq的源碼,鋒爺說這個項目已經很成熟,并且代碼也很有借鑒和學習的意義,在自己寫erlang代碼之前看看別人是怎么寫的,可以少走彎路,避免養成一些不好的習慣,學習一些最佳實踐。讀了一個星期,這個項目果然非常棒,代碼也寫的非常清晰易懂,一些細節的處理上非常巧妙,比如我這里想分享的網絡層一節。
              Rabbitmq是一個MQ系統,也就是消息中間件,它實現了AMQP 0.8規范,簡單來說就是一個TCP的廣播服務器。AMQP協議,你可以類比JMS,不過JMS僅僅是java領域內的API規范,而AMQP比JMS更進一步,它有自己的wire-level protocol,有一套可編程的協議,中立于語言。簡單介紹了Rabbitmq之后,進入正題。
              Rabbitmq充分利用了Erlang的分布式、高可靠性、并發等特性,首先看它的一個結構圖:


          這張圖展現了Rabbitmq的主要組件和組件之間的關系,具體到監控樹的結構,我畫了一張圖:







              頂層是rabbit_sup supervisor,它至少有兩個子進程,一個是rabbit_tcp_client_sup,用來監控每個connection的處理進程 rabbit_reader的supervisor;rabbit_tcp_listener_sup是監控tcp_listener和 tcp_acceptor_sup的supervisor,tcp_listener里啟動tcp服務器,監聽端口,并且通過tcp_acceptor_sup啟動N個tcp_accetpor,tcp_acceptor發起accept請求,等待客戶端連接;tcp_acceptor_sup負責監控這些acceptor。這張圖已經能給你一個大體的印象。
             
              講完大概,進入細節,說說幾個我覺的值的注意的地方:
          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}) ->
              %%這里做了patch
              %% patch up the socket so it looks like one we got from
              %% gen_tcp:accept/1
              {ok, Mod} = inet_db:lookup_socket(LSock),
              inet_db:register_socket(Sock, Mod),

              try
                  %% report
                  {Address, Port}         = inet_op(fun () -> inet:sockname(LSock) end),
                  {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,
                                         inet_parse:ntoa(PeerAddress), PeerPort]),
                  %% 調用回調模塊,將Sock作為附加參數
                  apply(M, F, A ++ [Sock])
              catch {inet_error, Reason} ->
                      gen_tcp:close(Sock),
                      error_logger:error_msg("unable to accept TCP connection: ~p~n",
                                             [Reason])
              end,

              %% 繼續發起異步調用
              case prim_inet:async_accept(LSock, -1) of
                  {ok, NRef} -> {noreply, State#state{ref=NRef}};
                  Error -> {stop, {cannot_accept, Error}, none}
              end;
          %%處理錯誤情況
          handle_info({inet_async, LSock, Ref, {error, closed}},
                      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};

          2、rabbitmq內部是使用了多個并發acceptor,這在高并發下、大量連接情況下有效率優勢,類似java現在的nio框架采用多個reactor類似,查看tcp_listener.erl:

          init({IPAddress, Port, SocketOpts,
                ConcurrentAcceptorCount, AcceptorSup,
                {M,F,A} = OnStartup, OnShutdown, Label}) ->
              process_flag(trap_exit, true),
              case gen_tcp:listen(Port, SocketOpts ++ [{ip, IPAddress},
                                                       {active, false}]) of
                  {ok, LSock} ->
                       %%創建ConcurrentAcceptorCount個并發acceptor
                      lists:foreach(fun (_) ->
                                            {ok, _APid} = supervisor:start_child(
                                                            AcceptorSup, [LSock])
                                    end,
                                    lists:duplicate(ConcurrentAcceptorCount, dummy)),

                      {ok, {LIPAddress, LPort}} = inet:sockname(LSock),
                      error_logger:info_msg("started ~s on ~s:~p~n",
                                            [Label, inet_parse:ntoa(LIPAddress), LPort]),
                      %%調用初始化回調函數
                      apply(M, F, A ++ [IPAddress, Port]),
                      {ok, #state{sock = LSock,
                                  on_startup = OnStartup, on_shutdown = OnShutdown,
                                  label = Label}};
                  {error, Reason} ->
                      error_logger:error_msg(
                        "failed to start ~s on ~s:~p - ~p~n",
                        [Label, inet_parse:ntoa(IPAddress), Port, Reason]),
                      {stop, {cannot_listen, IPAddress, Port, Reason}}
              end.

          這里有一個技巧,如果要循環N次執行某個函數F,可以通過lists:foreach結合lists:duplicate(N,dummy)來處理。

          lists:foreach(fun(_)-> F() end,lists:duplicate(N,dummy)).

          3、simple_one_for_one策略的使用,可以看到對于tcp_client_sup和tcp_acceptor_sup都采用了simple_one_for_one策略,而非普通的one_fo_one,這是為什么呢?
          這牽扯到simple_one_for_one的幾個特點:
          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

          3)start_child/2 對于simple_one_for_one來說,不必傳入完整的child spect,傳入參數list,會自動進行參數合并在一個地方定義好child spec之后,其他地方只要start_child傳入參數即可啟動child進程,簡化child都是同一類型進程情況下的編程

          在 rabbitmq中,tcp_acceptor_sup的子進程都是tcp_acceptor進程,在tcp_listener中是啟動了 ConcurrentAcceptorCount個tcp_acceptor子進程,通過supervisor:start_child/2方法:

          %%創建ConcurrentAcceptorCount個并發acceptor
                      lists:foreach(fun (_) ->
                                            {ok, _APid} = supervisor:start_child(
                                                            AcceptorSup, [
          LSock])
                                    end,
                                    lists:duplicate(ConcurrentAcceptorCount, dummy)),

          注意到,這里調用的start_child只傳入了LSock一個參數,另一個參數CallBack是在定義child spec的時候傳入的,參見tcp_acceptor_sup.erl:
          init(Callback) ->
              {ok, {{simple_one_for_one, 10, 10},
                    [{tcp_acceptor, {tcp_acceptor, start_link, [Callback]},
                      transient, brutal_kill, worker, [tcp_acceptor]}]}}.

          Erlang內部自動為simple_one_for_one做了參數合并,最后調用的是tcp_acceptor的init/2:

          init({Callback, LSock}) ->
              case prim_inet:async_accept(LSock, -1) of
                  {ok, Ref} -> {ok, #state{callback=Callback, sock=LSock, ref=Ref}};
                  Error -> {stop, {cannot_accept, Error}}
              end.

          對于tcp_client_sup的情況類似,tcp_client_sup監控的子進程都是rabbit_reader類型,在 rabbit_networking.erl中啟動tcp_listenner傳入的處理connect事件的回調方法是是 rabbit_networking:start_client/1:

          start_tcp_listener(Host, Port) ->
              start_listener(Host, Port, "TCP Listener",
                             %回調的MFA
                             {?MODULE, start_client, []}).

          start_client(Sock) ->
              {ok, Child} = supervisor:start_child(rabbit_tcp_client_sup, []),
              ok = rabbit_net:controlling_process(Sock, Child),
              Child ! {go, Sock},
              Child.

          start_client調用了supervisor:start_child/2來動態啟動rabbit_reader進程。

          4、協議的解析,消息的讀取這部分也非常巧妙,這一部分主要在rabbit_reader.erl中,對于協議的解析沒有采用gen_fsm,而是實現了一個巧妙的狀態機機制,核心代碼在mainloop/4中:
          %啟動一個連接
          start_connection(Parent, Deb, ClientSock) ->
              process_flag(trap_exit, true),
              {PeerAddressS, PeerPort} = peername(ClientSock),
              ProfilingValue = setup_profiling(),
              try
                  rabbit_log:info("starting TCP connection ~p from ~s:~p~n",
                                  [self(), PeerAddressS, PeerPort]),
                   %延時發送握手協議
                  Erlang:send_after(?HANDSHAKE_TIMEOUT * 1000, self(),
                                    handshake_timeout),
                  %進入主循環,更換callback模塊,魔法就在這個switch_callback
                  mainloop(Parent, Deb, switch_callback(
                                          #v1{sock = ClientSock,
                                              connection = #connection{
                                                user = none,
                                                timeout_sec = ?HANDSHAKE_TIMEOUT,
                                                frame_max = ?FRAME_MIN_SIZE,
                                                vhost = none},
                                              callback = uninitialized_callback,
                                              recv_ref = none,
                                              connection_state = pre_init},
                                          %%注意到這里,handshake就是我們的回調模塊,8就是希望接收的數據長度,AMQP協議頭的八個字節。
                                          handshake, 8))

          魔法就在switch_callback這個方法上:
          switch_callback(OldState, NewCallback, Length) ->
              %發起一個異步recv請求,請求Length字節的數據
              Ref = inet_op(fun () -> rabbit_net:async_recv(
                                        OldState#v1.sock, Length, infinity) end),
              %更新狀態,替換ref和處理模塊
              OldState#v1{callback = NewCallback,
                          recv_ref = Ref}.


          異步接收Length個數據,如果有,erlang會通知你處理。處理模塊是什么概念呢?其實就是一個狀態的概念,表示當前協議解析進行到哪一步,起一個label的作用,看看mainloop/4中的應用:

          mainloop(Parent, Deb, State = #v1{sock= Sock, recv_ref = Ref}) ->
              %%?LOGDEBUG("Reader mainloop: ~p bytes available, need ~p~n", [HaveBytes, WaitUntilNBytes]),
              receive
                  %%接收到數據,交給handle_input處理,注意handle_input的第一個參數就是callback
                  {inet_async, Sock, Ref, {ok, Data}} ->
                      %handle_input處理
                      {State1, Callback1, Length1} =
                          handle_input(State#v1.callback, Data,
                                       State#v1{recv_ref = none}),

                      %更新回調模塊,再次發起異步請求,并進入主循環
                      mainloop(Parent, Deb,
                               switch_callback(State1, Callback1, Length1));


          handle_input有多個分支,每個分支都對應一個處理模塊,例如我們剛才提到的握手協議:

          %handshake模塊,注意到第一個參數,第二個參數就是我們得到的數據
          handle_input(handshake, <<"AMQP",1,1,ProtocolMajor,ProtocolMinor>>,
                       State = #v1{sock = Sock, connection = Connection}) ->
               %檢測協議是否兼容
              case check_version({ProtocolMajor, ProtocolMinor},
                                 {?PROTOCOL_VERSION_MAJOR, ?PROTOCOL_VERSION_MINOR}) of
                  true ->
                      {ok, Product} = application:get_key(id),
                      {ok, Version} = application:get_key(vsn),
                      %兼容的話,進入connections start,協商參數
                      ok = send_on_channel0(
                             Sock,
                             #'connection.start'{
                               version_major = ?PROTOCOL_VERSION_MAJOR,
                               version_minor = ?PROTOCOL_VERSION_MINOR,
                               server_properties =
                               [{list_to_binary(K), longstr, list_to_binary(V)} ||
                                   {K, V} <-
                                       [{"product",     Product},
                                        {"version",     Version},
                                        {"platform",    "                               {"copyright",   ?COPYRIGHT_MESSAGE},
                                        {"information", ?INFORMATION_MESSAGE}]],
                               mechanisms = <<"PLAIN AMQPLAIN">>,
                               locales = <<"en_US">> }),
                      {State#v1{connection = Connection#connection{
                                               timeout_sec = ?NORMAL_TIMEOUT},
                                connection_state = starting},
                       frame_header, 7};
                   %否則,斷開連接,返回可以接受的協議
                  false ->
                      throw({bad_version, ProtocolMajor, ProtocolMinor})
              end;

              其他協議的處理也是類似,通過動態替換callback的方式來模擬狀態機做協議的解析和數據的接收,真的很巧妙!讓我們體會到Erlang的魅力,FP的魅力。

          5、序列圖:
          1)tcp server的啟動過程:

          2)一個client連接上來的處理過程:


              小結:從上面的分析可以看出,rabbitmq的網絡層是非常健壯和高效的,通過層層監控,對每個可能出現的風險點都做了考慮,并且利用了prim_net模塊做異步IO處理。分層也是很清晰,將業務處理模塊隔離到client_sup監控下的子進程,將網絡處理細節和業務邏輯分離。在協議的解析和業務處理上雖然沒有采用gen_fsm,但是也實現了一套類似的狀態機機制,通過動態替換Callback來模擬狀態的變遷,非常巧妙。如果你要實現一個tcp server,強烈推薦從rabbitmq中扣出這個網絡層,你只需要實現自己的業務處理模塊即可擁有一個高效、健壯、分層清晰的TCP服務器。

          posted @ 2009-11-29 12:00 dennis 閱讀(11901) | 評論 (8)編輯 收藏

              入這行也有四年了,從過去對軟件開發的懵懂狀態,到現在可以算是有一個初步認識的過程,期間也參與了不少項目,從一開始單純的編碼,到現在可以部分地參與一些設計方案的討論,慢慢對設計方案的評判標準有一點感受。讀軟件工程、架構設計、模式之類的書,對于書中強調的一些標準和原則的感受只是感官淺層的印象,你可以一股腦從嘴里蹦出一堆詞,什么開閉原則、依賴倒轉、針對接口編程、系統的可伸縮性、可維護性、可重用等等,也僅僅停留在知道的份子上。不過現在,我對一個設計方案的評價標準慢慢變的很明確:簡單、符合當前和一定預期時間內的需求、可靠、直觀(或者說透明)
             簡單,不是簡陋,這是廢話了。但是要做到簡單,卻是絕不簡單,簡單跟第四點直觀有直接的關系,簡單的設計就是一個直觀的設計,可以讓你一眼看得清的設計方案,也就是透明。一個最簡單的評判方法,你可以講這個方案講給一個局外人聽,如果能在短時間內讓人理解的,那么這個方案八成是靠譜的。記的有本書上講過類似的方法,你有一個方案,那就拿起電話打給一個無關的人員,如果你能在電話里說清楚,就表示這個方案相當靠譜,如果不能,那么這個方案很可能是過度復雜,過度設計了。
             簡單的設計,往往最后得出的結果是一個可靠性非常高的系統。這很容易理解,一個復雜的設計方案,有很多方面會導致最后的實現會更復雜:首先是溝通上的困難,一個復雜的方案是很難在短時間內在團隊內部溝通清楚,每個開發人員對這個方案的理解可能有偏差;其次,復雜的方案往往非常考驗設計人員和開發人員的經驗、能力和細致程度,復雜的方案要考量的方面肯定比簡單方案多得多,一個地方沒有考慮到或者不全面,結果就是一個充滿了隱患的系統,時不時地蹦出一個BUG來惡心你,這并非開發人員的能力問題,而是人腦天然的局限性(天才除外,咳咳)。
             第二點,符合當前和一定預期時間內的需求。我們都知道,不變的變化本身,指望一個方案永久解決所有問題是烏托邦的夢想。復雜方案的出爐通常都是因為設計人員過度考量了未來系統的需求和變化,我們的系統以后要達到10倍的吞吐量,我們的系統以后要有幾十萬的節點等等。當然,首先要肯定的是對未來需求的考量是必需的,一個系統如果實現出來只能應付短時間的需求,那肯定是不能接受的。但是我們要警惕的是過度考量導致的過度復雜的設計方案,這還有可能是設計人員“炫技”的欲望隱藏在里頭。這里面有一個權衡的問題,比如這里有兩個方案:一個是兩三年內絕對實用的方案,簡單并且可靠直觀,未來的改進余地也不錯;另一個方案是可以承載當前的幾十倍流量的方案,方案看起來很優雅,很時尚,實現起來也相對復雜。如何選擇?如果這個系統是我們當前就要使用的,并且是關鍵系統,那么我絕對會選擇前一個方案,在預期時間內去改進這個方案,因為對于關鍵系統,簡單和可靠是性命攸關的。況且,我堅定地認為一個復雜的設計方案中絕對隱藏著一個簡單的設計,這就像一個復雜的數學證明,通常都可以用更直觀更簡單的方式重新證明(題外話,費爾馬大定理的證明是極其復雜的,現在還有很多人堅信有一個直觀簡單的證明存在,也就是那個費爾馬沒有寫下的證明)。最近我們的一個方案討論也證明了這一點,一個消息優先級的方案,一開始的設想是相對復雜的,需要在存儲結構和調度上動手腳,后來集思廣益,最后定下的方案非常類似linux的進程調度策略,通過分級queue和時間片輪詢的方式完美解決了優先級的問題。這讓我想起了軟件開發的“隱喻”的力量,很多東西都是相通相似的。
             上面這些亂彈都是自己在最近碰到的一些討論和系統故障想起的,想想還是有必要寫下來做個記錄。

             

          posted @ 2009-11-28 23:38 dennis 閱讀(1135) | 評論 (0)編輯 收藏

              Xmemcached 1.2.0發布到現在,從反饋來看,已經有不少用戶嘗試使用xmc作為他們的memcached client,并且1.2.0這個版本也比較穩定,沒有發現嚴重的BUG。Xmemcached下一個版本是1.2.1,初步計劃是在元旦左右發布,計劃做出的改進如下:

          1、重寫所有的單元測試和集成測試,提高代碼的健壯性
          2、新增一些功能,如 issue 66
          3、移除deprecated方法
          4、提供用戶指南。

             1.2.1之后初步的設想是開發1.3版本,現在xmc的最大問題是對yanf4j的依賴,耦合比較嚴重,1.3版本將抽象出網絡層,解耦yanf4j和xmc,yanf4j也將重構并引入filter機制。1.3版本也將發布一個支持unix domain socket的附帶項目,事實上這個項目已經初步開發完成,基于juds,但是性能并不理想,我的計劃是自己寫一個東西來替代juds,juds最大的問題是僅支持阻塞IO,沒有使用poll/epoll、select之類。

             總之,我可以確認的是xmc本身將繼續發展,也希望更多的朋友來嘗試使用,有任何問題和意見都可以反饋給我,我都將及時回復。



          posted @ 2009-11-25 16:19 dennis 閱讀(740) | 評論 (0)編輯 收藏

          update:修復了在linux firefox上不兼容的BUG。
            
             下午搞了個Erlang web shell,可以在web頁面上像eshell那樣進行交互式的Erlang編程,方便學習和測試。這樣一來,一個erlwsh就可以服務多個client,只要你有網絡和瀏覽器,隨時隨地可以敲上幾行erlang看看結果。代碼很簡單,就不多說了,有興趣的看看,通過mochiweb的http chunk編碼,client通過Ajax Post方式提交。眼見為實,看看運行截圖:





              工程在google code上: http://code.google.com/p/erlwsh/
             
              安裝很簡單,首先確保你已經安裝了Erlang,接下來:
          svn checkout http://erlwsh.googlecode.com/svn/trunk/ erlwsh-read-only
          cd erlwsh-read-only
          scripts/install_mochiweb.sh
          make
          ./start.sh

              因為需要使用mochiweb,所以提供了下載并自動安裝的腳本,這是litaocheng的大作。啟動后訪問 http://localhost:8000/shell 即可,have fun.



          posted @ 2009-11-19 19:22 dennis 閱讀(2318) | 評論 (0)編輯 收藏

              上篇是在興奮的狀態下當天晚上寫的,這下篇拖到現在,印象也開始有點模糊了,以下全憑記憶,如有謬誤敬請原諒。

              CN-Erlounge第二天的topic,一開始是來自汕頭的一位朋友介紹他對利用單機程序組建分布式模型的分析與實例,實話說這個Topic很一般,基本上文不對題,并且舉的例子沒有說服力,有點為了用Erlang而用Erlang的感覺,其實跟Erlang關系不大,并且用Erlang搭建原型的話,還不如用python、ruby腳本來搞,后來跟同事的交流說,他介紹的還只是作坊式的一些做法,沒有多少可借鑒的意義。
             接下來是侯明遠的《基于Erlang實現的MMO服務器連接管理服務》,也就是Erlang在他的網游項目替換c++的一些嘗試,效果非常好,不僅代碼量大大減少,而且維護起來也非常容易。特別是他介紹了用Erlang搭建測試環境的嘗試給了我們不少啟發,事實上在回來后,我也嘗試用Erlang寫了個用于壓測的代理服務器,不過由于我們的client仍然是Java,無法做到類似的分布式壓測管理,僅用Erlang做中心的代理轉發服務器。感受是Erlang做網絡編程確實非常容易,Erlang的網絡層將底層封裝的非常完美,對于用戶來說完全屏蔽了網絡編程的復雜細節,并且提供了gen_server、gen_fsm這樣的基礎設施,寧外Erlang對binary數據的操作非常容易,對協議解析來說也是個巨大優勢,整個程序就200多行代碼,這還包括了一個通用的tcp服務器框架,借鑒了mochiweb的socket server實現。過去我對Erlang的message passing風格的理解還局限在actor模型上,進程之間相互發送消息,而其實Erlang的消息傳遞風格還體現在語言本身以及整個otp庫的實現上,例如在accept一個連接后,我們調用服務器的邏輯代碼:
          accept_loop({Server, LSocket, M}) ->
                 {ok, Socket} 
          = gen_tcp:accept(LSocket),
                 
          % spawn a new process to accept
                 gen_server:cast(Server, {accepted, self()}),
                 
          % initialize
                 State
          =M:init(Socket),
                 M:loop(State,Socket).

             其中的M是你的邏輯模塊,我們直接調用M:loop(State,Socket)進入邏輯模塊的處理,這里的init和loop方法都是約定,或者說模塊的回調方法,你也可以理解成Ruby的duck typing。我不知道M有沒有這兩個方法,我只是嘗試給它們傳遞消息(調用),等待響應,這同樣是消息傳遞風格。同樣,理解gen_server這樣的behaviour的關鍵也是回調,你只要實現這些behaviour的回調方法,響應這些模塊的消息,你將天然地擁有它們的強大功能。
             
              接下來是周愛民的《談談erlang網絡環境下的幾種數據流轉形式》,怎么說呢,我聽的懂,但是似乎沒有抓到key point,老大們理解問題、分析問題的層次似乎不同了。不過其中講到如何解決異步的通訊順序問題對我們有一定借鑒價值。聽了快兩天的課,非常疲倦,加上頭天晚上沒睡好,這上午稀里嘩啦就過了,中午組委會提供披薩,實話說好難吃啊,口味不慣。

              壓軸的是阿里云老吳的《XEngine介紹》,第一次聽說xengine是在阿里云計算公司的成立展覽上,我跟開發人員有個短暫的交流,大概明白Erlang在xengine中扮演的角色。XEngine的野心很大,做中國的EC2、AppEngine,Erlang在其中的角色扮演了監控和協調的作用,利用它天然的分布式編程模型,xengine需要依賴阿里云的飛天計劃,涵蓋了分布式文件系統、MQ、通訊組件、分布式持久層等等,這些基礎設施沒搞好,xengine 還只能是“云”。集團內部早有消息是希望能統一集團內的各種基礎設施,包括我們現在的這個MQ系統,我跟老吳開玩笑說他們做好了我們就要失業了:)。說到云計算,新浪的app engine據說已經開始內部測試了。那天我們老大還在說貌似國內只有阿里在搞app engine,沒想到新浪倒走到前面咯。

              后來是提議到公園去走走,大家隨意聊聊,因為比較累以及跟各位大佬們不熟,阿寶朱GG他們為了趕飛機也提早走了,我們三個就提前撤退咯。杭州的出租車3點半交班,加上舉辦馬拉松,打不到車,走了N遠的路才坐到公交回家,到家天色剛晚。

              CN-Erlounge IV的質量是我參加過的技術會議里面最高的,不過我其實沒參加過多少技術會議,哈哈。總結下感受,從CN-Erlounge的Topic來看,已經有很多公司在實踐中應用Erlang,我問arbow這一屆Erlang大會跟過去的區別(過去我沒參加 過),arbow就說這一屆的實踐經驗的分享相對比較多,一個側面也可以反應Erlang在國內的發展程度。不過Erlang還是小眾語言,這從參會的人數上可以看出來。搜狐、校內、阿里這樣的互聯網巨頭都開始嘗試Erlang,一方面可以證明Erlang這個平臺的吸引力,一方面也可以說明Erlang在國內已經開始進入實際應用階段,對于許多還在觀望的人來說,這是個好消息。

           

          posted @ 2009-11-13 17:30 dennis 閱讀(989) | 評論 (0)編輯 收藏

              今天和同事一起去參加了CN-Erlounge IV大會,大會的精彩程度超過我的預期,每個Topic都是精心準備并且非常實在,并且見到了很多只聞其名未見其人的大牛,比如傳說中的T1、許老大、莊表偉、周愛民老師等。我們3個太早去了,8點半到了會場,發現大多數還沒來,阿寶同學和鋒爺他們更是9點多才出的門,因此整個會議進程都相應推遲了。
             首先是校內網的成立濤做了《Erlang開發實踐》的演講,主題是一個典型的Erlang項目的開發流程、目錄結構、單元測試、集成測試、常見問題解決等的概括性介紹,并且他還特意寫了一個工程作為Sample,就是放在google code上的erlips,非常佩服這樣的專業精神。交流提到校內網已經部署了30個以上的Erlang節點做廣告推送系統。Topic本身非常實在,并且有實際的代碼和工程文件提供,可以作為了解Erlang開發基本過程的骨架工程。接下來是鋒爺的重頭戲《Erlang系統調優》,鋒爺的Topic其實超出了Erlang的范疇,對所有系統的性能調優都有借鑒的意義,主題本身將Erlang平臺和unix操作系統做了比較,認為Erlang本身也是個OS平臺,并且介紹了Erlang提供的方方面面的工具,包括調試、診斷、性能剖析、監控、語言級別的優化、系統的優化選項、協議的選型、應該避免的陷阱、最佳實踐等等,你不得不承認Erlang實在是太牛x了,這樣的平臺難怪能做到7個9的可靠性。這個Topic非常精彩,從ppt本身能看到的只是局部信息,等有視頻的時候準備重新看看。

             中午組委會提供了午餐,還是很方便,會議的地點吃飯地方不好找,不過晚飯沒提供,我們跑了不遠的路找了家小飯館解決晚飯問題。下午的Topic一開始是餓狼戰役的創建者老范的介紹 ,餓狼戰役是一個Erlang編寫的棋牌型的游戲,玩家可以編寫自己的指揮進程參與競賽,實際上是作為一個Erlang學習的良好環境,類似過去非常流行的robot code游戲一樣。我因為跟阿寶他們去閑逛,錯過了大部分介紹,源碼已經讀過,不過我對AI一點也不了解,寫個程序干掉英格蘭衛兵還是沒問題的,哈哈。后來是python社區的大媽介紹了erlbattle社區的養成問題,談到了一個社區的生命周期問題,如何去建設一個技術社區,我沒有多少感受,不多扯了。餓狼戰役推薦去看看,如果你對AI或者erlang有興趣的話,可以去試試。接下來是T1做的《CUDA編程》的Topic,這個Topic我在提前看ppt的時候就覺的估計自己完全聽不懂,最后果然如此,這是一個關于現在很熱門GPU編程的Topic,講述了如何在10ms內完成jpeg的壓縮的優化手段和編程技巧,最終的結果是2毫秒多一點就搞定了這個需求,T1介紹的非常詳細關于算法和技巧方面的細節,完全跟做工程的是兩個世界。T1大大是火星人,咱就不多說了,景仰就行了。

              晚上的兩個Topic都是關于如何在C++中借鑒Erlang來實現消息傳遞風格的編程,51.com的qiezi和崔博介紹了他們的actor框架,他們是基于協程和線程池調度來實現偽同步調用,可以實現類似Erlang的進程風格的消息傳遞,但是要做一個工作就是將類似socket讀寫、文件讀寫、sleep這樣的阻塞調用封裝一下,有異步io可以利用的就利用異步IO,沒有的就使用線程池調度,事實上他們做的事情正是Erlang已經幫你做了,現場有很多爭議,認為這樣還不如使用Erlang,因為你這樣做無法避免兩個問題:協程的異常處理和阻塞調用可能導致整個進程內的協程不可用,畢竟協程是非搶占式的,并且無法充分利用多CPU的優勢。但是許老大提到,從工程實踐角度,Erlang畢竟還是小眾語言,維護和招人上都不容易,而系統的高可靠性可以從更高層次上去解決,而我們可以從Erlang借鑒這些做法來改進我們的傳統語言編程模型。許老大還提到他認為的Erlang編程模型的兩個缺點:一個是同步調用的死鎖問題,一個是資源進程的獨占性問題,這兩個問題最終還是要回歸到異步模型。這兩個問題,我認為其實是一個問題,還是由于資源的有限和獨占性引起的,像IO這樣的資源就是,你可以將請求并行化設置回調函數不阻塞調用本身,但是實際的IO讀寫本身仍然是串行的,只不過將這部分工作交給誰來做的問題,我覺的這個問題對于任何編程語言都是一樣的無法解決的。對于同步模型和異步模型本身,我更偏向于同步模型,這從xmc的API就可以看出來,同步模型更符合人類直覺,也易于使用,而異步模型容易將業務碎片化,不直觀并且使用上也不便利。

             以上是今天的流水賬,有興趣看到這的估計沒幾個,哇咔咔。

             補充,遺漏了一個香港老外作的topic演講,是關于erlang實現的restms,restms是一種restful的message協議,現在想來他主要介紹了restms協議以及一個erlang實現,也就是fireflymq,其中特別介紹了riak這樣一個key-value store,它類似amazon dynamo,同樣采用consistent hash,多節點備份,vector clock同步等等,比較特殊的地方是他可以將數據組織成類似web超鏈接形成的網狀結構并存儲和查詢。

          posted @ 2009-11-08 00:12 dennis 閱讀(1110) | 評論 (1)編輯 收藏

            看到這么一個題目:
              {3,2,2,6,7,8}排序輸出,7不在第二位,68不在一起。
           
            這樣的題目似乎避免不了遍歷,關鍵還在于過濾條件的安排,怎么讓過濾的范圍盡量地小。通常的做法是循環遍歷,對于類似Prolog這樣的語言來說,由于內置了推理引擎,可以簡單地描述問題,讓引擎來幫你做遞歸遍歷,解決這類問題是非常簡單的。Prolog好久沒寫,以Ruby的amb操作符為例來解決這道題目:

          #結果為hash,去重
          $hash={}
          amb
          =Amb.new
          array
          =[3,2,2,6,7,8]
          class << array
           alias remove delete
           
          def delete(*nums)
             result
          =dup
             nums.each do 
          |n|
              result.delete_at(result.index(n)) 
          if result.index(n)
             end
             result
           end
          end
          #從集合選元素
          one=amb.choose(*array)
          two
          =amb.choose(*(array.delete(one)))
          three
          =amb.choose(*(array.delete(one,two)))
          four
          =amb.choose(*(array.delete(one,two,three)))
          five
          =amb.choose(*(array.delete(one,two,three,four)))
          six
          =amb.choose(*(array.delete(one,two,three,four,five)))

          #條件1:第二個位置不能是7
          amb.require(two!=7)
          #條件2:6跟8不能一起出現
          def six_eight_not_join(a,b)
             
          "#{a}#{b}"!="68"&&"#{a}#{b}"!="86"
          end
          amb.require(six_eight_not_join(one,two))
          amb.require(six_eight_not_join(two,three))
          amb.require(six_eight_not_join(three,four))
          amb.require(six_eight_not_join(four,five))
          amb.require(six_eight_not_join(five,six))

          #條件3:不重復,利用全局hash判斷
          def distinct?(one,two,three,four,five,six)
            
          if $hash["#{one},#{two},#{three},#{four},#{five},#{six}"].nil?
               $hash[
          "#{one},#{two},#{three},#{four},#{five},#{six}"]=1 #記錄
               true
            
          else
               false
            end
          end
          amb.require(distinct?(one,two,three,four,five,six))
          puts 
          "#{one},#{two},#{three},#{four},#{five},#{six}"
          amb.failure


             三個條件的滿足通過amb.require來設置,這里安排的只是一種順序,可以根據實際測試結果來安排這些條件的順序以最大程度地提高效率。代碼注釋很清楚了,我就不多嘴了。Ruby amb的實現可以看這里。什么是amb可以看這個


          posted @ 2009-10-19 11:37 dennis 閱讀(3037) | 評論 (2)編輯 收藏

           
              我們小組還要招java方面的工程師和架構師,老大說還有兩個社招名額,不用就浪費了。我可以幫忙推薦,情況介紹如下:

          工作地點:杭州
          公司:阿里旗下子公司
          工作內容:消息中間件或者分布式持久框架
          要求:

          1、因為是社招,請應屆直接忽略,公司有校園招聘
          2、最好能有2年以上工作經驗(非硬性)
          3、java基礎牢固
          4、對java并發、網絡或者數據庫編程有豐富經驗,如果對JVM方面也了解,那更好
          5、有分布式系統開發和設計經驗尤佳
          6、有一定的性能調優經驗
          7、熟悉各種常用的開源框架
          8、對技術有追求、有激情,有氣度,能溝通,能交流。

          來這里你能得到什么:
          1、大規模分布式系統的設計和開發
          2、處理海量數據系統的設計與開發
          3、大量的技術交流機會
          4、相對輕松的、和諧的團隊和工作氛圍


          歡迎有興趣的朋友投遞簡歷,我的email : killme2008@gmail.com







          posted @ 2009-10-18 21:02 dennis 閱讀(891) | 評論 (3)編輯 收藏

              unix域協議并不是一個實際的協議族,而是在單個主機上執行客戶/服務器通信的一種方法,是IPC的方法之一,特定于*nix平臺。使用unix domain socket有三個好處:
          1)在同一主機上,unix domain socket比一般的tcp socket快上一倍,性能因素這是一個主要原因。
          2)unix domain socket可以在同一主機的不同進程之間傳遞文件描述符
          3)較新的unix domain socket實現把客戶的ID和組ID提供給服務器,可以讓服務器作安全檢查。

             memcached的FAQ中也提到為了安全驗證,可以考慮讓memcached監聽unix domain socket。Memcached支持這一點,可以通過-s選項指定unix domain socket的路徑名,注意,為了可移植性,盡量使用絕對路徑,因為Posix標準聲稱給unix domain socket綁定相對路徑將導致不可預計的后果,我在linux的測試是可以使用相對路徑。假設我將memcached綁定到/home/dennis/memcached,可以這樣啟動memcached:

          memcached -s /home/dennis/memcached


          端口呢?沒有端口了,/home/dennis/memcached這個文件你可以理解成FIFO的管道,unix domain socket的server/client通過這個管道通訊。

             libmemcached支持通過unix domain socket來訪問memcached,基于libmemcached實現的client應該都可以使用這一功能。目前來看,java平臺由于不支持平臺相關的unix domain socket,因此無法享受memcached的這一特性。

             不過有一個開源項目通過jni支持實現了unix domain socket,這個項目稱為juds。核心類就三個,使用非常簡單。下載文件后,解壓縮,make & make install即可。注意,Makefile中寫死了JAVA_HOME,手工修改即可。看一個例子,經典的Time server:
          package com.google.code.juds.test;

          import java.io.IOException;

          import com.google.code.juds.*;
          import java.io.*;
          import java.text.DateFormat;
          import java.text.SimpleDateFormat;
          import java.util.Date;

          public class TimeServer {
              
          public static void main(String[] args) {
                  
          try {
                      UnixDomainSocketServer server 
          = new UnixDomainSocketServer(
                              
          "/home/dennis/time", UnixDomainSocket.SOCK_STREAM);
                      OutputStream output 
          = server.getOutputStream();
                       Date date 
          = new Date();
                       DateFormat dateFormat 
          = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                      output.write(dateFormat.format(date).getBytes());
                  } 
          catch (IOException e) {
                              e.printStackTrace();
                  }

              }

          }

              通過UnixDomainSocketServer創建server,指定類型為SOCK_STREAM,juds也支持UDP類型。client的使用如下:
                  byte[] b = new byte[128];
                 
          UnixDomainSocketClient socket = new UnixDomainSocketClient("/home/dennis/time",
                          UnixDomainSocket.SOCK_STREAM);
                  InputStream in 
          = socket.getInputStream();
                  in.read(b);
                  System.out.println(
          "Text received: \"" + new String(b) + "\"");
                  socket.close();
              顯然,juds還只支持阻塞IO,考慮可進一步使用select、poll來擴展實現非阻塞IO。

              最后一個例子,通過juds訪問memcached的unix domain socket,簡單的version協議調用:
          byte[] b = new byte[128];
                  UnixDomainSocketClient socket 
          = new UnixDomainSocketClient("/home/dennis/memcached",
                          UnixDomainSocket.SOCK_STREAM);
                  OutputStream out 
          = socket.getOutputStream();
                  String text 
          = "version\r\n";
                  out.write(text.getBytes());
                  InputStream in 
          = socket.getInputStream();
                  in.read(b);
                  System.out.println(
          "Text received: \"" + new String(b) + "\"");
                  socket.close();
             輸出
               Text received: "VERSION 1.4.1"

          posted @ 2009-10-15 06:12 dennis 閱讀(5392) | 評論 (0)編輯 收藏

              字符串操作是任何一門編程語言中最常用的操作之一,scheme也提供了一系列procudure來操作字符串。

          1、字符串的比較,有6個,分別是string=?  string>? string<? string>=? string<=?

          這與其他語言中對string的比較并無不同,比較字符和長度。

          例子:
          (string=? "mom" "mom") <graphic> #t
          (string<? "mom" "mommy") <graphic> #t
          (string>? "Dad" "Dad") <graphic> #f
          (string=? "Mom and Dad" "mom and dad") <graphic> #f
          (string<? "a" "b" "c") <graphic> #t

          注意這些比較操作是大小寫敏感。相應的,大小寫不敏感的版本:

          procedure: (string-ci=? string1 string2 string3 ...)
          procedure: (string-ci<? string1 string2 string3 ...)
          procedure: (string-ci>? string1 string2 string3 ...)
          procedure: (string-ci<=? string1 string2 string3 ...)
          procedure: (string-ci>=? string1 string2 string3 ...)

          2、從字符構造字符串,使用string過程
          (string #\a)  => "a"
          (string #\a #\b #\c)  => "abc"

          注意,換行字符是#\newline,回車字符是#\return

          3、重復N個字符構造字符串
          (make-string)  => ""
          (make-string 4 #\a)  =>"aaaa")

          4、字符串長度 string-length
          (string-length "") =>0
          (string-length "dennis") => 6

          5、取第N個字符,相當于java中的charAt:

          (string-ref "hi there" 0) <graphic> #\h
          (string-ref "hi there" 5) <graphic> #\e

          6、修改字符串的第N個字符:
          (string-set! "hello" 0 #\H) => "Hello"

          7、拷貝字符串:
          (let ((str "abc"))
            (eq? str (string-copy str)))  => #f
          (let ((str "abc"))
            (equal? str (string-copy str)))  => #t

          8、拼接字符串,string-append
          (string-append) => ""
          (string-append "abc" "defg") => "abcdefg"

          9、截取子串
          (substring "hi there" 0 1) <graphic> "h"
          (substring "hi there" 3 6) <graphic> "the"
          (substring "hi there" 5 5) <graphic> ""

          10、填充字符串
          (let ((str (string-copy "sleepy")))
            (string-fill! str #\Z)
            str) <graphic> "ZZZZZZ"

          11、與list的相互轉換

          (string->list "") <graphic> ()
          (string->list "abc") <graphic> (#\a #\b #\c)

          (list->string '()) <graphic> ""
          (list->string '(#\a #\b #\c)) <graphic> "abc"
          (list->string
            (map char-upcase
                 (string->list "abc"))) <graphic> "ABC"

          posted @ 2009-10-12 17:59 dennis 閱讀(1913) | 評論 (0)編輯 收藏

          僅列出標題
          共56頁: First 上一頁 12 13 14 15 16 17 18 19 20 下一頁 Last 
          主站蜘蛛池模板: 榕江县| 西藏| 改则县| 靖边县| 双辽市| 苏尼特右旗| 卢氏县| 宾川县| 大冶市| 通渭县| 岚皋县| 白银市| 桂林市| 舟曲县| 抚松县| 鄂伦春自治旗| 宁阳县| 绵阳市| 藁城市| 石楼县| 秦皇岛市| 博罗县| 彰化县| 抚顺县| 丽水市| 潼关县| 灵武市| 亳州市| 繁昌县| 克东县| 乐山市| 买车| 玉树县| 盐亭县| 墨竹工卡县| 丁青县| 池州市| 连州市| 信宜市| 常德市| 祁门县|