庄周梦蝶

生活、程序、未来
   :: 首页 ::  ::  :: 聚合  :: 管理

    最近在锋爷的建议下开始读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",    "Erlang/OTP"},
                              {"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 阅读(11867) | 评论 (8)编辑 收藏

    入这行也有四年了,从过去对软件开发的懵懂状态,到现在可以算是有一个初步认识的过程,期间也参与了不少项目,从一开始单纯的编码,到现在可以部分地参与一些设计方案的讨论,慢慢对设计方案的评判标准有一点感受。读软件工程、架构设计、模式之类的书,对于书中强调的一些标准和原则的感受只是感官浅层的印象,你可以一股脑从嘴里蹦出一堆词,什么开闭原则、依赖倒转、针对接口编程、系统的可伸缩性、可维护性、可重用等等,也仅仅停留在知道的份子上。不过现在,我对一个设计方案的评价标准慢慢变的很明确:简单、符合当前和一定预期时间内的需求、可靠、直观(或者说透明)
   简单,不是简陋,这是废话了。但是要做到简单,却是绝不简单,简单跟第四点直观有直接的关系,简单的设计就是一个直观的设计,可以让你一眼看得清的设计方案,也就是透明。一个最简单的评判方法,你可以讲这个方案讲给一个局外人听,如果能在短时间内让人理解的,那么这个方案八成是靠谱的。记的有本书上讲过类似的方法,你有一个方案,那就拿起电话打给一个无关的人员,如果你能在电话里说清楚,就表示这个方案相当靠谱,如果不能,那么这个方案很可能是过度复杂,过度设计了。
   简单的设计,往往最后得出的结果是一个可靠性非常高的系统。这很容易理解,一个复杂的设计方案,有很多方面会导致最后的实现会更复杂:首先是沟通上的困难,一个复杂的方案是很难在短时间内在团队内部沟通清楚,每个开发人员对这个方案的理解可能有偏差;其次,复杂的方案往往非常考验设计人员和开发人员的经验、能力和细致程度,复杂的方案要考量的方面肯定比简单方案多得多,一个地方没有考虑到或者不全面,结果就是一个充满了隐患的系统,时不时地蹦出一个BUG来恶心你,这并非开发人员的能力问题,而是人脑天然的局限性(天才除外,咳咳)。
   第二点,符合当前和一定预期时间内的需求。我们都知道,不变的变化本身,指望一个方案永久解决所有问题是乌托邦的梦想。复杂方案的出炉通常都是因为设计人员过度考量了未来系统的需求和变化,我们的系统以后要达到10倍的吞吐量,我们的系统以后要有几十万的节点等等。当然,首先要肯定的是对未来需求的考量是必需的,一个系统如果实现出来只能应付短时间的需求,那肯定是不能接受的。但是我们要警惕的是过度考量导致的过度复杂的设计方案,这还有可能是设计人员“炫技”的欲望隐藏在里头。这里面有一个权衡的问题,比如这里有两个方案:一个是两三年内绝对实用的方案,简单并且可靠直观,未来的改进余地也不错;另一个方案是可以承载当前的几十倍流量的方案,方案看起来很优雅,很时尚,实现起来也相对复杂。如何选择?如果这个系统是我们当前就要使用的,并且是关键系统,那么我绝对会选择前一个方案,在预期时间内去改进这个方案,因为对于关键系统,简单和可靠是性命攸关的。况且,我坚定地认为一个复杂的设计方案中绝对隐藏着一个简单的设计,这就像一个复杂的数学证明,通常都可以用更直观更简单的方式重新证明(题外话,费尔马大定理的证明是极其复杂的,现在还有很多人坚信有一个直观简单的证明存在,也就是那个费尔马没有写下的证明)。最近我们的一个方案讨论也证明了这一点,一个消息优先级的方案,一开始的设想是相对复杂的,需要在存储结构和调度上动手脚,后来集思广益,最后定下的方案非常类似linux的进程调度策略,通过分级queue和时间片轮询的方式完美解决了优先级的问题。这让我想起了软件开发的“隐喻”的力量,很多东西都是相通相似的。
   上面这些乱弹都是自己在最近碰到的一些讨论和系统故障想起的,想想还是有必要写下来做个记录。

   

posted @ 2009-11-28 23:38 dennis 阅读(1113) | 评论 (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 阅读(722) | 评论 (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 阅读(2292) | 评论 (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 阅读(974) | 评论 (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 阅读(1103) | 评论 (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 阅读(3012) | 评论 (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 阅读(878) | 评论 (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 阅读(5368) | 评论 (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 阅读(1893) | 评论 (0)编辑 收藏

仅列出标题
共56页: First 上一页 12 13 14 15 16 17 18 19 20 下一页 Last