PHP用swoole实现爬虫(二)

Stella981
• 阅读 567

本文大部分代码为伪代码,具体实现:一个简单的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事件回调函数。

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

task的问题

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

$server->on('requset'. function(\swoole_request $request, \swoole_response $response) use($server){
    $data=json_decode($request->rawContent());
    $server->task($data);  //将任务投递到task
});


$server->on('task',function(\swoole_server $serv, $task_id, $data){
    while(true){
        //执行爬虫操作
            
        }
$server->finish();
});

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

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

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

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

  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父进程之间可以方便的通信

具体实现伪代码如下:

$server->on('request',function($req,$res){
    if request is crawler 
        then 
            if start
                new swoole_process(function() use(data){ 
                      do crawler
                }) ;
                cache log processId
            if stop
                cache get processId
                kill processId
            if reload
                goto stop  
                goto start 
    else
        do other
});    

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

优化

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

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

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

   $table = new swoole_table(20);
    $table->column('t',swoole_table::TYPE_INT);
    $table->column('x',swoole_table::TYPE_INT);
    $table->create();
    $server = new swoole_http_server('0.0.0.0',9999);
    $server->table = $table; 

$server->on('Request', function($request, $response) use($server){
        if($request->server['request_uri'] == '/favicon.ico') {
                $response->end();
                return;
        }
$uri = ltrim($request->server['request_uri'],'/');
         $server->table->set($uri, [
                    't'     =>      1,
            'x'     =>      1
        ]);
        $response->end('');
});

总结

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

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
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
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Stella981 Stella981
2年前
Google地球出现“无法连接到登录服务器(错误代码:c00a0194)”解决方法
Google地球出现“无法连接到登录服务器(错误代码:c00a0194)”解决方法参考文章:(1)Google地球出现“无法连接到登录服务器(错误代码:c00a0194)”解决方法(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fwww.codeprj.com%2Fblo
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这