Swoft源码之Swoole和Swoft的分析
这篇文章给大家分享的内容是关于Swoft 源码剖析之Swoole和Swoft的一些介绍(Task投递/定时任务篇),有一定的参考价值,有需要的朋友可以参考一下。
前言
Swoft
的任务功能基于Swoole
的Task机制
,或者说Swoft
的Task
机制本质就是对Swoole
的Task机制
的封装和加强。
任务投递
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
|
任务投递Task::deliver()
将调用参数打包后根据$type
参数通过Swoole
的$server->taskCo()
或$server->task()
接口投递到Task进程
。Task
本身始终是同步执行的,$type
仅仅影响投递这一操作的行为,Task::TYPE_ASYNC
对应的$server->task()
是异步投递,Task::deliver()
调用后马上返回;Task::TYPE_CO
对应的$server->taskCo()
是协程投递,投递后让出协程控制,任务完成或执行超时后Task::deliver()
才从协程返回。
任务执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
|
此处是swoole.onTask
的事件回调,其职责仅仅是将将Worker
进程投递来的打包后的数据转发给TaskExecutor
。
Swoole
的Task
机制的本质是Worker进程
将耗时任务投递给同步的Task进程
(又名TaskWorker
)处理,所以swoole.onTask
的事件回调是在Task进程
中执行的。上文说过,Worker进程
是你大部分HTTP
服务代码执行的环境,但是从TaskEventListener.onTask()
方法开始,代码的执行环境都是Task进程
,也就是说,TaskExecutor
和具体的TaskBean
都是执行在Task进程
中的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
|
任务执行思路很简单,将Worker进程
发过来的数据解包还原成原来的调用参数,根据$name
参数找到对应的TaskBean
并调用其对应的task()
方法。其中TaskBean
使用类级别注解@Task(name="TaskName")
或者@Task("TaskName")
声明。
值得一提的一点是,@Task
注解除了name
属性,还有一个coroutine
属性,上述代码会根据该参数选择使用协程的runCoTask()
或者同步的runSyncTask()
执行Task
。但是由于而且由于Swoole
的Task进程
的执行是完全同步的,不支持协程,所以目前版本请该参数不要配置为true
。同样的在TaskBean
中编写的任务代码必须的同步阻塞的或者是要能根据环境自动将异步非阻塞和协程降级为同步阻塞的
从Process中投递任务
前面我们提到:
Swoole
的Task
机制的本质是Worker进程
将耗时任务投递给同步的Task进程
(又名TaskWorker
)处理。
换句话说,Swoole
的$server->taskCo()
或$server->task()
都只能在Worker进程
中使用。
这个限制大大的限制了使用场景。 如何能够为了能够在Process
中投递任务呢?Swoft
为了绕过这个限制提供了Task::deliverByProcess()
方法。其实现原理也很简单,通过Swoole
的$server->sendMessage()
方法将调用信息从Process
中投递到Worker进程
中,然后由Worker进程替其投递到Task进程
当中,相关代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
|
数据打包后使用$server->sendMessage()
投递给Worker
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
$server->sendMessage
后,Worker进程
收到数据时会触发一个swoole.pipeMessage
事件的回调,Swoft
会将其转换成自己的swoft.pipeMessage
事件并触发.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
|
swoft.pipeMessage
事件最终由PipeMessageListener
处理。在相关的监听其中,如果发现swoft.pipeMessage
事件由Task::deliverByProcess()
产生的,Worker进程
会替其执行一次Task::deliver()
,最终将任务数据投递到TaskWorker进程
中。
一道简单的回顾练习:从Task::deliverByProcess()
到某TaskBean
最终执行任务,经历了哪些进程,而调用链的哪些部分又分别是在哪些进程中执行?
从Command进程或其子进程中投递任务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
|
对于Command
进程的任务投递,情况会更复杂一点。
上文提到的Process
,其往往衍生于Http/Rpc
服务,作为同一个Manager
的子孙进程,他们能够拿到Swoole\Server
的句柄变量,从而通过$server->sendMessage()
,$server->task()
等方法进行任务投递。
但在Swoft
的体系中,还有一个十分路人的角色: Command
。Command
的进程从shell
或cronb
独立启动,和Http/Rpc
服务相关的进程没有亲缘关系。因此Command
进程以及从Command
中启动的Process
进程是没有办法拿到Swoole\Server
的调用句柄直接通过UnixSocket
进行任务投递的。
为了为这种进程提供任务投递支持,Swoft
利用了Swoole
的Task进程
的一个特殊功能----消息队列。
同一个项目中Command
和Http\RpcServer
通过约定一个message_queue_key
获取到系统内核中的同一条消息队列,然后Comand
进程就可以通过该消息队列向Task进程
投递任务了。
该机制没有提供对外的公开方法,仅仅被包含在Task::deliver()
方法中,Swoft
会根据当前环境隐式切换投递方式。但该消息队列的实现依赖Semaphore
拓展,如果你想使用,需要在编译PHP
时加上--enable-sysvmsg
参数。
定时任务
除了手动执行的普通任务,Swoft
还提供了精度为秒的定时任务功能用来在项目中替代Linux的Crontab
功能.
Swoft
用两个前置Process
---任务计划进程:CronTimerProcess
和任务执行进程CronExecProcess
,和两张内存数据表-----RunTimeTable
(任务(配置)表)OriginTable
((任务)执行表)用于定时任务的管理调度。
两张表的每行记录的结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
此处为何要使用Swoole的内存Table?
Swoft
的的定时任务管理是分别由 任务计划进程 和 任务执行进程 进程负责的。两个进程的运行共同管理定时任务,如果使用进程间独立的array()
等结构,两个进程必然需要频繁的进程间通信。而使用跨进程的Table
(本文的Table
,除非特别说明,都指Swoole
的Swoole\Table
结构)直接进行进程间数据共享,不仅性能高,操作简单 还解耦了两个进程。
为了Table
能够在两个进程间共同使用,Table
必须在Swoole Server
启动前创建并分配内存。具体代码在Swoft\Task\Bootstrap\Listeners->onBeforeStart()
中,比较简单,有兴趣的可以自行阅读。
背景介绍完了,我们来看看这两个定时任务进程的行为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
CronTimerProcess
是Swoft
的定时任务调度进程,其核心方法是Crontab->initRunTimeTableData()
。
该进程使用了Swoole
的定时器功能,通过Swoole\Timer
在每分钟首秒时执行的回调,CronTimerProcess
每次被唤醒后都会遍历任务表计算出当前这一分钟内的60秒分别需要执行的任务清单,写入执行表并标记为 未执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
|
CronExecProcess
作为定时任务的执行者,通过Swoole\Timer
每0.5s
唤醒自身一次,然后把 执行表
遍历一次,挑选当下需要执行的任务,通过sendMessage()
投递出去并更新该 任务执行表中的状态。
该执行进程只负责任务的投递,任务的实际实际执行仍然在Task进程
中由TaskExecutor
处理。
定时任务的宏观执行情况如下:
明确的学习思路能更高效的学习
Swoft源码之Swoole和Swoft的分析的更多相关文章
- Swoft 源码剖析 - Swoole和Swoft的那些事 (Http/Rpc服务篇)
前言 Swoft在PHPer圈中是一个门槛较高的Web框架,不仅仅由于框架本身带来了很多新概念和前沿的设计,还在于Swoft是一个基于Swoole的框架.Swoole在PHPer圈内学习成本最高的工具 ...
- swoft| 源码解读系列一: 好难! swoft demo 都跑不起来怎么破? docker 了解一下呗~
title: swoft| 源码解读系列一: 好难! swoft demo 都跑不起来怎么破? docker 了解一下呗~description: 阅读 sowft 框架源码, swoft 第一步, ...
- swoft| 源码解读系列二: 启动阶段, swoft 都干了些啥?
date: 2018-8-01 14:22:17title: swoft| 源码解读系列二: 启动阶段, swoft 都干了些啥?description: 阅读 sowft 框架源码, 了解 sowf ...
- swoft 源码解读【转】
官网: https://www.swoft.org/ 源码解读: http://naotu.baidu.com/file/814e81c9781b733e04218ac7a0494e2a?toke ...
- Java集合源码学习(四)HashMap分析
ArrayList.LinkedList和HashMap的源码是一起看的,横向对比吧,感觉对这三种数据结构的理解加深了很多. >>数组.链表和哈希表结构 数据结构中有数组和链表来实现对数据 ...
- Java集合源码学习(三)LinkedList分析
前面学习了ArrayList的源码,数组是顺序存储结构,存储区间是连续的,占用内存严重,故空间复杂度很大.但数组的二分查找时间复杂度小,为O(1),数组的特点是寻址容易,插入和删除困难.今天学习另外的 ...
- Java集合源码学习(二)ArrayList分析
>>关于ArrayList ArrayList直接继承AbstractList,实现了List. RandomAccess.Cloneable.Serializable接口,为什么叫&qu ...
- Apache Spark源码走读之6 -- 存储子系统分析
欢迎转载,转载请注明出处,徽沪一郎. 楔子 Spark计算速度远胜于Hadoop的原因之一就在于中间结果是缓存在内存而不是直接写入到disk,本文尝试分析Spark中存储子系统的构成,并以数据写入和数 ...
- 【 js 基础 】【 源码学习 】源码设计 (更新了backbone分析)
学习源码,除了学习对一些方法的更加聪明的代码实现,同时也要学习源码的设计,把握整体的架构.(推荐对源码有一定熟悉了之后,再看这篇文章) 目录结构:第一部分:zepto 设计分析 第二部分:unders ...
随机推荐
- mac外接显示器 字体发虚解决方案
描述 今天买的LG性价比之王21:9的2k显示器到了,但是连接mbp后发现,字体发虚,模糊 Macbook 外接显示器默认为 TV 模式,TV 渲染模式下,文字效果非常非常非常差 解决 下载 patc ...
- Redis(二)数据结构与键管理
一.基础知识 1.全局命令 keys * :查看所有键 dbsize:返回当前数据库中键的总数 exists key:检查键是否存在 del key ... :删除键 expire key sec ...
- InfluxDB常见疑问与解答 - 数据写入时如何在表级别指定保留策略
网友Siguoei:我想让一个库中不同的measurment能够指定不同的保存策略.而不是写入时使用数据库的默认保留策略. Answer:这个特性InfluxDB支持的,写入时序数据时,在行协议前加上 ...
- 学习笔记52_mongodb增删改查
使用use db1作为当前数据库 如果没有db1,会自动创建 使用switch db2,当前数据库切换为db2 使用show dbs,显示当前所有数据库 使用show collection ,显示当前 ...
- Vue学习笔记(五)——配置开发环境及初建项目
前言 在上一篇中,我们通过初步的认识,简单了解 Vue 生命周期的八个阶段,以及可以应用在之后的开发中,针对不同的阶段的钩子采取不同的操作,更好的实现我们的业务代码,处理更加复杂的业务逻辑. 而在这一 ...
- Go 基础学习笔记 (5)| 数据类型说明与使用
在 Go 编程语言中,数据类型用于声明函数和变量. 数据类型的出现是为了把数据分成所需内存大小不同的数据,编程的时候需要用大数据的时候才需要申请大内存,就可以充分利用内存. Go 语言按类别有以下几种 ...
- Spring mvc之源码 handlerMapping和handlerAdapter分析
Spring mvc之源码 handlerMapping和handlerAdapter分析 本篇并不是具体分析Spring mvc,所以好多细节都是一笔带过,主要是带大家梳理一下整个Spring mv ...
- LCD 调试总结
(1) 液晶显示模式 并行:MCU接口.RGB接口.Vysnc接口 串行:SPI接口.MDDI接口 (2) 屏幕颜色 实质上即为色阶的概念.色阶是表示手机液晶显示屏亮度强弱的指数标准,也就是通常所说的 ...
- 原来JS是这样的 - 原型链
上一篇提到属性描述符 [[Get]] 和 [[Put]] 以及提到了访问描述符 [[Prototype]],看它们的特性就会很容易的让人想到经典的面向对象风格体系中对类操作要做的事情,但带一些 int ...
- element 动态合并表格
前言 element 官方的例子太简单了,不满足实际的需求 数据肯定是动态的,合并的行数,列数都是动态的,该如何知道每一行的合并数呢 需求 动态合并表格,数据来源于数据库 正文 一开始,我的数据源是单 ...