Deprecated
: htmlspecialchars(): Passing null to parameter #1 ($string) of type string is deprecated in
/www/wwwroot/testblog.58heshihu.com/var/Widget/Archive.php
on line
1057
首页
关于
Search
1
给你10个市场数据调研报告的免费下载网站!以后竞品数据就从这里找!
182 阅读
2
php接口优化 使用curl_multi_init批量请求
144 阅读
3
《从菜鸟到大师之路 ElasticSearch 篇》
107 阅读
4
2024年备考系统架构设计师
104 阅读
5
PHP 文件I/O
92 阅读
php
thinkphp
laravel
工具
开源
mysql
数据结构
总结
思维逻辑
令人感动的创富故事
读书笔记
前端
vue
js
css
书籍
开源之旅
架构
消息队列
docker
教程
代码片段
redis
服务器
nginx
linux
科普
java
c
ElasticSearch
测试
php进阶
php基础
登录
Search
标签搜索
php函数
php语法
性能优化
安全
错误和异常处理
问题
vue
Composer
Session
缓存
框架
Swoole
api
并发
异步
正则表达式
php-fpm
mysql 索引
开发规范
协程
dafenqi
累计撰写
786
篇文章
累计收到
28
条评论
首页
栏目
php
thinkphp
laravel
工具
开源
mysql
数据结构
总结
思维逻辑
令人感动的创富故事
读书笔记
前端
vue
js
css
书籍
开源之旅
架构
消息队列
docker
教程
代码片段
副业
redis
服务器
nginx
linux
科普
java
c
ElasticSearch
测试
php进阶
php基础
页面
关于
搜索到
10
篇与
的结果
2024-01-02
Swoole 异步 PHP 编程入门:构建高性能应用程序
Swoole 异步 PHP 编程入门:构建高性能应用程序近年来,随着 Swoole 的推出,异步编程在 PHP 社区中越来越受欢迎。Swoole 是一个强大的扩展,为 PHP 带来了事件驱动、非阻塞的特性。本教程将引导您了解使用 Swoole 进行异步 PHP 编程的世界,从设置开发环境到构建实用的异步应用程序。我们还将比较异步代码与传统同步 PHP 脚本的性能。先决条件在我们深入了解 Swoole 之前,请确保您具备以下先决条件:安装了 PHP 7.0 或更高版本。Composer(用于安装Swoole和其他依赖项)。对 PHP 和 Web 开发有基本了解。Swoole简介什么是Swoole? Swoole 将 PHP 转变为高性能、异步和事件驱动的编程环境,支持构建各种可扩展且高效的应用程序,包括 WebSocket 服务器、HTTP 服务器等。事件驱动和异步编程 传统 PHP 代码是同步执行的,即一次执行一个操作,并阻塞直到操作完成。异步编程则允许多个任务同时运行,而不会阻塞主线程。Swoole 利用事件驱动来实现这种并发性。搭建Swoole开发环境使用 Swoole 之前,请按照以下步骤设置您的开发环境:通过 Composer 安装 Swoole 使用 Composer 安装 Swoole创建一个新的 PHP 项目或导航到现有项目:composer require swoole/swoole 创建一个基本的 Swoole 应用程序 创建一个新的 PHP 文件(例如swoole_example.php)并包含 Swoole:<?php require_once 'vendor/autoload.php' ; //代码稍后放在这里理解事件循环 事件循环是异步应用程序的核心。它负责持续检查事件,并调度回调来处理它们。Swoole 提供了自己的事件循环,您可以在应用程序中使用它。以下是如何在 Swoole 中创建和运行事件循环的示例:<?php use Swoole\Event; // 创建事件循环 $event = new Event(); $event->add(function () { echo "Hello from the event loop!\n"; }); // 启动事件循环 $event->loop();构建示例异步应用程序 让我们使用 Swoole 创建一个简单的异步 HTTP 服务器。该服务器将响应传入的 HTTP 请求,并返回“Hello, Swoole!” 的消息。<?php use Swoole\Http\Request; use Swoole\Http\Response; use Swoole\Http\Server; // 创建一个 HTTP 服务器 $server = new Server('127.0.0.1', 9501); // 配置服务器 $server->on('request', function (Request $request, Response $response) { // 设置响应内容类型 $response->header('Content-Type', 'text/plain'); // 发送响应 $response->end('你好,Swoole!'); }); // 启动服务器 $server->start();将此代码保存在文件中(例如,http_server.php)并从命令行运行它:php http_server.php php您的 Swoole HTTP 服务器已启动。服务器地址为 http://127.0.0.1:9501。性能比较为了了解使用 Swoole 进行异步编程的优势,我们将其与传统同步 PHP 脚本的性能进行对比。我们将创建一个简单的脚本来同时执行多个 HTTP 请求。同步 PHP 脚本<?php $urls = [ 'https://example.com', 'https://example.org', 'https://example.net', ]; foreach ($urls as $url) { $content = file_get_contents($url); echo "从 $url 获取\n"; }异步 Swoole 脚本<?php use Swoole\Coroutine\Http\Client; $urls = [ 'https://example.com', 'https://example.org', 'https://example.net', ]; foreach ($urls as $url) { go(function () use ($url) { $client = new Client($url); $client->get('/'); echo "从 $url 获取\n"; }); } Swoole\Event::wait();运行这两个脚本,您会发现:异步 Swoole 脚本可以并发从多个 URL 获取内容。同步脚本则顺序执行。使用 Swoole 进行异步 PHP 编程为构建高性能应用程序开辟了新的可能性。在本教程中,我们介绍了 Swoole,搭建了开发环境,解释了事件循环,并创建了一个简单的异步 HTTP 服务器。此外,我们还比较了异步代码与传统同步 PHP 脚本的性能。
2024年01月02日
15 阅读
0 评论
0 点赞
2024-01-01
如何使用PHP微服务实现分布式数据存储和检索
如何使用PHP微服务实现分布式数据存储和检索随着互联网技术的不断发展,数据的规模也在迅速增长。传统的数据存储和检索方式往往面临着各种限制,例如单机存储容量限制、性能瓶颈、单点故障等等。为了解决这些问题,微服务架构逐渐兴起,并逐渐应用于各个领域。本文将介绍如何使用PHP微服务来实现分布式数据存储和检索,并给出相关的代码示例。一、什么是微服务架构微服务架构是一种通过将应用程序拆分成多个小型服务并运行在独立的进程中,从而实现松耦合、独立可伸缩、轻量级的架构。每个微服务都可以独立部署、独立开发、独立测试,并通过网络调用来实现服务之间的通信。微服务架构可以有效地解决传统单体应用的各种问题,并提供更高的灵活性和可伸缩性。二、分布式数据存储和检索的挑战在传统的单机存储环境中,由于存储容量和性能的限制,很难应对大规模数据的存储和检索需求。而且,单点故障也会造成数据的不可用。为了解决这些问题,我们可以使用分布式数据存储和检索技术。分布式数据存储和检索技术可以将数据分布到多个节点上进行存储,每个节点都是一个独立的服务。通过让数据分布在多个节点上,不仅可以提高存储容量和性能,还可以提高数据的可用性。同时,还可以通过在服务之间进行数据同步,保持数据的一致性。三、使用PHP微服务实现分布式数据存储和检索我们可以使用PHP微服务框架来实现分布式数据存储和检索。下面是一个简单的示例:创建一个存储微服务首先,我们需要创建一个存储微服务,用于存储数据。可以使用PHP的Swoole扩展来创建一个HTTP服务器,处理存储请求。<?php $http = new SwooleHttpServer("0.0.0.0", 9501); $http->on('request', function ($request, $response) { // 处理存储请求 $data = $request->post['data']; // 存储数据 // ... $response->header("Content-Type", "text/plain"); $response->end("Stored successfully "); }); $http->start();创建一个检索微服务然后,我们需要创建一个检索微服务,用于检索存储的数据。同样,可以使用PHP的Swoole扩展来创建一个HTTP服务器,处理检索请求。<?php $http = new SwooleHttpServer("0.0.0.0", 9502); $http->on('request', function ($request, $response) { // 处理检索请求 $keyword = $request->get['keyword']; // 检索数据 // ... $response->header("Content-Type", "text/plain"); $response->end("Retrieved successfully "); }); $http->start();客户端调用示例最后,我们可以编写一个PHP脚本来演示如何通过客户端调用这两个微服务。<?php $client = new SwooleCoroutineHttpClient('127.0.0.1', 9501); $client->post('/store', ['data' => 'Hello, World!']); $response = $client->body; $client = new SwooleCoroutineHttpClient('127.0.0.1', 9502); $client->get('/search', ['keyword' => 'Hello']); $response = $client->body;通过以上代码示例,我们可以看到,通过使用PHP微服务框架,我们可以简单地实现分布式数据存储和检索。通过创建存储和检索微服务,并在客户端调用这两个微服务,就可以实现数据的存储和检索功能。结论:通过使用PHP微服务实现分布式数据存储和检索,我们可以克服传统数据存储和检索方式的各种限制。分布式数据存储和检索技术可以提高存储容量和性能,保障数据的可用性,并提供更高的灵活性和可伸缩性。希望本文对您理解如何使用PHP微服务实现分布式数据存储和检索有所帮助。
2024年01月01日
11 阅读
0 评论
0 点赞
2023-08-10
Swoole进程结构
Swoole进程结构一、进程的基本知识什么是进程,所谓进程其实就是操作系统中一个正在运行的程序,我们在一个终端当中,通过php,运行一个php文件,这个时候就相当于我们创建了一个进程,这个进程会在系统中驻存,申请属于它自己的内存空间系统资源并且运行相应的程序对于一个进程来说,它的核心内容分为两个部分,一个是它的内存,这个内存是这进程创建之初从系统分配的,它所有创建的变量都会存储在这一片内存环境当中一个是它的上下文环境我们知道进程是运行在操作系统的,那么对于程序来说,它的运行依赖操作系统分配给它的资源,操作系统的一些状态。在操作系统中可以运行多个进程的,对于一个进程来说,它可以创建自己的子进程,那么当我们在一个进程中创建出若干个子进程的时候那么可以看到如图,子进程和父进程一样,拥有自己的内存空间和上下文环境clipboard.png二、Swoole进程结构Swoole的高效不仅仅于底层使用c编写,他的进程结构模型也使其可以高效的处理业务,我们想要深入学习,并且在实际的场景当中使用必须了解,下面我们先看一下结构图:clipboard.png首先先介绍下swoole的这几种进程分别是干什么的:从这些层级的名字,我们先大概说一下,下面这些层级分别是干什么的,做一个详细的说明。Master进程:主进程Manger进程:管理进程Worker进程:工作进程Task进程:异步任务工作进程1、Master进程第一层,Master进程,这个是swoole的主进程,这个进程是用于处理swoole的核心事件驱动的,那么在这个进程当中可以看到它拥有一个MainReactor[线程]以及若干个Reactor[线程],swoole所有对于事件的监听都会在这些线程中实现,比如来自客户端的连接,信号处理等。clipboard.png每一个线程都有自己的用途,下面多每个线程有一个了解MainReactor(主线程)主线程会负责监听server socket,如果有新的连接accept,主线程会评估每个Reactor线程的连接数量。将此连接分配给连接数最少的reactor线程,做一个负载均衡。Reactor线程组Reactor线程负责维护客户端机器的TCP连接、处理网络IO、收发数据完全是异步非阻塞的模式。swoole的主线程在Accept新的连接后,会将这个连接分配给一个固定的Reactor线程,在socket可读时读取数据,并进行协议解析,将请求投递到Worker进程。在socket可写时将数据发送给TCP客户端。心跳包检测线程(HeartbeatCheck)Swoole配置了心跳检测之后,心跳包线程会在固定时间内对所有之前在线的连接发送检测数据包UDP收包线程(UdpRecv)接收并且处理客户端udp数据包2、管理进程ManagerSwoole想要实现最好的性能必须创建出多个工作进程帮助处理任务,但Worker进程就必须fork操作,但是fork操作是不安全的,如果没有管理会出现很多的僵尸进程,进而影响服务器性能,同时worker进程被误杀或者由于程序的原因会异常退出,为了保证服务的稳定性,需要重新创建worker进程。Swoole在运行中会创建一个单独的管理进程,所有的worker进程和task进程都是从管理进程Fork出来的。管理进程会监视所有子进程的退出事件,当worker进程发生致命错误或者运行生命周期结束时,管理进程会回收此进程,并创建新的进程。换句话也就是说,对于worker、task进程的创建、回收等操作全权有“保姆”Manager进程进行管理。再来一张图梳理下Manager进程和Worker/Task进程的关系。clipboard.png3、Worker进程worker 进程属于swoole的主逻辑进程,用户处理客户端的一系列请求,接受由Reactor线程投递的请求数据包,并执行PHP回调函数处理数据生成响应数据并发给Reactor线程,由Reactor线程发送给TCP客户端可以是异步非阻塞模式,也可以是同步阻塞模式4、Task进程taskWorker进程这一进城是swoole提供的异步工作进程,这些进程主要用于处理一些耗时较长的同步任务,在worker进程当中投递过来。三、进程查看及流程梳理当启动一个Swoole应用时,一共会创建2 + n + m个进程,2为一个Master进程和一个Manager进程,其中n为Worker进程数。m为TaskWorker进程数。默认如果不设置,swoole底层会根据当前机器有多少CPU核数,启动对应数量的Reactor线程和Worker进程。我机器为1核的。Worker为1。所以现在默认我启动了1个Master进程,1个Manager进程,和1个worker进程,TaskWorker没有设置也就是为0,当前server会产生3个进程。在启动了server之后,在命令行查看当前产生的进程clipboard.png这三个进程中,所有进程的根进程,也就是例子中的2123进程,就是所谓的Master进程;而2212进程,则是Manager进程;最后的2321进程,是Worker进程。client跟server的交互1、client请求到达 Main Reactor,Client实际上是与Master进程中的某个Reactor线程发生了连接。2、Main Reactor根据Reactor的情况,将请求注册给对应的Reactor (每个Reactor都有epoll。用来监听客户端的变化) 3、客户端有变化时Reactor将数据交给worker来处理4、worker处理完毕,通过进程间通信(比如管道、共享内存、消息队列)发给对应的reactor。 5、reactor将响应结果发给相应的连接请求处理完成示意图:clipboard.png后续准备本文是在自己学习Swoole接触到的一些知识,在初步整理后发送出来,希望能与大家一起学习,文章不足等问题大家可以一起讨论学习,欢迎骚扰~~。后面准备从网络模型入手更好的理解swoole的实现原理,比较与传统PHP-FPM工作模式的问题,之前出过一篇关于(一)如何实现一个单进程阻塞的网络服务器大家可以先了解下,如何一步步演变为多进程master-worker模型。
2023年08月10日
17 阅读
0 评论
0 点赞
2023-08-10
swoole 协程初体验
swoole 协程初体验date: 2018-5-30 14:31:38title: swoole| swoole 协程初体验description: 通过协程的执行初窥 swoole 中协程的调度; 理解协程为什么快; swoole 协程和 go 协程对比折腾 swoole 协程有一段时间了, 总结一篇入门贴, 希望对新手有帮助.内容概览:协程的执行顺序: 初窥 swoole 中协程的调度协程为什么快: 减少IO阻塞带来的性能损耗swoole 协程和 go 协程对比: 单进程 vs 多线程协程的执行顺序先来看看基础的例子:go(function () {echo "hello go1 \n";});echo "hello main \n";go(function () {echo "hello go2 \n";});go() 是 \Co::create() 的缩写, 用来创建一个协程, 接受 callback 作为参数, callback 中的代码, 会在这个新建的协程中执行.备注: \Swoole\Coroutine 可以简写为 \Co上面的代码执行结果:root@b98940b00a9b /v/w/c/p/swoole# php co.phphello go1hello mainhello go2执行结果和我们平时写代码的顺序, 好像没啥区别. 实际执行过程:运行此段代码, 系统启动一个新进程遇到 go(), 当前进程中生成一个协程, 协程中输出 heelo go1, 协程退出进程继续向下执行代码, 输出 hello main再生成一个协程, 协程中输出 heelo go2, 协程退出运行此段代码, 系统启动一个新进程. 如果不理解这句话, 你可以使用如下代码:// co.php<?phpsleep(100);执行并使用 ps aux 查看系统中的进程:root@b98940b00a9b /v/w/c/p/swoole# php co.php &⏎root@b98940b00a9b /v/w/c/p/swoole# ps auxPID USER TIME COMMAND1 root 0:00 php -a10 root 0:00 sh 19 root 0:01 fish 749 root 0:00 php co.php 760 root 0:00 ps aux⏎我们来稍微改一改, 体验协程的调度:use Co;go(function () {Co::sleep(1); // 只新增了一行代码 echo "hello go1 \n";});echo "hello main \n";go(function () {echo "hello go2 \n";});\Co::sleep() 函数功能和 sleep() 差不多, 但是它模拟的是 IO等待(IO后面会细讲). 执行的结果如下:root@b98940b00a9b /v/w/c/p/swoole# php co.phphello mainhello go2hello go1怎么不是顺序执行的呢? 实际执行过程:运行此段代码, 系统启动一个新进程遇到 go(), 当前进程中生成一个协程协程中遇到 IO阻塞 (这里是 Co::sleep() 模拟出的 IO等待), 协程让出控制, 进入协程调度队列进程继续向下执行, 输出 hello main执行下一个协程, 输出 hello go2之前的协程准备就绪, 继续执行, 输出 hello go1到这里, 已经可以看到 swoole 中 协程与进程的关系, 以及 协程的调度, 我们再改一改刚才的程序:go(function () {Co::sleep(1); echo "hello go1 \n";});echo "hello main \n";go(function () {Co::sleep(1); echo "hello go2 \n";});我想你已经知道输出是什么样子了:root@b98940b00a9b /v/w/c/p/swoole# php co.phphello mainhello go1hello go2⏎协程快在哪? 减少IO阻塞导致的性能损失大家可能听到使用协程的最多的理由, 可能就是 协程快. 那看起来和平时写得差不多的代码, 为什么就要快一些呢? 一个常见的理由是, 可以创建很多个协程来执行任务, 所以快. 这种说法是对的, 不过还停留在表面.首先, 一般的计算机任务分为 2 种:CPU密集型, 比如加减乘除等科学计算IO 密集型, 比如网络请求, 文件读写等其次, 高性能相关的 2 个概念:并行: 同一个时刻, 同一个 CPU 只能执行同一个任务, 要同时执行多个任务, 就需要有多个 CPU 才行并发: 由于 CPU 切换任务非常快, 快到人类可以感知的极限, 就会有很多任务 同时执行 的错觉了解了这些, 我们再来看协程, 协程适合的是 IO 密集型 应用, 因为协程在 IO阻塞 时会自动调度, 减少IO阻塞导致的时间损失.我们可以对比下面三段代码:普通版: 执行 4 个任务$n = 4;for ($i = 0; $i < $n; $i++) {sleep(1); echo microtime(true) . ": hello $i \n";};echo "hello main \n";root@b98940b00a9b /v/w/c/p/swoole# time php co.php1528965075.4608: hello 01528965076.461: hello 11528965077.4613: hello 21528965078.4616: hello 3hello mainreal 0m 4.02suser 0m 0.01ssys 0m 0.00s⏎单个协程版:$n = 4;go(function () use ($n) {for ($i = 0; $i < $n; $i++) { Co::sleep(1); echo microtime(true) . ": hello $i \n"; };});echo "hello main \n";root@b98940b00a9b /v/w/c/p/swoole# time php co.phphello main1528965150.4834: hello 01528965151.4846: hello 11528965152.4859: hello 21528965153.4872: hello 3real 0m 4.03suser 0m 0.00ssys 0m 0.02s⏎多协程版: 见证奇迹的时刻$n = 4;for ($i = 0; $i < $n; $i++) {go(function () use ($i) { Co::sleep(1); echo microtime(true) . ": hello $i \n"; });};echo "hello main \n";root@b98940b00a9b /v/w/c/p/swoole# time php co.phphello main1528965245.5491: hello 01528965245.5498: hello 31528965245.5502: hello 21528965245.5506: hello 1real 0m 1.02suser 0m 0.01ssys 0m 0.00s⏎为什么时间有这么大的差异呢:普通写法, 会遇到 IO阻塞 导致的性能损失单协程: 尽管 IO阻塞 引发了协程调度, 但当前只有一个协程, 调度之后还是执行当前协程多协程: 真正发挥出了协程的优势, 遇到 IO阻塞 时发生调度, IO就绪时恢复运行我们将多协程版稍微修改一下:多协程版2: CPU密集型$n = 4;for ($i = 0; $i < $n; $i++) {go(function () use ($i) { // Co::sleep(1); sleep(1); echo microtime(true) . ": hello $i \n"; });};echo "hello main \n";root@b98940b00a9b /v/w/c/p/swoole# time php co.php1528965743.4327: hello 01528965744.4331: hello 11528965745.4337: hello 21528965746.4342: hello 3hello mainreal 0m 4.02suser 0m 0.01ssys 0m 0.00s⏎只是将 Co::sleep() 改成了 sleep(), 时间又和普通版差不多了. 因为:sleep() 可以看做是 CPU密集型任务, 不会引起协程的调度Co::sleep() 模拟的是 IO密集型任务, 会引发协程的调度这也是为什么, 协程适合 IO密集型 的应用.再来一组对比的例子: 使用 redis// 同步版, redis使用时会有 IO 阻塞$cnt = 2000;for ($i = 0; $i < $cnt; $i++) {$redis = new \Redis(); $redis->connect('redis'); $redis->auth('123'); $key = $redis->get('key');}// 单协程版: 只有一个协程, 并没有使用到协程调度减少 IO 阻塞go(function () use ($cnt) {for ($i = 0; $i < $cnt; $i++) { $redis = new Co\Redis(); $redis->connect('redis', 6379); $redis->auth('123'); $redis->get('key'); }});// 多协程版, 真正使用到协程调度带来的 IO 阻塞时的调度for ($i = 0; $i < $cnt; $i++) {go(function () { $redis = new Co\Redis(); $redis->connect('redis', 6379); $redis->auth('123'); $redis->get('key'); });}性能对比:多协程版root@0124f915c976 /v/w/c/p/swoole# time php co.phpreal 0m 0.54suser 0m 0.04ssys 0m 0.23s⏎同步版root@0124f915c976 /v/w/c/p/swoole# time php co.phpreal 0m 1.48suser 0m 0.17ssys 0m 0.57s⏎swoole 协程和 go 协程对比: 单进程 vs 多线程接触过 go 协程的 coder, 初始接触 swoole 的协程会有点 懵, 比如对比下面的代码:package mainimport ("fmt" "time")func main() {go func() { fmt.Println("hello go") }() fmt.Println("hello main") time.Sleep(time.Second)}14:11 src $ go run test.gohello mainhello go刚写 go 协程的 coder, 在写这个代码的时候会被告知不要忘了 time.Sleep(time.Second), 否则看不到输出 hello go, 其次, hello go 与 hello main 的顺序也和 swoole 中的协程不一样.原因就在于 swoole 和 go 中, 实现协程调度的模型不同.上面 go 代码的执行过程:运行 go 代码, 系统启动一个新进程查找 package main, 然后执行其中的 func mian()遇到协程, 交给协程调度器执行继续向下执行, 输出 hello main如果不添加 time.Sleep(time.Second), main 函数执行完, 程序结束, 进程退出, 导致调度中的协程也终止go 中的协程, 使用的 MPG 模型:M 指的是 Machine, 一个M直接关联了一个内核线程P 指的是 processor, 代表了M所需的上下文环境, 也是处理用户级代码逻辑的处理器G 指的是 Goroutine, 其实本质上也是一种轻量级的线程MPG 模型而 swoole 中的协程调度使用 单进程模型, 所有协程都是在当前进程中进行调度, 单进程的好处也很明显 -- 简单 / 不用加锁 / 性能也高.无论是 go 的 MPG模型, 还是 swoole 的 单进程模型, 都是对 CSP理论 的实现.CSP通信方式, 在1985年时的论文就已经有了, 做理论研究的人, 如果没有能提前几年, 十几年甚至几十年的大胆假设, 可能很难提高了.写在最后今天从 go() 出发, 得以一瞥协程世界, 协程的世界里还有很多很有意思的东西, 需要我们去发现. 比如:我们普通版的代码是当前进程里执行的, 只是单个进程, 可我们现在可能有了很多协程, 会不会有什么奇遇呢?还有一个细节: swoole 中有 Co::sleep() 和 sleep() 2个方法的, 而 go 中只有 time.Sleep() 一个方法?这是 swoole 协程需要经历的一个阶段(毕竟 go 快 10 年了), 还不够 智能的判断 IO阻塞, 所以上面也使用了相应的协程版 redis co\Redis() -- 你得使用配套协程版, 才能达到协程调度的效果.如果对协程的发展阶段感兴趣, 可以阅读下面这篇文章:Why c++ coroutine?Why libgo?: 关于协程全景式的概述的, 推荐花时间读一读想解锁 swoole 协程的更多姿势:Swoole 2.1 正式版发布,协程+通道带来全新的 PHP 编程模式Swoole 4.0 正式版,面向生产环境的 PHP 协程引擎Swoole4-全新的PHP编程模式_韩天峰_PHPCON2018最后, 本期示例代码可以从我的开源项目中获取, 请享用
2023年08月10日
7 阅读
0 评论
0 点赞
2023-08-10
Swoole 中 Process
Swoole 中 Process这篇 blog 折腾了很久才写出来, 问题主要还是在 理解 上. 有时候就是这样,理解了之后就很简单, 不理解就很难; 知道了就很简单, 不知道往往就很难. 所以 stay hungry stay foolish stay young 真的很重要本来计划开发 swoft 框架 中的 Process 模块, 所以需要对 swoole 的 Process 模块要有比较深入的了解才行. 不过根据 swoole 官方 wiki 的实践过程中, 一直有未理解的部分. 之前虽然也做过多次 多进程编程, 但是当真正需要进行框架开发的时候, 就会发现以前学到的知识不够全面, 无法指导整体的设计. 好在一直在坚持, 奉上现在理解的程度.内容一览:进程相关基础操作: fork/exit/kill/wait进程相关高级操作: 主进程退出子进程干完活后也退出; 子进程异常退出主进程自动重启进程间通信(IPC) - 管道(pipe)进程间通信(IPC) - 消息队列(message queue)swoole process 模块提供的更多功能进程相关基础操作进程是什么: 进程是运行者的程序先来看看一个最简单的例子:<?phpecho posix_getpid(); // 获取当前进程的 pidswoole_set_process_name('swoole process master'); // 修改所在进程的进程名sleep(100); // 模拟一个持续运行 100s 的程序, 这样就可以在进程中查看到它, 而不是运行完了就结束作者:daydaygo链接:https://www.jianshu.com/p/4b6326cdaaa7來源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。通过 ps aux 查看进程:未设置进程名设置进程名再来看看 swoole 中使用子进程的基础操作:use Swoole\Process;$process = new Process(function (Process $worker) {if (Process::kill($worker->pid, 0)) { // kill操作常用来杀死进程, 传入 0 可以用来检测进程是否存在 $worker->exit(); // 退出子进程 }});$process->start(); // 启动子进程Process::wait(); // 回收退出的子进程作者:daydaygo链接:https://www.jianshu.com/p/4b6326cdaaa7來源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。new Process(): 通过回调函数来设置子进程将要执行的逻辑$process->start(): 调用 fork() 系统调用, 来生成子进程Process::kill(): kill操作给进程发送信号, 常用来杀死进程, 传入 0 可以用来检测进程是否存在Process::wait(): 调用 wait() 系统调用, 回收子进程, 如果不回收, 子进程会编程 僵尸进程, 浪费系统资源$worker->exit(): 子进程主动退出我在这里有一个疑问:主进程的生命周期是怎么样的? 子进程的生命周期是怎么样的?有这样一个疑问也来自于我之前的思维惯性: 理解一个事物时从事物的生命周期进行理解. 结合 进程是运行着的程序 来一起理解:new Process(): 只有回调函数的逻辑会在进程中执行除此之外的代码都是在主进程中执行进程相关高级操作主进程退出子进程干完活后也退出子进程异常退出主进程自动重启<?phpuse Swoole\Process;class MyProcess1{public $mpid = 0; // master pid, 即当前程序的进程ID public $works = []; // 记录子进程的 pid public $maxProcessNum = 1; public $newIndex = 0; public function __construct() { try { swoole_set_process_name(__CLASS__. ' : master'); $this->mpid = posix_getpid(); $this->run(); $this->processWait(); } catch (\Exception $e) { die('Error: '. $e->getMessage()); } } public function run() { for ($i=0; $i<$this->maxProcessNum; $i++) { $this->createProcess(); } } public function createProcess($index = null) { if (is_null($index)) { $index = $this->newIndex; $this->newIndex++; } $process = new Process(function (Process $worker) use($index) { // 子进程创建后需要执行的函数 swoole_set_process_name(__CLASS__. ": worker $index"); for ($j=0; $j<3; $j++) { // 模拟子进程执行耗时任务 $this->checkMpid($worker); echo "msg: {$j}\n"; sleep(1); } }, false, false); // 不重定向输入输出; 不使用管道 $pid = $process->start(); $this->works[$index] = $pid; return $pid; } // 主进程异常退出, 子进程工作完后退出 public function checkMpid(Process $worker) // demo中使用的引用, 引用表示传的参数可以被改变, 由于传入 $worker 是 \Swoole\Process 对象, 所以不用使用 & { if (!Process::kill($this->mpid, 0)) { // 0 可以用来检测进程是否存在 $worker->exit(); $msg = "master process exited, worker {$worker->pid} also quit\n"; // 需要写入到日志中 file_put_contents('process.log', $msg, FILE_APPEND); // todo: 这句话没有执行 } } // 重启子进程 public function rebootProcess($pid) { $index = array_search($pid, $this->works); if ($index !== false) { $newPid = $this->createProcess($index); echo "rebootProcess: {$index}={$pid}->{$newPid} Done\n"; return; } throw new \Exception("rebootProcess error: no pid {$pid}"); } // 自动重启子进程 public function processWait() { while (1) { if (count($this->works)) { $ret = Process::wait(); // 子进程退出 if ($ret) { $this->rebootProcess($ret['pid']); } } else { break; } } }}new MyProcess1();作者:daydaygo链接:https://www.jianshu.com/p/4b6326cdaaa7來源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。说明以下几点:子进程运行结束后就会退出, 通过 Process::wait() 检测到子进程退出信号执行自动重启, 子进程就会一直执行下去关于函数参数传 引用/指针, 一个很好的理解方式是: 参数可以被修改运行并模拟主进程异常退出:模拟主进程异常退出输出进程间通信(IPC) - 管道(pipe)管道的几个关键词:半双工: 数据单向流动, 一端只读, 一端只写.同步 vs 异步: 默认为同步阻塞模式, 可以使用 swoole_event_add() 添加管道到 swoole 的 event loop 中, 实现异步IO管道类型(数据格式): SOCK_STREAM, 流式, 需要用户自己处理数据的封包/解包; SOCK_DGRAM, 数据报, 每次收发都是一次完整的数据包 (DGRAM/STREAM)注意, swoole wiki - process->write() 中提到 SOCK_DGRAM 并不会乱序丢包先来看一个简单的例子, php从shell管道中读取数据:// get pip data$fp = fopen('php://stdin', 'r');if ($fp) {while ($line = fgets($fp, 4096)) { echo "php get pip data: ". $line; } fclose($fp);}作者:daydaygo链接:https://www.jianshu.com/p/4b6326cdaaa7來源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。从shell管道读取数据swoole process中的管道很强大, 支持 子进程写, 主进程读 以及 主进程写, 子进程读:use Swoole\Process;// 子进程写, 父进程读$process = new Process(function (Process $worker) {$worker->write("worker");});$process->start();$msg = $process->read();echo "from process: $msg", "\n";// 父进程写, 子进程读$process = new Process(function (Process $worker) {$msg = $worker->read(); echo "from master: $msg", "\n";});$process->start();$process->write('master');作者:daydaygo链接:https://www.jianshu.com/p/4b6326cdaaa7來源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。使用管道多次读写注意区分 $worker->write() 和 $process->write(), 之前一直错误的以为这 2 个是相同的, 其实就是把 $process 误以为是子进程, 从而相当于 $process->write() 就是子进程写管道 -- 其实这里是主进程内执行的逻辑, 是主进程写数据到管道, 供子进程读取swoole中其他管道相关操作:异步IOuse Swoole\Process;use Swoole\Event;// 异步IO$process = new Process(function (Process $worker) {$GLOBALS['worker'] = $worker; Event::add($worker->pipe, function (int $pipe) { // 使用 swoole_event_add 添加管道到异步IO /** @var Process $worker */ $worker = $GLOBALS['worker']; $msg = $worker->read(); echo "from master: $msg \n"; $worker->write("hello master"); sleep(2); $worker->exit(0); });});$process->start();$process->write("master msg 1");$msg = $process->read();echo "from process: $msg \n";作者:daydaygo链接:https://www.jianshu.com/p/4b6326cdaaa7來源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。异步IO设置超时use Swoole\Process;// 设置管道超时$process = new Process(function (Process $worker) {sleep(5);});$process->start();$process->setTimeout(0.5);$ret = $process->read();var_dump($ret);var_dump(swoole_errno());作者:daydaygo链接:https://www.jianshu.com/p/4b6326cdaaa7來源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。管道超时插播一个趣事, @thinkpc 看完 2017北京PHP开发者年会, 就知道为啥会点赞了关闭管道// 关闭管道: 默认值0->关闭读写 1->关闭写 2->关闭读$process->close();进程间通信(IPC) - 消息队列(message queue)消息队列:一系列保存在内核中的消息链表有一个 msgKey, 可以通过此访问不同的消息队列有数据大小限制, 默认 8192, 可以通过内核修改阻塞 vs 非阻塞: 阻塞模式下 pop()空消息队列/push()满消息队列会阻塞, 非阻塞模式可以直接返回swoole 中使用消息队列:通信模式: 默认为争抢模式, 无法将消息投递给指定子进程新建消息队列后, 主进程就可以使用消息队列不可和管道一起使用, 也无法使用 swoole event loop主进程中要调用 wait(), 否则子进程中调用 pop()/push() 会报错use Swoole\Process;$process = new Process(function (Process $worker) {// $worker->push('worker'); echo "from master: ". $worker->pop(). "\n"; sleep(2); // $worker->exit();}, false, false); // 关闭管道// 参数一为 msgKey, 这里是默认值// 参数二为 通信模式, 默认值 2 表示争抢模式, 这里还加上了 非阻塞$process->useQueue(ftok(__FILE__, 1), 2| Process::IPC_NOWAIT);$process->push('hello1'); // 使用 useQueue 后, 主进程就可以读写消息队列了$process->push('hello2');echo "from woker: ". $process->pop(). "\n";// echo "from woker: ". $process->pop(). "\n";$process->start(); // 启动子进程// 消息队列状态var_dump($process->statQueue());// 删除队列, 如果不调用则不会在程序结束时清楚数据, 下次使用相同 msgKey 时还可以访问数据$process->freeQueue();var_dump(Process::wait()); // 要调用 wait(), 否则子进程中 push()/pop() 会报错作者:daydaygo链接:https://www.jianshu.com/p/4b6326cdaaa7來源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。消息队列swoole process 模块提供的更多功能swoole_set_process_name(): 修改进程名, 不兼容 macswoole_process->exec(string $execfile, array $args) 执行外部程序参数 $execfile 需要使用可执行文件的绝对路径, 参数 args 为参数数组// 比如 python test.py 123swoole_process->exec('/usr/bin/python', ['test.py', 123]);// 更复杂的例子swoole_process->exec(('/usr/local/bin/php', ['/var/www/project/yii-best-practice/cli/yii', 't/index', '-m=123', 'abc', 'xyz']);// 父进程 exec 进程进行管道通信use Swoole\Process;$process = new Process(function (Process $worker) {$worker->exec('/bin/echo', ['hello']); $worker->write('hello');}, true); // 需要启用标准输入输出重定向$process->start();echo "from exec: ". $process->read(). "\n";作者:daydaygo链接:https://www.jianshu.com/p/4b6326cdaaa7來源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。父进程与exec进程通过管道通信SwooleProcess::kill($pid, $signo = SIGTERM): 向指定进程发送信号, 默认是终止进程, 传 0 可检测进程是否存在SwooleProcess::wait(): 回收子进程, 如果主进程不调用此方法, 子进程会变成 僵尸进程, 浪费系统资源SwooleProcess::signal(): 异步信号监听use Swoole\Process;// 异步信号监听 + waitProcess::signal(SIGCHLD, function ($signal) { // 监听子进程退出信号// 可能同时有多个子进程退出, 所以要while循环 while ($ret = Process::wait(false)) { // false 表示不阻塞 var_dump($ret); }});作者:daydaygo链接:https://www.jianshu.com/p/4b6326cdaaa7來源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。SwooleProcess::daemon(): 将当前进程变为一个守护进程use Swoole\Process;// daemonProcess::daemon();swoole_set_process_name('test daemon process');sleep(100);daemon-守护进程SwooleProcess::alarm(): 高精度定时器(微秒级), 对 setitimer 系统调用的封装, 可以配合 SwooleProcess::signal() / pcntl_signal 使用注意不可和 SwooleTimer 同时使用// signal + alarm// 第一个参数表示时间, 单位 us, -1 表示清除定时器// 第二个参数表示类型 0->真实时间->SIGALAM 1->cpu时间->SIGVTALAM 2->用户态+内核态时间->SIGPROFProcess::alarm(100*1000); // 100msProcess::signal(SIGALRM, function ($signal) {static $i = 0; echo "#$i \t alarm \n"; $i++; if ($i>20) { Process::alarm(-1); // -1 表示清除 }});作者:daydaygo链接:https://www.jianshu.com/p/4b6326cdaaa7來源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。alarmSwooleProcess::setaffinity(): 设置CPU亲和, 即将进程绑定到指定CPU核上传值范围: [0, swoole_cpu_num())CPU亲和: CPU的速度远远高于IO的速度, 所以CPU有多级缓存来解决IO等待的问题, 绑定指定CPU, 更容易命中CPU缓存写在最后资源推荐:图灵社区 - 理解UNIX进程 + 「理解Unix进程」读书笔记blog - 「进程」编程todo:使用输入输出重定向管道类型为 SOCK_STREAM 时的情况, 是否需要 封包/解包 处理, 即 swoole wiki - process->write() 中提到的 管道通信默认的方式是流式,write写入的数据在read可能会被底层合并多进程 + 异步IO 的注意事项能理解 因为子进程会继承父进程的内存和IO句柄 这个会产生的影响, 但是给的示例并没有说明这个问题use Swoole\Process;use Swoole\Event;// 多个子进程 + 异步IO$workers = [];$workerNum = 3;for ($i=0; $i<$workerNum; $i++) {$process = new Process(function (Process $worker) { $worker->write($worker->pid); echo "worker: {$worker->pid} \n"; }); $pid = $process->start(); $workers[$pid] = $process; // Event::add($process->pipe, function (int $pipe) use ($process) { // $data = $process->read(); // echo "recv: $data \n"; // });}foreach ($workers as $worker) {Event::add($worker->pipe, function (int $pipe) use ($worker) { $data = $worker->read(); echo "recv: $data \n"; });}作者:daydaygo链接:https://www.jianshu.com/p/4b6326cdaaa7來源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。多进程异步IO始发于简书:daydaygo
2023年08月10日
19 阅读
0 评论
0 点赞
1
2