Perl异步访问Mysql数据库

注:这篇文章转发自Chinaunix的Perl论坛,原文请点这里,可能是本博的首篇转发文档。

这篇主要想介绍异步数据库,目前Perl的web框架主要是Dancer和Mojolicious,我用Dancer很久,但一直没法实现完整的异步数据库操作,改用Mojo才3天,很容易实现了异步数据库。

我先说完美的异步数据库操作的应该是什么样的。假设有一个web页面涉及5个sql查询操作,每个操作耗时1秒,在只有一个worker的情况下(单进程),如果有一个人访问这个页面,读取时间应该是1秒。如果有10个人同时访问这个页面,10个人都应该在1秒钟之内看到页面(理想状况下)。

以上情况用Dancer是无法实现的,即使实现也是麻烦的要死,需要做好多的修改。Mojolicious,轻松实现。

我科普一下异步数据库操作的实现,知道的朋友跳过。目前异步数据库的实现有两种:
第一种是用多进程来模拟,有多个阻塞的数据库查询的时候,fork出新的进程然后,等待,结束返回。

第二种是在socket级上做真的异步。这其实才是真的异步。用数据库API本身自带的功能实现。例如DBD::mysql模块就有异步的功能。

第一种的最大优点是“干净”。不会出什么状况。而且速度并不会慢。缺点是太消耗服务器资源,一个页面5个查询,10个人同时来,如果没限制,他敢给你fork出一堆进程来。我就是因为这个原因最后放弃了“第一种”。

第二种的最大优点是,由于是数据库驱动支持的,所以可以实现socket级别上的真异步。缺点也是有的,因为是同一个进程,这样的异步主要是数据库端查询时间的异步,数据返回阶段仍然是阻塞的(等待数据返回的时候并不阻塞)。但个人认为这块可以忽略不计,毕竟查询最耗时的还是数据库端。

接下来介绍目前Perl的异步数据库模块

  • AnyEvent::DBI

这个是anyevent作者写的,是我之前一直在用,属于“第一种”。

  • Coro::Mysql

这个也是anyevent作者写的,编译原因没试成功(要求和DBD::mysql编译用同一个mysql库)。。。但也不想再试了,看了下代码也是“第一种”。完全可以用前者取代。

  • Coro::DBI

这个和上面的类似,也属于“第一种”。

  • AnyEvent::DBI::MySQL

这个是我目前用的,作者水平很高,并且能很好的结合Mojo使用。属于“第二种”

  • DBIx::Custom

这个是小日本的作品,异步的功能仍然是在实验阶段,但也可以结合Mojo使用,属于“第二种”。

  • DBD::mysql

最后这个是数据库驱动,没什么可说的了,“第二种”。

最后其实就是从AnyEvent::DBI::MySQL和DBIx::Custom中挑选了,我实在烦DBIx::Class那套,所以不喜欢DBIx::Custom,况且看着小日本写的那惨不忍睹的英语,太起急。另外AnyEvent::DBI::MySQL默认就支持数据库连接池,这部分大家去看模块文档。

我下面介绍如何实现用AnyEvent::DBI::MySQL和Mojolicious搭建支持非阻塞数据库查询的应用。

跳过Mojolicious::Lite了,项目大了全放一个文件里不实际,直接Mojolicious。
生成Mojolicious完整项目的目录结构的命令是mojo generate app,这个类似于dancer -a。这个得吐槽一下,mojo的作者实在不会介绍项目,这么常用和重要的命令居然没有在醒目的地方写出,找了半天才找到。

Mojolicious的完整项目生成以后在lib目录中有个MyApp.pm文件,这个文件中会有一个startup函数,这是唯一需要修改的地方,因为需要在项目启动的时候把异步数据库的连接加进去,只有这样才能实现所有数据库连接的异步,否则就只能是局部数据库异步了。

下面是完整的startup函数:

sub startup {
 my $app = shift;

$app->config(db => {dsn=>$db_source, login=>$db_user, pass=>$db_pass});
 $app->helper(dbh => sub { shift->{dbh} });
 $app->helper(new_dbh => sub {
 state $db = shift->app->config('db') or return;
 return AnyEvent::DBI::MySQL->connect(@{$db}{qw(dsn login pass)},
 {mysql_enable_utf8 => 1});
 });

$app->hook(before_routes => sub {
 my $c = shift;
 # each connection have own dbh
 $c->{dbh} = $c->new_dbh;
 });

# Router
 my $r = $app->routes;

# Normal route to controller
 $r->get('/')->to('example#welcome');
}

接下来写个sql测试一下就好了。
select now(), sleep(5)

在默认生成的例子中,这部分是在Example.pm文件中做的,MyApp.pm指定了处理对应route的模块和方法,Example.pm里实现了这些方法的。

下面是对应的welcoum函数:

sub welcome {
 my $self = shift;

$self->render_later;

my $dbh = $self->dbh;
 $dbh->selectall_arrayref($sql_sleep, sub {
 my ($ary_ref) = @_;
 my $result = $ary_ref->[0][0];
 $self->render(msg => "py py py $result");
 });
}

之后用morbo或hypnotoad启动应用就可以了。如果有兴趣可以用ab测试一下,简单些你就打开5个浏览器然后访问就可以了,所有连接只需要等5秒,而不是等待25秒才得到所有结果。

此条目发表在Common分类目录,贴了, 标签。将固定链接加入收藏夹。