BeWithYou

胡搞的技术博客

  1. 首页
  2. web前端/Javascript
  3. zan框架阅读笔记

zan框架阅读笔记


最近在看有赞的zan框架,并没打算实际应用,只是想学习一下他的设计思路。

网上文档写的很不详细,连sql在框架里如何使用都没有说。好在框架代码条理还挺清晰,我们只需要初始化一个demo项目,再用ide一步一步跟进去读源码即可。

这里只关注HTTP服务的部分,简单看一下框架运行的主要流程。细枝末节部分就不去深究啦。

HTTP程序入口

bin/httpd.php

创建Application对象并且生成http_server调用start()方法

Application

整个应用的对象,包含一个static instance,一个container容器。

构造方法:设置appName,设置basePath,bootstrap(),把当前对象设置成静态的instance。

bootstrap()里要做的:新建Container对象,用容器把启动项列表里的内容都make一遍。

启动列表里的东西:

  1. RunMode 检测运行环境 从php.ini里读取相关变量 确定运行环境是线上还是开发等
  2. Debug 设置debug相关的
  3. path 设置路径相关
  4. env 设置memory_limit
  5. configuration 设置config相关 会把share目录和具体runMode目录的都合并进来
  6. sharedObject 设置Di相关 用Di类把容器包起来
  7. registerClassAliases 给核心类注册别名
  8. loadFile require指定的文件进来

Container

依赖注入用的容器类。

mockInstance 存储不共享的对象。

instance存储可以共享的对象。

制造对象的方法有make,singleton,这里依赖注入跟Laravel不一样。只是简单的单层的构造对象,把params直接传给ReflectionClass,不需要递归构建。可能框架设计上也不需要复杂的依赖注入控制反转。

如果shared传false的话,每次都会新建对象,而不会存储到容器内部。

Factory

Application里通过容器make了FactoryFactory提供了创建httpServertcpServer的方法。

Factory创建httpServer httpServer里包装了swoole_http_server

Server(http)

封装了swoole_http_server,设置config,注册了onStart onShutdown onWorkerStart onWorkerStop onWorkerError onRequest方法。

重点都在onRequest回调里。处理每次http请求。

ServerStartWorkerStart里分别启动了各自的列表启动项。

bootServerStartItem里的启动项都use了单例的trait,第一次启动设置config包括worker进程都可以用,包括:

  1. Router 设置路由相关
  2. UrlRule 把path.routing里指定的路径里的文件返回的变量引进来
  3. UrlConfig 设置本地Url,如果config里没有相关配置 则忽略
  4. Middleware 设置中间件相关
  5. ExceptionHandlerChain 为RequestExceptionHandlerChain注册一大坨异常处理的handler
  6. Cache 设置cachemap
  7. SqlMap 设置sqlmap
  8. init/ServerStart/config.php 里设置的自定义启动项

bootWorkerStartItem里的启动项,包括:

  1. ConnectionPool 创建连接池
  2. init/WorkerStart/config.php里设置的自定义启动项

连接池

这里只看MySQL和Redis,其实原理都一样。

MySQL连接池

ConnectionManager里根据config文件为Pool添加若干个对应种类的连接对象,其中包括mysqli的。

实际上是创建了数个\Zan\Framework\Network\Connection\Driver\Mysqli对象,里面包装了mysqli。并且针对每个对象,开启定时heartbeat。

连接池里分为freeConnectionactiveConnection,用的时候使用$connection = (yield ConnectionManager::getInstance()->get('mysql.default')); 可以得到一个可用的连接。其实内部是从free里取了一个,放到了active里。用完之后再release,recyle进free里。

但是新版的swoole弃用了swoole_mysql_query方法,所以只能使用1.85以前的扩展。

实际查询时,需要使用协程的方式来查询操作。如下:

$connection = (yield ConnectionManager::getInstance()->get('mysql.default'));
$engine = new \Zan\Framework\Store\Database\Mysql\Mysqli($connection);

$engine = (yield $engine->query('select * from links'));
$result = (yield $engine->fetchRows());
foreach($result as $row){
  var_dump($row);
}
//其实不需要主动release 我们fetchRows的时候就自动release了
//$connection->release();
Redis连接池

同上,内部通过swoole_client实现了一个自己的redis客户端类,使用RedisManager类来具体操作。使用方式如下:

$connection = (yield ConnectionManager::getInstance()->get('redis.default'));
$manager = new \Zan\Framework\Store\NoSQL\Redis\RedisManager($connection);
$result = (yield $manager->get('test'));
var_dump($result);
连接不够用了怎么办?

当我们用ConnectionManager类从连接池里获取一个连接时,如果不够用了,会yield住,返回一个FutureConnection对象,直到有可用连接再继续。

可以这么模拟,将redis配置设为连接池只有1个可用连接,并且注释掉RedisManagerget方法内的release函数。之后每当我们从连接池获取一个redis可用连接后,用Timer::after()方法延迟2秒钟释放连接。可以观测到,后来的获取连接方法会一直等待,直到旧连接释放。

Timer::after(2000, function() use($connection){
    $connection->release();
});
FutureConnection

实际上是注册了一个Event,告诉程序当有本类型的连接释放时,触发getConnection方法返回一个可用连接。当旧连接release时,会自动触发mysql_free之类的事件,从而让上面的协程继续往下走。

Request的处理

Server里提到,swoole_http_server收到的请求会由一个RequestHandler来处理。那么处理流程大概是什么样的呢?

RequestHandler里初始化了请求相关的内容放入Context中,并且初始化了中间件相关的内容。真正的请求处理交给RequestTask来做,而这一过程是以协程的方式来运行的。最终交给一个Task来处理。所以这个Task为我们这次请求所有涉及到的所有协程提供了一个独立堆栈。我们在具体的某个Controller或者Service 里写业务的时候,都要以协程的方式来思考问题。

小结

粗略的看框架运行的主要流程,与我们之前接触的PHP框架都不一样。zan framework是以一个daemon进程的方式常驻内存运行的,而不是其他框架那样依赖FPM。框架依赖swoole_server来处理http请求和tcp请求。每次请求的过程实际上是独立堆栈的协程,这样就提高了每个worker process的效率,让每次onRequest看起来是异步处理的。 目前只看了框架里的主要流程,还有很多细节部分比如事件链、异常处理、中间件、SqlMap等没有细看,因为不在主要流程里。有空的话还会具体学习一下。

回到顶部