laravel队列-让守护进程处理耗时任务
待解决的问题
最近在做一个服务器集群管理的web项目,需要处理一些极其耗时的操作,比如磁盘格式化分区。对于这个需求,最开始的想法是,为了让节点上的rpc(远程过程调用) service端尽可能简单(简单到只需要popen执行一条指令即可,有时间我再专门写一篇博客讲讲这个项目的rpc是如何实现的),我们选择了让web端直接等待处理结果,那么问题来了,如何保证用户不必等待,又能保证任务准确的执行呢?
简单的rpc结构如下图

以往在处理一些稍微耗时的操作,可以通过优化代码结构,优化数据库操作次数,起一些线程来处理一些简单的比如发邮件,生成大的压缩文件,提取视频缩量图,服务器间互访等等操作,来避免用户在web页面的等待。
但现在这个操作显然不能用之前的这些方法做,因为现在的操作哪怕只执行一次,都是非常耗时的,更何况可能需要处理的可能是上百上千台服务器。这是在线程层面很难做,要知道在响应请求的web进程中起一个线程来做的话,在响应完断开tcp连接之后,这个进程很可能被kill掉,像Apache就是这样,当然可以通过配置改变apache的行为,但显然不太靠谱。
更好的做法是在web服务器上起一个守护进程去做这个事情,那么问题就在于如何创建守护进程了,好在laravel帮我们考虑了这个事情。
Laravel的队列
laravel的队列默认是以sync(同步)的方式来处理多个任务,这显然不是我们想要的。鉴于这个项目使用的是laravel4.1版本,我选择了beanstalkd来实现异步处理多个任务。
其中beanstalkd是一种比较专业的队列服务驱动器,是一个常驻后台服务,我们可以通过它提供的接口来把任务提交给它,由它创建的守护进程来执行队列。
配置队列执行环境
1.安装beanstalkd服务
我开发的电脑为CentOS5.4,版本比较低,所以装的过程中还是遇到些麻烦
首先执行下面的指令
wget ftp://fr2.rpmfind.net/linux/epel/5/ppc/epel-release-5-4.noarch.rpm
rpm -ivh epel-release--.noarch.rpm
yum makecache
yum search beanstalkd
但最后发现找不到这个软件,于是将yum的源换成了163的
cd /etc/yum.repos.d
wget http://mirrors.163.com/.help/CentOS5-Base-163.repo
mv CentOS-Base.repo CentOS-Base.repo.bak
再次makecache && install 就OK了,安装完后启动beanstalkd服务
service beanstalkd start
另外可以搜索到beanstalkd的配置文件放在了sysconfig下

为laravel添加beanstalkd的驱动
beanstalkd的php驱动包为pda/pheanstalk
进入laravel的protected目录,composer.json在这个目录下
执行
composer require pda/pheanstalk .*
出现如下的错误
,
可以看出镜像地址响应 502,所以需要给composer找一个可用的镜像 http://www.phpcomposer.com/
修改~/.composer/config.json如下

然后回到protected目录,再执行前面安装驱动的命令安装,这回出现了不一样的错误

上面的php包中,第一个是为了phpstorm的对laravel更好的支持,后面一个symfony/yaml已经安装,并不需要升级,所以修改composer.json,直接将这两个项目删除掉就行了
删除完之后,再次执行安装命令安装

可以看到终于成功了,可以通过 composer show -i 查看安装了哪些包
测试
在TestController中添加一个action
class TestController extends BaseController
{ public function getQueue(){
//
Log::info("添加一个对列任务");
Queue::push('SendEmail',array('message'=>'哈哈'));
Log::info('任务添加完毕');
exit;
} }
在app目录下新建tasks目录,并修改protected/composer.json和app/global.php,将这个目录加到类加载路径中
修改global.php
ClassLoader::addDirectories(array(
app_path().'/commands',
app_path().'/controllers',
app_path().'/models',
app_path().'/database/seeds',
app_path().'/library',
app_path().'/tasks',
));
修改composer.json
"classmap": [
"app/commands",
"app/controllers",
"app/models",
"app/database/migrations",
"app/database/seeds",
"app/tasks",
"app/tests/TestCase.php"
]
以后的耗时调度任务的代码就放在这个目录下面了
首先新建一个BaseTask.php
/**
* Created by PhpStorm.
* User: Administrator
* Date: 2015/8/19 0019
* Time: 11:55
*/
abstract class BaseTask
{
public abstract function fire($job,$data);
}
然后新建一个SendMail.php
/**
* Created by PhpStorm.
* User: Administrator
* Date: 2015/8/19 0019
* Time: 11:50
*/
class SendEmail extends BaseTask
{ public function fire($job, $data)
{
// TODO: Implement fire() method.
Log::info("对列任务执行".json_encode($data)."Time : ".time());
sleep(30);
Log::info("对列任务执行完毕".time());
// 将任务从队列冲删除
$job->delete();
// 将任务返回到队列 // $job->release(); }
}
'default' => 'beanstalkd',
'beanstalkd' => array(
'driver' => 'beanstalkd',
'host' => 'localhost',
'queue' => 'default',
'ttr' => 60,
),
php artisan queue:work
php artisan queue:listen
然后访问url,http://192.168.1.10/ssanlv/test/queue,可以发现请求马上就完成了,页面并没有等待
查看protected/app/storage/logs/laravel.log 可以看到下面的内容

508-478 刚好30秒
下面测试一个实际的问题,印象中apache服务器与客户端在请求完成断开连接后会kill掉负责处理的httpd进程,只有配置了keep-alive参数在会将进程保留到apache进程池中,所以,但用户请求一个耗时操作之后,关闭了浏览器,这个处理耗时任务的守护进程会不会也被kill掉呢?当然,其实有点多虑了,当响应完成之后tcp链接已经被断开掉了,如果进程会被kill掉,那么早就kill掉了,跟你浏览器关没关应该没多大关系,还是试试吧,实践才是硬道理
这里将SendMial中的sleep时间改长一点,改为 600秒

最后发现没有执行完,可以看到listen报出异常

很显然执行超时,看来是前面设置的ttr的问题
将ttr注释掉或者修改掉更高的值,发现还是不行,最后在仔细看看报错信息,发现

所以改变命令的执行方式
php artisan queue:listen --timeout=
最后命令任务成功执行完毕

可以看到 1353-753 = 600 刚刚好
另外,看样子 这个任务对列应该是被保存起来了,当我没有启动 listen时,任务怎么都不会处理,但我一但启动listen,前面添加的任务就会立马执行

但最后还是有个问题这个是对列形式进行处理,要启动下一个对列任务,必须等上一个对列任务执行完毕,不过之前曾看到过,一个work对应一个任务队列,那么我完全可以起多个任务队列,有点多核CPU的调度哦。
更好的办法
最后,再跟一位大神讨论了一下,探讨出了另外一个更加优秀的办法,虽然会加重节点上rpc service代码的复杂度,不过也不是很麻烦。
这种方式就是回调,管理集群的web服务器可以不用等待,只需如下步骤,
- 通过web服务器上的rpc client向要执行耗时操作的节点上的rpc service发送一条指令,
- 节点上的rpc service收到指令后,不先执行指令,而是马上向web服务器,也就是rpc client返回一个任务ID。
- web服务器将这个id作为一条任务记录保存到数据库。
- 节点上的rpc service处理指令,至于处理指令,也是在节点上在单独起一个进程 P 来处理,因为rpc service也不能让rpc client傻等着
- 处理进程 P 处理完了之后,将执行结果和任务ID作为参数,回调web服务器的一个web接口
- web服务器接到rpc service的回调之后,通过ID查找到任务,更新任务的执行状态,更新数据
很显然,这种方式更加可靠,也大大减轻了web 服务器的负担,要知道Linux 系统的线程数是有限制的,但这要耗时任务多了,如果然服务器去等,不管啥策略都很可能吧服务器整垮。
laravel队列-让守护进程处理耗时任务的更多相关文章
- 配置supervisor 让laraver的队列实现守护进程
1,安装: #brew install supervisor 默认会安装在/usr/local/Cellar/supervisor目录 2,在etc下面新建supervisor.conf 文件,复制下 ...
- linux 脚本--守护进程
#/bin/bash #队列的守护进程 Date = `date +"%F-%H:%M:%S"` XMML = "/var/www/html/xiangmu" ...
- 守护进程,互斥锁,IPC,队列,生产者与消费者模型
小知识点:在子进程中不能使用input输入! 一.守护进程 守护进程表示一个进程b 守护另一个进程a 当被守护的进程结束后,那么守护进程b也跟着结束了 应用场景:之所以开子进程,是为了帮助主进程完成某 ...
- python并发编程基础之守护进程、队列、锁
并发编程2 1.守护进程 什么是守护进程? 表示进程A守护进程B,当被守护进程B结束后,进程A也就结束. from multiprocessing import Process import time ...
- Python_守护进程、锁、信号量、事件、队列
1.创建进程 守护进程(*****) _.daemon = True # _进程成为守护进程 守护进程也是一个子进程. 主进程的<代码>执行结束之后守护进程自动结束. import ti ...
- python 进程锁 生产者消费者模型 队列 (进程其他方法,守护进程,数据共享,进程隔离验证)
#######################总结######### 主要理解 锁 生产者消费者模型 解耦用的 队列 共享资源的时候 是不安全的 所以用到后面的锁 守护进程:p.daem ...
- 并发 --- 2 进程的方法,进程锁 守护进程 数据共享 进程队列, joinablequeue模型
一.进程的其他方法 1. .name 进程名 (可指定) 2. .pid 进程号 3. os.getpid 在什么位置就是什么的进程号 4. .is ...
- Python并发编程(守护进程,进程锁,进程队列)
进程的其他方法 P = Process(target=f,) P.Pid 查看进程号 查看进程的名字p.name P.is_alive() 返回一个true或者False P.terminate( ...
- python并发编程之进程1(守护进程,进程锁,进程队列)
进程的其他方法 P = Process(target=f,) P.Pid 查看进程号 查看进程的名字p.name P.is_alive() 返回一个true或者False P.terminate( ...
随机推荐
- linux命令之more
转自:http://www.cnblogs.com/peida/archive/2012/11/02/2750588.html more命令,功能类似 cat ,cat命令是整个文件的内容从上到下显示 ...
- java初学的几个问题
1. 请问配置JDK时环境变量path和JAVA_HOME的作用是什么? 作用:告诉操作系统编译器运行的路径和生成的类路径.这样java源程序才可以进行编译和运行. 以下4-7题请在JDK环境下编译和 ...
- 用HiveDB横向切分MySQL数据库
HiveDB是一个用来横向切分mysql数据库的开源框架,构建一个高性能和可扩展的基于mysql的系统需要大量的系统设计经验和良好的代码的实现,一个比较好的策略是将你的数据横向切分在多个server上 ...
- cocos2d-x jsb 防止触摸事件传递
在游戏中要实现消息弹窗,让用户点击确认,其他区域产生遮罩,阻挡下层的事件被点击到,这是个很常用的功能,在cocos2d-x中,可以通过为layer添加事件代理来实现: pDirector->ge ...
- careercup-树与图 4.7
4.7 设计并实现一个算法,找出二叉树中某两个结点的第一个共同祖先.不得将额外的结点储存在另外的数据结构中.注意:这不一定是二叉查找树. 解答 本题的关键应当是在Avoid storing addit ...
- Android开发之从网络URL上下载JSON数据
网络下载拉取数据中,json数据是一种格式化的xml数据,非常轻量方便,效率高,体验好等优点,下面就android中如何从给定的url下载json数据给予解析: 主要使用http请求方法,并用到Htt ...
- oracle数据库入门
oracle 数据库入门. 1.数据 2.数据存储的地方:变量 数组 容器 (内存中),文件,数据库(文件) 3.数据库系统:sqlserver 2000 2005 2008 mysql 5 ...
- WINDOWS Server2003上部署一个Asp.Net的网站
对于许多新手来说,如何在服务器上部署网站并不了解,这个教程希望对新手有帮助. 1.IIS服务器 2..Net Framework3.5 (方便起见,我们这里Framework说的都是指的这个版本) 这 ...
- Java json设置时间格式,Jackson设置时间格式,json设置单引号
Java json设置时间格式,Jackson设置时间格式,json设置单引号 >>>>>>>>>>>>>>> ...
- 自定义控件学习 Day44
自定义控件学习 Day44 onMeasure 测量控件的宽高. onLayout 设置位置 onDarw 绘制控件 问题堆栈 1. 事件监听传递 最外层获取到控件,根据事件事件传递机制,返回值fal ...