忘记密码   免费注册 查看新帖 | 论坛精华区

ChinaUnix.net

  平台 论坛 博客 认证专区 大话IT 视频 徽章 文库 沙龙 自测 下载 频道自动化运维 虚拟化 储存备份 C/C++ PHP MySQL 嵌入式 Linux系统
123下一页
最近访问板块 发新帖
查看: 14344 | 回复: 22

[详细介绍]Perl的web框架Mojolicious与异步数据库操作(有数据库操作的朋友一定要看) [复制链接]

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
发表于 2013-10-19 08:10 |显示全部楼层
本帖最后由 兰花仙子 于 2013-10-21 22:20 编辑

这篇主要想介绍异步数据库,目前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的异步数据库模块
  1. AnyEvent::DBI
复制代码
这个是anyevent作者写的,是我之前一直在用,属于“第一种”。
  1. Coro::Mysql
复制代码
这个也是anyevent作者写的,编译原因没试成功(要求和DBD::mysql编译用同一个mysql库)。。。但也不想再试了,看了下代码也是“第一种”。完全可以用前者取代。
  1. Coro::DBI
复制代码
这个和上面的类似,也属于“第一种”。
  1. AnyEvent::DBI::MySQL
复制代码
这个是我目前用的,作者水平很高,并且能很好的结合Mojo使用。属于“第二种”
  1. DBIx::Custom
复制代码
这个是小日本的作品,异步的功能仍然是在实验阶段,但也可以结合Mojo使用,属于“第二种”。
  1. 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函数
  1. sub startup {
  2.     my $app = shift;

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

  10.     $app->hook(before_routes => sub {
  11.         my $c = shift;
  12.         # each connection have own dbh
  13.         $c->{dbh} = $c->new_dbh;
  14.     });

  15.     # Router
  16.     my $r = $app->routes;

  17.     # Normal route to controller
  18.     $r->get('/')->to('example#welcome');
  19. }
复制代码
接下来写个sql测试一下就好了。
select now(), sleep(5)
在默认生成的例子中,这部分是在Example.pm文件中做的,MyApp.pm指定了处理对应route的模块和方法,Example.pm里实现了这些方法的。
下面是对应的welcoum函数。
  1. sub welcome {
  2.     my $self = shift;

  3.     $self->render_later;

  4.     my $dbh = $self->dbh;
  5.     $dbh->selectall_arrayref($sql_sleep, sub {
  6.         my ($ary_ref)  = @_;
  7.         my $result = $ary_ref->[0][0];
  8.         $self->render(msg => "py py py $result");
  9.     });
  10. }
复制代码
之后用morbo或hypnotoad启动应用就可以了。如果有兴趣可以用ab测试一下,简单些你就打开5个浏览器然后访问就可以了,所有连接只需要等5秒,而不是等待25秒才得到所有结果。

时间太少,平时太忙,周末还得陪老婆逛街,写完也没时间再看一遍,词不达意之处,见谅。另外虽然数据库异步使用很久了,但正式接触Mojolicious才几天,这部分写的不好大家也见谅,等有更深入的了解了再写后续。也欢迎指正错误。谢谢。

论坛徽章:
3
CU十二周年纪念徽章
日期:2013-10-24 15:41:34射手座
日期:2014-04-25 21:23:23子鼠
日期:2013-12-14 14:57:19
发表于 2013-10-19 08:46 |显示全部楼层
学习楼主的这种探索精神{:3_190:},介绍了好多新东东

论坛徽章:
1
辰龙
日期:2014-05-15 19:37:15
发表于 2013-10-19 09:22 |显示全部楼层
本帖最后由 iakuf 于 2013-10-19 09:47 编辑

回复 1# py

这个模块有个最大的问题,就是同时一个连接上只能一个异步.因为 "DBD::mysql support only single asynchronous query per MySQL connection". 所以生产环境要一定是有多个数据库查询同时的异步, 这个并不能和我们理解的工作方式一样.

例如下面,我们在 SQL 语句中 SLEEP 5 秒,看看查询的速度:

  1. use EV;
  2. use 5.010;
  3. use Smart::Comments;
  4. use AnyEvent::DBI::MySQL;

  5. my $dbh = AnyEvent::DBI::MySQL->connect('dbi:mysql:database=test', 'user', 'pass');

  6. for ( 1 .. 10 ) {
  7.     $dbh->selectall_arrayref(
  8.             "select SLEEP(5), 3",
  9.             sub {
  10.                 my ($ary_ref)  = @_;
  11.                 ### $ary_ref
  12.             }  
  13.     );
  14. }
  15. EV::run;
复制代码
这样工作是会出问题的.
必须使用下面这种方式,但下面这种方式太浪费连接了,大多的查询很快,大多的开销都在创建连接和断开.

  1. #!/usr/bin/perl
  2. use EV;
  3. use 5.010;
  4. use Smart::Comments;
  5. use AnyEvent::DBI::MySQL;

  6. my @dbh;

  7. for ( 1 .. 10 ) {
  8.     $dbh[$_] = AnyEvent::DBI::MySQL->connect('dbi:mysql:database=test', 'test', '');
  9.     $dbh[$_]->selectall_arrayref(
  10.             "select SLEEP(5), 3",
  11.             sub {
  12.                 my ($ary_ref)  = @_;
  13.                 ### $ary_ref
  14.             }  
  15.     );         
  16. }
  17. EV::run;
复制代码
目前完美的异步方案还只有 DBIx::Custom.这个可以连接复用.并可以控制连接池.

所以当你写的上面那个应用 ,每次查询由 helper 创建一个新的连接,高并发的时候,数据会因为连接数死掉,另外创建连接的性能开销比较大.

你不用担心 DBIx::Custom 和 DBIx::Class 一样,我也不太喜欢 DBIx::Class, DBIx::Custom 的封装超级简单,学习成本很小.基本不想用它的语法,都可以用完整的 SQL 语句.
另外,非常稳定,远超过所讲的实验性的. 在日本的商业网站也在使用这个.非常可靠.

你可以看看
  1. use Mojolicious::Lite;
  2.   use EV;
  3.   use DBIx::Custom;

  4.   my $dbi = DBIx::Custom->connect(
  5.     dsn => 'dbi:mysql:database=usertest',
  6.     user => 'root'
  7.   );

  8.   $dbi->async_conf({
  9.     prepare_attr => {async => 1},
  10.     fh => sub { shift->dbh->mysql_fd }
  11.   });

  12.   get '/' => sub {
  13.     my $self = shift;
  14.    
  15.     $self->render_later;
  16.     $dbi->select('SLEEP(5), 3', async => sub {
  17.       my ($dbi, $result) = @_;
  18.       my $row = $result->fetch_one;
  19.       $self->render(text => $row->[1]);
  20.     });
  21.   };

  22.   app->start;
复制代码
我现在连自己的分布文件系统的大部分的接口通信和 Agent 都是用 Mojo 开发的,减少很多工作量.开发超快,性能又好.

论坛徽章:
34
CU大牛徽章
日期:2013-04-17 11:10:17CU大牛徽章
日期:2013-09-18 15:26:10狮子座
日期:2013-09-27 17:44:07CU十二周年纪念徽章
日期:2013-10-24 15:41:34射手座
日期:2013-10-24 21:01:23辰龙
日期:2013-12-20 17:07:19狮子座
日期:2014-05-12 11:00:00寅虎
日期:2014-06-04 16:25:27IT运维版块每日发帖之星
日期:2015-08-17 06:20:002015亚冠之首尔
日期:2015-11-04 22:25:43数据库技术版块每日发帖之星
日期:2015-12-01 06:20:00平安夜徽章
日期:2015-12-26 00:06:30
发表于 2013-10-19 11:40 |显示全部楼层
数据库的异步操作需要数据库驱动的支持么?
比如oracle/postgresql,用DBIx::Custom 可以实现异步查询吗?
相对而言,mysql是基于线程的,连接比oracle/postgresql消耗要小很多 .

论坛徽章:
5
丑牛
日期:2014-01-21 08:26:26卯兔
日期:2014-03-11 06:37:43天秤座
日期:2014-03-25 08:52:52寅虎
日期:2014-04-19 11:39:48午马
日期:2014-08-06 03:56:58
发表于 2013-10-19 13:50 |显示全部楼层
谢谢介绍异步数据库。。{:2_179:}

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
发表于 2013-10-19 15:29 |显示全部楼层
本帖最后由 py 于 2013-10-19 15:32 编辑

回复 3# iakuf

是的。这句话我看到了。你的第一段代码是阻塞的,第二段是正常工作的。因为DBD::mysql本身就只支持一个连接一个异步。
DBD::mysql support only single asynchronous query per MySQL connection. To make it easier to overcome this limitation provided connect() constructor work using DBI->connect_cached() under the hood
作者是用了DBI的connect_cached来缓存数据库连接,所以有数据库连接的时候是不会每次都做连接的。缓存机制是有的。

我也看到DBIx::Custom用的是DBIx::Connector,在我connector => 1的时候提示我安装DBIx::Connector。
从文档上看,DBIx::Connector是对DBI的connect_cached做了一些改进。我一会把DBIx::Custom搭上试试,看看效果。

另外,我已经联系了AnyEvent:BI::MySQL的作者,看他能不能把连接缓存改过来,也用DBIx::Connector

PS. 你得大力推广mojo啊,感觉大家好像不太爱看文档,都喜欢看现成的例子

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
发表于 2013-10-19 15:33 |显示全部楼层
laputa73 发表于 2013-10-19 11:40
数据库的异步操作需要数据库驱动的支持么?
比如oracle/postgresql,用DBIx::Custom 可以实现异步查询吗?
相 ...


oracle我没试过,但从文档上看DBIx::Custom应该是可以的

论坛徽章:
1
辰龙
日期:2014-05-15 19:37:15
发表于 2013-10-19 16:23 |显示全部楼层
回复 4# laputa73

大多的时候,原生驱动支持异步才有好的性能, 如果原生不支持,就只能靠模块使用 fork 之类的机制,性能就会很差.
mysql 本来的驱动模块 DBD::mysql 也不支持异步,也是近来在 4.019 加入的然后 AnyEvent 之类其它的才能使用它的原生异步功能.在那之类 DBI 的 MySQL 异步都是通过 Fork 子进程来实现的.

   

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
发表于 2013-10-21 14:17 |显示全部楼层
回复 3# iakuf

AnyEvent:BI::MySQL的作者在我发邮件的几分钟后就回了我邮件。
作者是个俄罗斯人,他坚持认为数据库异步操作的连接混存,cached_connect好过DBIx::Connector

总结他的长篇大论,归纳2点。

1. 异步数据库连接缓存用DBIx::Connector就是“asking for
troubles”,他认为DBIx::Connector只是能够手工设置连接数,在连接mysql的时候效果并没有好过DBD::mysql自带的cached_connect

2. DBIx::Custom模块在做异步查询的时候,并没有使用DBIx::Connector,实际上它并没有使用任何数据库连接的缓存机制。
https://metacpan.org/source/KIMO ... DBIx/Custom.pm#L369

这可能也是DBIx::Custom的异步还是试验阶段的原因。
   

论坛徽章:
1
天蝎座
日期:2013-11-25 10:40:37
发表于 2013-10-21 21:46 |显示全部楼层
回复 9# py


    我看它就是用AnyEvent监听DBI的socket而已。不是说DBI都是阻塞的么,这里非阻塞是是么原因?
您需要登录后才可以回帖 登录 | 注册

本版积分规则

  

北京皓辰网域网络信息技术有限公司. 版权所有 京ICP证:060528号 北京市公安局海淀分局网监中心备案编号:1101082001
广播电视节目制作经营许可证(京) 字第1234号 中国互联网协会会员  联系我们:
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP