PHP用swoole实现爬虫(二)

本文大部分代码为伪代码,具体实现:一个简单的swoole服务器

如何解决worker锁住问题

按照epoll模型,master和manager只分配任务,实际执行交给worker。但是爬虫是一个超级耗时的任务,IO和CPU虽然不太损耗, 主要损耗都存在网络上(也是IO),明显受制于网络情况的变化。如(一)中所说,假设我只有2个CPU,我分配4个worker,这个时候服务器就无法处理新的请求了,这会造成资源的浪费。

所以我要寻找解决方案,首先我想到的用swoole的task解决,官方定义:

投递一个异步任务到task_worker池中。此函数是非阻塞的,执行完毕会立即返回。Worker进程可以继续处理新的请求。使用Task功能,必须先设置 task_worker_num,并且必须设置Server的onTask和onFinish事件回调函数。

1int swoole_server::task(mixed $data, int $dst_worker_id = -1) 2$task_id = $serv->task("some data"); 3//swoole-1.8.6或更高版本 4$serv->task("taskcallback", -1, function (swoole_server $serv, $task_id, $data) { 5 echo "Task Callback: "; 6 var_dump($task_id, $data); 7});

task的问题

首先我们在request事件中将任务转发到task:

1$server->on('requset'. function(\swoole_request $request, \swoole_response $response) use($server){ 2 $data=json_decode($request->rawContent()); 3 $server->task($data); //将任务投递到task 4}); 5 6 7$server->on('task',function(\swoole_server $serv, $task_id, $data){ 8 while(true){ 9 //执行爬虫操作 10 11 } 12$server->finish(); 13});

这样的确解决的耗时异步任务的投递方式。但是因为需求会出现一个新问题,爬虫任务是一直在执行的,我们要手动使其退出,需要从服务器外部发起请求关闭task。但是,task的finish并不能关闭指定的taskId。

如何关闭taskId呢?我尝试过用swoole的sendMessage来解决:

1$server->on('task',function($server.$taskId,$data){ 2 //用外部cache记录taskId 3}); 4$server->on('request',function($req,$res){ 5 //假设遇到关闭信号 关闭指定TASKiD 6 $server->sendMessage($data,$taskId); 7}); 8

但是实际上是无法关闭的,原因有二:

  1. 和上面一样,while(true)不会释放控制权
  2. task是一种特殊的worker_id,但是他和worker的ID一样,是从1开始,会导致系统复杂难以维护(虽然可以用$server->isTask判断)

之后我发现了一个神器:swoole_process

swoole_process 解决方案

swoole_process提供了如下特性:

  1. swoole_process提供了基于unixsock的进程间通信,使用很简单只需调用write/read或者push/pop即可
  1. swoole_process支持重定向标准输入和输出,在子进程内echo不会打印屏幕,而是写入管道,读键盘输入可以重定向为管道读取数据
  2. 配合swoole_event模块,创建的PHP子进程可以异步的事件驱动模式
  3. swoole_process提供了exec接口,创建的进程可以执行其他程序,与原PHP父进程之间可以方便的通信

具体实现伪代码如下:

1$server->on('request',function($req,$res){ 2 if request is crawler 3 then 4 if start 5 new swoole_process(function() use(data){ 6 do crawler 7 }) ; 8 cache log processId 9 if stop 10 cache get processId 11 kill processId 12 if reload 13 goto stop 14 goto start 15 else 16 do other 17});

可以很好的解决上述问题,不需要维护负载的定时器,task任务等等。新增的需求是自己维护一个process map['taskname':[processId:int,stop:int]]

优化

前期我使用的是redis记录,会出现一定不稳定的情况(频繁读写redis,需要维护长连接等等)。 后期改用swoole_table进行维护,还可以保证任务最大数(swoole_table在使用的时候就需要初始化内存,多余的数据无法写入),swoole table的坑:

  1. 需要在server->start之前初始化
  2. 需要指定详细的数据类型
  3. 假如在worker中使用了create,会使其在之前的create无效并且无法在进程中共享

给个例子,避免大家走入更多的坑

1 $table = new swoole_table(20); 2 $table->column('t',swoole_table::TYPE_INT); 3 $table->column('x',swoole_table::TYPE_INT); 4 $table->create(); 5 $server = new swoole_http_server('0.0.0.0',9999); 6 $server->table = $table; 7 8$server->on('Request', function($request, $response) use($server){ 9 if($request->server['request_uri'] == '/favicon.ico') { 10 $response->end(); 11 return; 12 } 13$uri = ltrim($request->server['request_uri'],'/'); 14 $server->table->set($uri, [ 15 't' => 1, 16 'x' => 1 17 ]); 18 $response->end(''); 19});

总结

由于是试验阶段,具体细节已经忘了很多,所以文中记录不太详细,有需要咨询详情的可以给我留言,也可以加我q:285753421

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )