源码解读也做了一段时间了, 总结一下自己的心得:
抓住 生命周期, 让代码在你脑海中 跑起来
分析架构, 关键字 分层 边界 隔离
一个好的框架, 弄清楚 生命周期 和 架构, 基本就已经到了 熟悉 的状态了, 之后是填充细节和编码熟练了
这里再介绍几个次重要的心得:
弄明白这个工具擅长干什么, 适合干什么. 这个信息也非常容易获取到, 工具的文档通常都会显眼标注出来, 可以通过这些 功能/特性, 尝试以点见面
从工程化的角度去看这个项目, 主要和上面的 架构 区分, 在处理核心业务, 也就是上面的 功能/特性 外, 工程化还涉及到 安全/测试/编码规范/语言特性 等方面, 这些也是平时在写业务代码时思考较少并且实践较少的部分
工具的使用, 推荐我现在使用的组合: phpstorm + 百度脑图 + Markdown笔记 + blog和 php-msf 的渊源等写技术生活相关的 blog 再来和大家八, 直接上菜.
生命周期 & 架构
官方文档制作了一张非常好的图: 处理请求流程图. 推荐各位同仁, 有闲暇时制作类似的图, 对思维很有的帮助.
根据这张图来思考 生命周期 & 架构, 这里就不赘述了, 这里分析一下 msf 中一些技术点:
协程相关知识
msf 中技术点摘录
协程
我会用我的方式来讲解, 如果需要深入了解的, 可以看我后面推荐的资源.
类 vs 对象 是一组很重要的概念. 类代表我们对事物的抽象, 这个抽象的能力在我们以后会一直用到, 希望大家有意识的培养这方面的意识, 至少可以起到触类旁通的作用. 对象是 实例化 的类, 是 真正干活的, 我们要讨论的 协程, 就是这样一个 真正干活的 角色.
协程从哪里来, 到哪里去, 它是干什么的?
想一想这几个简单的问题, 也许你对协程的理解就更深刻了, 记住这几个关键词:
产生. 需要有地方来产生协程, 你可能不需要知道细节, 但是需要知道什么时候发生了
调度. 肯定是有很多协程一起工作的, 所以需要调度, 怎么调度的呢?
销毁. 是否会销毁? 什么时候销毁?
现在, 我们再来看看协程的使用方式对比, 这里注意一下, 我没有用 协程的实现方式对比, 因为很多时候, 需求实际是这样的:
怎么实现我不管, 我选最好用的.
1 2 3 4 5 6 7 8 9 |
|
大致 是这样的一个等式: 使用协程 = 加上 yield, 所以搞清楚哪些地方需要加上 yield 就好了 -- 有阻塞IO的地方, 比如 文件IO, 网络IO(redis/mysql/http) 等.
当然, 大致 就是还有需要注意的地方
协程调度顺序, 如果不注意, 就可能会退化成同步调用.
调用链: 使用 yield 的调用链上, 都需要加上 yield. 比如下面这样:
1 2 3 4 |
|
对比一下 swoole2.0 的协程方案:
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 |
|
使用 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 进程, 会异步处理多个请求, 简单理解就是下面的等式:
1 2 |
|
所以, 我们就需要一种新的方式, 来进行 request 间的隔离.
在编程语言里, 有一个专业词汇 scope(作用域). 通常会使用 scope/生命周期, 所以我一直强调的生命周期的概念, 真的很重要.
swoole 本身是实现了隔离的:
1 2 3 4 5 |
|
msf 在 Context 上还做了一层封装, 让 Context 看起来 为所欲为:
1 2 |
|
细节可以查看 src/Helpers/Context.php 文件
对象池
对象池这个概念, 大家可能比较陌生, 目的是减少对象的频繁创建与销毁, 以此来提升性能, msf 做了很好的封装, 使用很简单:
1 2 3 |
|
对象池的具体代码在 src/Base/Pool.php 下:
底层使用反射来实现对象的动态创建
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 |
|
使用 SplStack 来管理对象
1 2 3 4 5 6 7 8 9 10 11 12 |
|
连接池 & 代理
连接池 Pools
连接池的概念就不赘述了, 我们来直接看 msf 中的实现, 代码在 src/Pools/AsynPool.php 下:
1 2 3 4 5 6 7 |
|
这里使用的 SplQueue 来管理连接和需要执行的命令. 可以和上面对比一下, 想一想为什么一个使用 SplStack, 一个使用 SplQueue.
代理 Proxy
代理是在连接池的基础上进一步的封装, msf 提供了 2 种封装方式:
主从 master slave
集群 cluster
查看示例 App\Controllers\Redis 中的代码:
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 |
|
代理就是在连接池的基础上进一步 搞事情. 以 主从 模式为例:
主从策略: 读主库, 写从库
代理做的事情:
判断是读操作还是写操作, 选择相应的库去执行
公共库
msf 推行 公共库 的做法, 希望不同功能组件可以做到 可插拔, 这一点可以看 laravel 框架和 symfony 框架, 都由框架核心加一个个的 package 组成. 这种思想我是非常推荐的, 但是仔细看 百度脑图 - php-msf 源码解读 这张图的话, 就会发现类与类之间的依赖关系, 分层/边界 做得并不好. 如果看过我之前的 blog - laravel源码解读 / blog - yii源码解读, 进行对比就会感受很明显.
但是, 这并不意味着 代码不好, 至少功能正常的代码, 几乎都能算是好代码. 从功能之外建立的 优越感, 更多的是对 美好生活的向往 -- 还可以更好一点.
AOP
php AOP 扩展: PECL :: Package :: AOP
PHP-AOP扩展介绍 | rango: 韩天峰(Rango)的博客 » PHP-AOP扩展介绍
AOP, 面向切面编程, 韩老大 的 blog - PHP-AOP扩展介绍 | rango 可以看看.
需不需要了解一个新事物, 先看看这个事物有什么作用:
AOP, 将业务代码和业务无关的代码进行分离, 场景有 日志记录 / 性能统计 / 安全控制 / 事务处理 / 异常处理 / 缓存 等等.
这里引用一段 程序员DD - 翟永超的公众号 文章里的代码, 让大家感受下:
同样是 CRUD, 不使用 AOP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
使用 AOP
1 2 3 4 |
|
代码只用一行, 需要的特性一个没少, 你是不是也想写这样的 CRUD 代码?
配置文件管理
先明确一下配置管理的痛点:
是否支撑热更新, 常驻内存需要考虑
考虑不同环境: dev test production
方便使用
热更其实可以算是常驻内存服务器的整体需求, 目前 php 常用的解决方案是 inotify, 可以参考我之前的 blog - swoft 源码解读 .
msf 使用第三方库来解析处理配置文件, 这里着重提一个 array_merge() 的细节:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
msf 中使用配置:
1 2 3 |
|
看起来 laravel 中要简单一些, 其实是通过 composer autoload 来加载函数, 这个函数对实际的操作包装了一层. 至于要不要这样做, 就看自己需求了.
写在最后
msf 最复杂的部分在 服务启动阶段, 继承也很长:
Child -> Server -> HttpServer -> MSFServer -> AppServer, 有兴趣可以挑战一下.
另外一个比较难的点, 是 MongoDbTask 实现原理.
msf 还封装了很多有用的功能, RPC / 消息队列 / restful, 大家根据文档自己探索即可.