扶凯

取势 明道 优术

作者为 扶 凯 发表

在 Mojo 这个 web 架构中除了功能非常全外,很好的一个就全异步的支持.并且直接可以使用 EV 之类.在加上有 DBIx::Custom 这种轻量级数据库连接的异步模块,还有 Mojo::Redis 的模块.更是让写应用得心应手. 下面给大家介绍一下写应用的一些小技巧.

先看一段代码.这是我们自己开发的监控系统中的一段真实的代码:

get '/' => sub {
    my $self = shift;
    my $businessid = $self->param('businessid') || 6;
    my @cid = $self->param('cid');

    $self->render_later;

    # 查业务  
    $dbi->select( 
        table => 'server_business',
        async => sub {
            my ($dbi, $result) = @_; 
            my $ref_bussine = $result->fetch_hash_all; 
            # 查组
            $dbi->select( 
                table => 'server_groups',
                where => { business_id => $businessid },  
                async => sub {
                    my ($dbi, $result) = @_; 
                    my $ref_group = $result->fetch_hash_all; 
                    my @id;
                    for( @$ref_group ) { 
                        push @id, $_->{id};
                    }   
                    $dbi->select( 
                        table => 'server_hostInfo',
                        where  => "group_id rlike '^(.*,)?(" . join('|',@id) .")(\$|,)'", 
                        async => sub {
                            my ($dbi, $result) = @_; 
                            my $ref_hostinfo = $result->fetch_hash_all; 
                            my $hostinfos = {}; 
                            foreach my $key (@$ref_hostinfo) {
                                my $hostname = $key->{host_name};
                                $hostinfos->{$hostname}  = $key;
                            }   
                            $self->render(
                                'index', 
                                bussiness   => $ref_bussine,
                                groups      => $ref_group,
                                server_list => $hostinfos,
                            );  
                        }   
                    );  

                }   
            );  

        }   
    );  
};


上面比较重要的是, 在 Mojo 中调用了 $self->render_later 的调用,告诉 Mojo 后面的内容是要进入事件回调,不需要 block 整个应用,直到等到有数据的时候回调就好了.
然后后面是查一些详细的信息.但整个 render 给模块,我们需要三个信息, 公司业务线,设备组和设备本身的信息,如果不使用联合查询,就只能写成我上面这样子.每次一次,接着回调下面的内容.
上面是先查整个业务,查完回调时然后在查组.查完组后查所有主机的信息.这样有三个回调,一级级调,性能非常好.但不是同时的.
在 Mojo 中的 Cookbook 中有个提到一个好东西,可以做事情回调的同步.这是我在项目中常常用到的. 其实这个时候很合适来使用.请看下面的代码.

sub  all {
    my $c = shift;

    $c->render_later;

    Mojo::IOLoop->delay(
        sub {
            my $delay = shift;
            $c->db->select(
                table => 'server_business',
                async => $delay->begin,
            );  
            $c->db->select(
                table => 'server_hostInfo',
                async => $delay->begin,
            );  
        },  
        sub {
            my ($delay, $server, $hostinfo) = @_; 

            $c->render(json => {
                server   => $server->fetch_hash_all,
                hostinfo => $hostinfo->fetch_hash_all,
            }); 
        },  
    );  
}

嗯.上面就因为使用了 Mojo::IOLoop->delay 这个时候, 在 delay 中的参数是二个子函数. 在执行时会先执行第一个子函数.直到第一个子函数中没有东西需要回调时,然后执行第二个子函数.
在这个 delay 的匿名子函数中,可以接收到 $delay 的对象,给原本要回调的地方,直接替换成 $delay->begin 做为回调.这样在 delay 的第二个匿名子函数中可以接收到其它第一个子函数中所有的回调.
上次 Perl 大会,我写过一个刷新 Squid 的例子,也是使用的上面的技巧

post '/fresh' => sub { 
    my $self = shift; 
    my $urls = $self->param('url'); 
    Mojo::IOLoop->delay( 
        sub { 
            my $delay = shift; 
            my @urls = split('\n', $urls); 
            for my $url (@urls) { 
                $self->ua->get(
                    $url => {'X-Purge' => ‘True'}, 
                    $delay->begin,
                ); 
            } 
        }, 
        sub { 
            my ($delay , @result) = @_;
            $self->render( text => "刷新完成, @result" ); 
        } 
     ); 
};


能这样写回调的同步,实在是太方便了.如果你是 AnyEvent 可以直接使用条件,然后有 begin 和 end  的这二个函数来做事件同步,有事件同步功能,虽然并不象上面这个这么好用.

下面我来介绍一下 redis 怎么一次查询多个元素,这个原生提供了多任务一次查询的功能.所以不需要象查 MySQL 时一样,需要做事件同步.

$redis->execute( 
        [ hmset => $tkey1 ],
        [ hmset => $tkey2 ],
        sub {
              $self->render(json => {status => 'ok'} );
        }   
);  


在 Mojo::Redis 中直接可以使用 execute ,后面参数中使用数组引用的方法,给多条要执行的数据使用逗号分开就好了,这样可以同时设置和查一批任务.

异步回调在面象对象中的小技巧
我们写面象对象的回调的时候,一定要先定义回调. 所以我们常常会写一个很大的函数,函数中又包含很多匿名的子函数存到变量中,等到需要时在调用.因为回调很多变量事先都不会存在,另外,还需要一些外部变量,所以定义的这个匿名子函数一定使用了很多它本向之外的变量.象 AnyEvent::HTTP 中处理响应的代码

      $state{read_response} = sub {
         return unless %state;

         for ("$_[1]") {
            y/\015//d; # weed out any \015, as they show up in the weirdest of places.

            /^HTTP\/0*([0-9\.]+) \s+ ([0-9]{3}) (?: \s+ ([^\012]*) )? \012/gxci
               or return _error %state, $cb, { @pseudo, Status => 599, Reason => "Invalid server response" };

            # 100 Continue handling
            # should not happen as we don't send expect: 100-continue,
            # but we handle it just in case.
      .....
            unless ($hdr{location} =~ s/^\///) {
               $url .= $upath;
               $url =~ s/\/[^\/]*$//;
            }

            $hdr{location} = "$url/$hdr{location}";
         }
      ........
     }

我们定义了一个匿名子函数,存到 read_response 中,在整个函数中,大量这种的定义,让函数变得超级长.很难看,又不方便拆分出来.因为使用了它外部的变量.
这个时候,我们可以使用一种技巧.见如下代码:

sub read_respone {
    my ($self, $state, $hdr) = @_; 

    return {
        return unless %$state;

        for ("$_[1]") {
           y/\015//d; # weed out any \015, as they show up in the weirdest of places.

           /^HTTP\/0*([0-9\.]+) \s+ ([0-9]{3}) (?: \s+ ([^\012]*) )? \012/gxci
              or return _error %$state, $cb, { @pseudo, Status => 599, Reason => "Invalid server response" };

           # 100 Continue handling
           # should not happen as we don't send expect: 100-continue,
           # but we handle it just in case.
            ... 
           unless ($hdr->{location} =~ s/^\///) {
              $url .= $upath;
              $url =~ s/\/[^\/]*$//;
           }   

           $hdr-et:{location} = "$url/$hdr{location}";
        }   
        ....
    }   
}

只需要小改一下,在面象对象的时候,我们使用对象本身来接收参数.然后直接返回一个子函数的引用.这样这个函数很方便的在事件调用中抽出来了.并且可以很方便取得原来的外部变量.另外,本身这个子函数也是能正常接收回调的参数.相当的方便,程序可以变得好看和容易读很多.

来了就留个评论吧! 1个评论