php-msf源码详解

我们来看分享下具体源码:

源码解读也做了一段时间了,总结一下自己的心得:

抓住 生命周期,让代码在你脑海中 跑起来

分析架构,关键字 分层 边界 隔离

一个好的框架,弄清楚 生命周期 和 架构,基本就已经到了 熟悉 的状态了,之后是填充细节和编码熟练了

这里再介绍几个次重要的心得:

弄明白这个工具擅长干什么,适合干什么. 这个信息也非常容易获取到,工具的文档通常都会显眼标注出来,可以通过这些 功能/特性,尝试以点见面

从工程化的角度去看这个项目,主要和上面的 架构 区分,在处理核心业务,也就是上面的 功能/特性 外,工程化还涉及到 安全/测试/编码规范/语言特性 等方面,这些也是平时在写业务代码时思考较少并且实践较少的部分

工具的使用,推荐我现在使用的组合: phpstorm + 百度脑图 + Markdown笔记 + blog和 php-msf 的渊源等写技术生活相关的 blog 再来和大家八,直接上菜.

生命周期 & 架构

官方文档制作了一张非常好的图: 处理请求流程图. 推荐各位同仁,有闲暇时制作类似的图,对思维很有的帮助.

根据这张图来思考 生命周期 & 架构,这里就不赘述了,这里分析一下 msf 中一些技术点:

协程相关知识

msf 中技术点摘录

协程

我会用我的方式来讲解,如果需要深入了解的,可以看我后面推荐的资源.

类 vs 对象 是一组很重要的概念. 类代表我们对事物的抽象,这个抽象的能力在我们以后会一直用到,希望大家有意识的培养这方面的意识,至少可以起到触类旁通的作用. 对象是 实例化 的类,是 真正干活的,我们要讨论的 协程,就是这样一个 真正干活的 角色.

协程从哪里来,到哪里去,它是干什么的?

想一想这几个简单的问题,也许你对协程的理解就更深刻了,记住这几个关键词:

产生. 需要有地方来产生协程,你可能不需要知道细节,但是需要知道什么时候发生了

调度. 肯定是有很多协程一起工作的,所以需要调度,怎么调度的呢?

销毁. 是否会销毁? 什么时候销毁?

现在,我们再来看看协程的使用方式对比,这里注意一下,我没有用 协程的实现方式对比,因为很多时候,需求实际是这样的:

怎么实现我不管,我选最好用的.

getRedisPool('tw')->get('apiCacheForABCoroutine'); // msf - 并发协程调用 $client1 = $this->getObject(Client::class,['http://www.baidu.com/']); yield $client1->goDnsLookup(); $client2 = $this->getObject(Client::class,['http://www.qq.com/']); yield $client2->goDnsLookup(); $result[] = yield $client1->goGet('/'); $result[] = yield $client2->goGet('/');

大致 是这样的一个等式: 使用协程 = 加上 yield,所以搞清楚哪些地方需要加上 yield 就好了 -- 有阻塞IO的地方,比如 文件IO,网络IO(redis/mysql/http) 等.

当然,大致 就是还有需要注意的地方

协程调度顺序,如果不注意,就可能会退化成同步调用.

调用链: 使用 yield 的调用链上,都需要加上 yield. 比如下面这样:

getRedisPool('tw')->get('apiCacheForABCoroutine'); } $res = yield a_test(); // 如果不加 yield,就变成了同步执行

对比一下 swoole2.0 的协程方案:

set([ 'worker_num' => 1,]); // 需要在协程 server 的异步回调函数中 $server->on('Request',function ($request,$response) { $tcpclient = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); // 需要配合使用协程客户端 $tcpclient->connect('127.0.0.1',9501,0.5) $tcpclient->send("hello world\n"); $redis = new Swoole\Coroutine\Redis(); $redis->connect('127.0.0.1',6379); $redis->setDefer(); // 标注延迟收包,实现并发调用 $redis->get('key'); $mysql = new Swoole\Coroutine\MySQL(); $mysql->connect([ 'host' => '127.0.0.1','user' => 'user','password' => 'pass','database' => 'test',]); $mysql->setDefer(); $mysql->query('select sleep(1)'); $httpclient = new Swoole\Coroutine\Http\Client('0.0.0.0',9599); $httpclient->setHeaders(['Host' => "api.mp.qq.com"]); $httpclient->set([ 'timeout' => 1]); $httpclient->setDefer(); $httpclient->get('/'); $tcp_res = $tcpclient->recv(); $redis_res = $redis->recv(); $mysql_res = $mysql->recv(); $http_res = $httpclient->recv(); $response->end('Test End'); }); $server->start();

使用 swoole2.0 的协程方案,好处很明显:

不用加 yield 了

并发调用不用刻意注意 yield 的顺序了,使用 defer() 延迟收包即可

但是,没办法直接用 使用协程 = 加上 yield 这样一个简单的等式了,上面的例子需要配合使用 swoole 协程 server + swoole 协程 client:

server 在异步回调触发时 生成协程

client 触发 协程调度

异步回调执行结束时 销毁协程

这就导致了 2 个问题:

不在 swoole 协程 server 的异步回调中怎么办: 使用 Swoole\Coroutine::create() 显式生成协程

需要使用其他的协程 Client 怎么办: 这是 Swoole3 的目标,Swoole2.0 可以考虑用协程 task 来伪装

这样看起来,好像 使用协程 = 加上 yield 这样要简单一些? 我不这样认为,补充一些观点,大家自己斟酌:

使用 yield 的方式,基于 php 生成器 + 自己实现 PHP 协程调度器,想要用起来不出错,比如上面 协程调度顺序,你还是需要去弄清楚这块的实现

Swoole2.0 的原生方式,理解起来其实更容易,只需要知道协程 生成/调度/销毁 的时机就可以用好

Swoole2.0 这样异步回调中频繁创建和销毁协程,是否十分损耗性能? -- 不会的,实际是一些内存操作,比进程/对象小很多

msf 中技术点摘录

msf 在设计上有很多出彩的地方,很多代码都值得借鉴.

请求上下文 Context

这是从 fpm 到 swoole http server 非常重要的概念. fpm 是多进程模式,虽然 $_POST 等变量,被称之为超全局变量,但是,这些变量在不同 fpm 进程间是隔离的. 但是到了 swoole http server 中,一个 worker 进程,会异步处理多个请求,简单理解就是下面的等式:

所以,我们就需要一种新的方式,来进行 request 间的隔离.

在编程语言里,有一个专业词汇 scope(作用域). 通常会使用 scope/生命周期,所以我一直强调的生命周期的概念,真的很重要.

swoole 本身是实现了隔离的:

on('request',$response) { $response->end("

Hello Swoole. #".rand(1000,9999)."

"); }); $http->start();

msf 在 Context 上还做了一层封装,让 Context 看起来 为所欲为:

getContext()->xxxModule->xxxModuleFunction();

细节可以查看 src/Helpers/Context.php 文件

对象池

对象池这个概念,大家可能比较陌生,目的是减少对象的频繁创建与销毁,以此来提升性能,msf 做了很好的封装,使用很简单:

getObject(DemoModel::class,[1,2]);

对象池的具体代码在 src/Base/Pool.php 下:

底层使用反射来实现对象的动态创建

if (!$poolName) {
return null;
}

$pool = $this->map[$poolName] ?? null;
if ($pool == null) {
$pool = $this->applyNewPool($poolName);
}

if ($pool->count()) {
$obj = $pool->shift();
$obj->__isConstruct = false;
return $obj;
} else {
// 使用反射
$reflector = new \ReflectionClass($poolName);
$obj = $reflector->newInstanceWithoutConstructor();

$obj->__useCount  = 0;
$obj->__genTime  = time();
$obj->__isConstruct = false;
$obj->__DSLevel  = Macro::DS_PUBLIC;
unset($reflector);
return $obj;

}
}

使用 SplStack 来管理对象

map)) { throw new Exception('the name is exists in pool map'); } $this->map[$poolName] = new \SplStack();

return $this->map[$poolName];
}
// 管理对象
$pool->push($classInstance);
$obj = $pool->shift();

连接池 & 代理

连接池 Pools

连接池的概念就不赘述了,我们来直接看 msf 中的实现,代码在 src/Pools/AsynPool.php 下:

callBacks = []; $this->commands = new \SplQueue(); $this->pool = new \SplQueue(); $this->config = $config; }

这里使用的 SplQueue 来管理连接和需要执行的命令. 可以和上面对比一下,想一想为什么一个使用 SplStack,一个使用 SplQueue.

代理 Proxy

代理是在连接池的基础上进一步的封装,msf 提供了 2 种封装方式:

主从 master slave

集群 cluster

查看示例 App\Controllers\Redis 中的代码:

getRedisPool('p1')->set('key1','val1'); $val = yield $this->getRedisPool('p1')->get('key1');
$this->outputJson($val);

}
// Redis代理使用示例(分布式)
public function actionProxySetGet()
{
for ($i = 0; $i <= 100; $i++) {
yield $this->getRedisProxy('cluster')->set('proxy' . $i,$i);
}
$val = yield $this->getRedisProxy('cluster')->get('proxy22');
$this->outputJson($val);
}

// Redis代理使用示例(主从)
public function actionMaserSlaveSetGet()
{
for ($i = 0; $i <= 100; $i++) {
yield $this->getRedisProxy('master_slave')->set('M' . $i,$i);
}

$val = yield $this->getRedisProxy('master_slave')->get('M66');
$this->outputJson($val);

}
}

代理就是在连接池的基础上进一步 搞事情. 以 主从 模式为例:

主从策略: 读主库,写从库 代理做的事情:

判断是读操作还是写操作,选择相应的库去执行 公共库

msf 推行 公共库 的做法,希望不同功能组件可以做到 可插拔,这一点可以看 laravel 框架和 symfony 框架,都由框架核心加一个个的 package 组成. 这种思想我是非常推荐的,但是仔细看 百度脑图 - php-msf 源码解读 这张图的话,就会发现类与类之间的依赖关系,分层/边界 做得并不好. 如果看过我之前的 blog - laravel源码解读 / blog - yii源码解读,进行对比就会感受很明显.

但是,这并不意味着 代码不好,至少功能正常的代码,几乎都能算是好代码. 从功能之外建立的 优越感,更多的是对 美好生活的向往 -- 还可以更好一点.

AOP

php AOP 扩展: http://pecl.php.net/package/aop

PHP-AOP扩展介绍 | rango: http://rango.swoole.com/archives/83

AOP,面向切面编程,韩老大 的 blog - PHP-AOP扩展介绍 | rango 可以看看.

需不需要了解一个新事物,先看看这个事物有什么作用:

AOP,将业务代码和业务无关的代码进行分离,场景有 日志记录 / 性能统计 / 安全控制 / 事务处理 / 异常处理 / 缓存 等等. 这里引用一段 程序员DD - 翟永超的公众号 文章里的代码,让大家感受下:

同样是 CRUD,不使用 AOP

delete(long id,String lang) { Map data = new HashMap(); boolean result = false; try { // 语言(中英文提示不同) Locale local = "zh".equalsIgnoreCase(lang) ? Locale.CHINESE : Locale.ENGLISH; result = configService.delete(id,local); data.put("code",0); } catch (CheckException e) { // 参数等校验出错,这类异常属于已知异常,不需要打印堆栈,返回码为-1 data.put("code",-1); data.put("msg",e.getMessage()); } catch (Exception e) { // 其他未知异常,需要打印堆栈分析用,返回码为99 log.error(e);

data.put("code",99);
data.put("msg",e.toString());
}
data.put("result",result);
return data;
}

使用 AOP

delete(long id) { return new ResultBean(configService.delete(id)); }

代码只用一行,需要的特性一个没少,你是不是也想写这样的 CRUD 代码?

配置文件管理

先明确一下配置管理的痛点:

是否支撑热更新,常驻内存需要考虑

考虑不同环境: dev test production

方便使用

热更其实可以算是常驻内存服务器的整体需求,目前 php 常用的解决方案是 inotify,可以参考我之前的 blog - swoft 源码解读 .

msf 使用第三方库来解析处理配置文件,这里着重提一个 array_merge() 的细节:

[ 'a1' => 'a1',]]; $b = ['a' => [ 'b1' => 'b1',]]; $arr = array_merge($a,$b); // 注意,array_merge() 并不会循环合并 var_dump($arr); // 结果 array(1) { ["a"]=> array(1) { ["b1"]=> string(2) "b1" } }

msf 中使用配置:

getConfig()->get('params.mock_ids',[]); // 对比一下 laravel $ids = cofnig('params.mock_ids',[]);

看起来 laravel 中要简单一些,其实是通过 composer autoload 来加载函数,这个函数对实际的操作包装了一层. 至于要不要这样做,就看自己需求了.

写在最后

msf 最复杂的部分在 服务启动阶段,继承也很长:

Child -> Server -> HttpServer -> MSFServer -> AppServer,有兴趣可以挑战一下.

另外一个比较难的点,是 MongoDbTask 实现原理.

msf 还封装了很多有用的功能,RPC / 消息队列 / restful,大家根据文档自己探索即可.

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


极简概括: PHP 的开源内存缓存扩展,类比Redis,但是一般都用Redis,所以APCu用的很少。官方文档:https://www.php.net/manual/zh/apcu.configuration.php解决问题:类比Redis做缓存组件,提升性能,同步数据使用。
请看如下代码: $list = [1,2,4,5]; $list2 = [5,6,7,9]; foreach ($list as $key =&gt; &amp;$value) { $value = strval($value); } foreach ($list2 as $key =&gt; $v
一、nginx 安装 1. 在nginx官网下载nginx源码&#x9;提供一个nginx官网下载地址: http://nginx.org/download/nginx-1.12.2.tar.gz 注意:请先确认 是否已经安装过 pcre pcre-devel openssl openssl-devel z
先看图 数据条数:9000+ 第1,2行,golangʾxcelize方式导出,耗时:5s 第3行,PHP+xlswriter方式导出,耗时:2min 一、介绍 xlswriter是一个高效处理excel文件的PHP扩展,底层以C语言实现;处理速度是PHPExcel几十倍甚至几百倍的效率。 官方链
今天使用Thinkphp5做异步任务传递where参数时遇到一个问题: 有一段如下代码: $where[&#39;jst.supplier&#39;] = [&#39;exp&#39;, Db::raw(&#39;&gt;0 or jst.is_supplier=1&#39;)]; 在使用swool
汇总 PHP5.1: autoload PDO MySQLi 类型约束 PHP5.2: JSON 支持 PHP5.3: 命名空间 匿名函数 闭包 新增魔术方法__callStatic()和__invoke() 新增魔术变量__DIR__ 动态调用静态方法 延迟静态绑定 Heredoc和 Nowdoc
文章浏览阅读8.4k次,点赞8次,收藏7次。SourceCodester Online Tours & Travels Management System pay.php sql injectionLine 16 of pay.php invokes a SQL query built using unvalidated input. This call could allow an attacker to modify the statement’s meaning or to execute arbitrary SQL commands.SQL
文章浏览阅读3.4k次,点赞46次,收藏51次。本文为大家介绍在windwos系统搭建typecho博客+cpolar内网穿透工具将博客发布到公共网络环境,实现远程也可以访问和操作。_windows搭建typecho
文章浏览阅读1.1k次。- php是最优秀, 最原生的模板语言, 替代语法,让php更加的优雅的与html生活在一起 -->请放心, 最终生成的,或者说用户最终看到的,仍然是一个html文档, php代码中的内容不会被泄漏的。-- 将php与html代码混编的时候,大括号很容易造成配对错误,最好杜绝它 -->php标签内部代码由php.exe解释, php标签之外的代码原样输出,仍由web服务器解析。-- 所以php的流程控制语句, 都提供了替代语法,用冒号代替大括号 -->php echo '百变鹏仔'?_利用php将静态页面修改为动态页面
文章浏览阅读1.1k次,点赞18次,收藏15次。整理K8s网络相关笔记博文内容涉及 Linux network namespace 认知以及彼此通信Demo,实际中的应用理解不足小伙伴帮忙指正不必太纠结于当下,也不必太忧虑未来,当你经历过一些事情的时候,眼前的风景已经和从前不一样了。——村上春树。_linux network namespace 多端通信 模式认知
文章浏览阅读1.2k次,点赞22次,收藏19次。此网络模型提供了一个逻辑二层(L2)网络,该网络封装在跨 Kubernetes 集群节点的现有三层(L3)网络拓扑上。使用此模型,可以为容器提供一个隔离的 L2 网络,而无需分发路由。封装网络带来了少量的处理开销以及由于覆盖封装生成 IP header 造成的 IP 包大小增加。封装信息由 Kubernetes worker 之间的 UDP 端口分发,交换如何访问 MAC 地址的网络控制平面信息。此类网络模型中常用的封装是 VXLAN、Internet 协议安全性 (IPSec) 和 IP-in-IP。_k8s网络组件对比
文章浏览阅读1.1k次,点赞14次,收藏19次。当我们谈论网络安全时,我们正在讨论的是保护我们的在线空间,这是我们所有人的共享责任。网络安全涉及保护我们的信息,防止被未经授权的人访问、披露、破坏或修改。
文章浏览阅读1.3w次,点赞3次,收藏7次。尽管您可以通过 ping 命令解析出网站的 IP 地址,但是可能在浏览器中访问时仍然遇到问题,这可能是因为浏览器使用的 DNS 解析结果不同于 ping 命令使用的解析结果。可能是因为您的网络或设备上设置了防火墙,阻止了对特定网站的访问。有些国家或组织可能会对特定的域名进行屏蔽,从而阻止访问相关网站。如果您的网络使用代理服务器进行访问控制,可能会由于代理服务器的配置问题导致无法访问某些网站。即使您的网络和设备一切正常,目标网站本身可能也存在问题,例如服务器故障、维护或过载,导致无法访问。_能ping通打不开网页
文章浏览阅读839次,点赞22次,收藏19次。本系统带文档lw万字以上文末可领取本课题的JAVA源码参考。
文章浏览阅读2.1k次,点赞31次,收藏22次。基于微信小程序奶茶点餐外卖系统设计与实现(PHP后台+Mysql)可行性分析毕设源代码毕业设计,数据安全和系统稳定性以及团队能力和资源配备方面都具备较好的条件。因此,该项目的可行性较高。:黄菊华老师《Vue.js入门与商城开发实战》《微信小程序商城开发》图书作者,CSDN博客专家,在线教育专家,CSDN钻石讲师;微信小程序作为一种快捷、方便的移动应用形式,成为很多用户点餐外卖的首选。项目的界面和功能都可以定制,包安装运行!项目配有对应开发文档、开题报告、任务书、PPT、论文模版等。
文章浏览阅读1.8k次,点赞52次,收藏38次。本文主要通过对系统的前台系统和后台管理系统进行了功能性需求分析,对系统的安全性和可扩展性进行了非功能性需求分析。在详细的需求分析的基础上,根据系统的功能设计确定了数据库结构,实现完整的代码编写。Lucky+Baby母婴用品网站使用 Dreamweaver、HBuilder代码编辑器、Apache服务器等开发工具,完成了系统的主要模块的页面设计和功能实现。本文展示了首页页面的实现效果图,并通过代码和页面介绍了用户注册功能、商品搜索功能、生成订单和查看我的订单功能、在线付款功能功能的实现过程。
文章浏览阅读1.5k次,点赞45次,收藏40次。本设计主要实现集人性化、高效率、便捷等优点于一身的人事信息管理系统,完成首页、系统用户、通知公告、部门信息、员工薪资、考勤签到、员工请假、招聘信息、应聘信息等功能模块。
文章浏览阅读1k次。该错误通常出现在数据库读取结果集数据时,比如当我们写好SQL语句从数据库读取数据时,本身应该返回结果集,再给结果集中读取数据。解决思路:这种错误一般是因为echo后面输出了一个数组导致的,或者是数组作为字符串进行拼接运算时导致的。该错误直译为:警告:mysqli_fetch_assoc函数期望参数1是mysqli的结果集,但是给了一个布尔值。这种错误是PHP解析器在解析时遇到了语法错误,直译为:解析错误:语法错误,意料之外的...该错误直译为:提示:未定义的索引:username。_array to string conversion in
文章浏览阅读2.7w次。解决http请求报错context deadline exceeded (Client.Timeout exceeded while awaiting headers)_context deadline exceeded (client.timeout exceeded while awaiting headers)
文章浏览阅读1.3k次,点赞26次,收藏24次。复杂网络是一种由大量相互连接的元素(节点或顶点)组成的网络结构,这些连接通常是非常复杂和动态的。这些网络可以在各种领域中发现,包括社交网络、生物学系统、信息技术和交通系统等。_代理建模