化繁为简,从零开始的PHP分布式框架设计

2020-01-22 09:34:02 浏览数 (1)

前言

经历了一个又一个项目,也接触了很多的PHP框架,我欣赏CI的简约,又贪婪swoole的效率,我将CI和swoole很草率的结合到了一起。起初呢风平浪静,慢慢的就遇到了不少的瓶颈,毕竟CI的设计理念还是贴合FPM模式,如何更加得心应手的使用swoole,同时追求开发上和运行时的效率呢,最主要的还是要方便扩展,就萌生了SwooleDistributed这个开源框架的想法。

在SwooleDistributed发布之前,开源社区还没有过针对swoole的分布式框架,起初的目的并不是一个完整的应用框架,而是一个简单的分布式通讯框架,后来需求变多了,框架也就慢慢的丰满了。

分布式

分布式这东西并不是有多神秘,但一个框架在基础构思中就包含分布式的思想,那无疑方便对以后的扩展。分布式系统涉及到多太物理机之间的调控,配置起来也是较为麻烦,SwooleDistributed使用了内网发现的技术手段,自动发现集群环境的物理机进行连接,简化了配置,甚至达到了无配置。

SwooleDistributed在前期可以控制成本的使用单机模式进行部署,也可以在后期进行水平扩展,对逻辑代码无需任何的改动。

你所需要的就是多增加物理机,跑上服务器就行啦。

MVCT

解决了底层的分布式通讯问题,接下来就是MVC结构的搭建了,这部分借鉴了CI的设计,使用Loader模块加载对应的Model,通过路由访问对应的Controller。相信使用过CI框架的工程师很容易就上手。 此外引入了Swoole独特的Task,将swoole的Task进行了封装优化,更加易于使用。

代码语言:javascript复制
//TestTask.php
class TestTask extends Task
{
   public function test()
   {
       print_r("test timer taskn");
   }
}
//TestController.php
class TestController extends Controller
{
 public function http_test_task()
   {
       $task = $this->loader->task('TestTask');
       $task->test();
       $result = yield $task->coroutineSend();
       $this->http_output->end($result);
   }
}

通过定时器和Task的结合,开发者可以很方便的制作定时任务,而这一切只需要简单的配置即可。

代码语言:javascript复制
/**
* timerTask定时任务
* (必填)task名称 task_name
* (必填)执行task的方法 method_name
* (选填)执行区间 [start_time,end_time) 格式: Y-m-d H:i:s 没有代表一直执行
* (必填)执行间隔 interval_time 单位: 秒
* (选填)最大执行次数 max_exec,默认不限次数
*/
$config['timerTask'][] = [
   'task_name'=>'TestTask',
   'method_name'=>'test',
   'start_time'=>'Y-m-d 00:00:00',
   'end_time'=>'Y-m-d 23:59:59',
   'interval_time'=>'2',
];

异步连接池

swoole提供了redis和mysql的异步客户端,大大提高的服务端的效率,但问题又来了,如果使用异步客户端,就必须维护一个异步连接池。 SwooleDistributed设计了一个通用的连接池模块,通过这个模块可以快捷简单的创建客户端的连接池,不仅如此redis和mysql的连接池早已封装在核心代码中,开发者只需调用其中的Api无需关注连接池的维护。 swoole的异步redis客户端是基于hredis和我们通常使用的redis扩展不一样,在使用批量方法时回调的值是有所区别,SwooleDistributed屏蔽了这一点不同之处,使用起来和平常的redis扩展一致。 异步的mysql客户端事务通常是比较难写的,SwooleDistributed同样针对mysql事务进行了封装,使用起来也非常的方便。 此外mysql也提供了一个语法构建器,可以简单方便的构建mysql语法。

代码语言:javascript复制
 public function test_coroutine()
   {
       $mySqlCoroutine = $this->mysql_pool->dbQueryBuilder->select('*')->from('account')->where('uid', 10303)->coroutineSend();
       $result = yield $mySqlCoroutine;
       $redisCoroutine = $this->redis_pool->coroutineSend('get', 'test');
       $result = yield $redisCoroutine;
       return $result;
   }

协程

之前我去过很多家公司,交流的过程中发现有些公司使用php做短连接,用golang做长连接,我问他们为何不用swoole呢?回答都是异步回调太难写了。 确实异步回调写起来很不好看,可能会有多层回调的嵌套,复杂点的代码非常的难看,swoole2.0已经使用了协程,但首先是新功能稳定性尚且不知,其次不支持php7,于是我呢就对现有框架进行了一次大的调整,通过yield关键字实现了全异步的协程风格。使用起来非常的简单,和一般的写法没有什么太大的区别,只要遵循一个原则,涉及到异步的地方加上yield关键字就可以。

代码语言:javascript复制
    /**
    * 协程测试
    */
   public function http_testCoroutine()
   {
       $this->testModel = $this->loader->model('TestModel', $this);
       $result = yield $this->testModel->test_coroutine();
       $this->http_output->end($result);
   }

协程之间支持嵌套,也支持throw Exception异常捕获,总之用起来和同步的写法基本一致。 协程之间不存在堵塞,也可以通过控制yield的位置调整send和rev的调度策略。 比如:

代码语言:javascript复制
$mySqlCoroutine = $this->mysql_pool->dbQueryBuilder->select('*')->from('account')->where('uid', 10303)->coroutineSend();
$result = yield $mySqlCoroutine;
$redisCoroutine = $this->redis_pool->coroutineSend('get', 'test');
$result = yield $redisCoroutine;

这段代码调度策略是mysql_send->mysql_rev->redis_send->redis_rev; 我们调整下yield的位置

代码语言:javascript复制
$mySqlCoroutine = $this->mysql_pool->dbQueryBuilder->select('*')->from('account')->where('uid', 10303)->coroutineSend();
$redisCoroutine = $this->redis_pool->coroutineSend('get', 'test');
$result = yield $mySqlCoroutine;
$result = yield $redisCoroutine;

这段代码调度策略变成了mysql_send->redis_send->mysql_rev->redis_rev;

同时支持TCP和HTTP

这样你就可以开启一个服务同时处理TCP和HTTP请求了,代码复用率就高很多了,同时SwooleDistributed还提供了一些策略,方便隔离http和tcp的路由请求。

Protobuf

提供了一个Protobuf的RPC实例,其实你可以通过框架的Pack和Route模块自由扩展,提供这个例子无非是我自己项目的需要顺便就发出来了,当然你可以自己实现。

其他

很多细小的功能拉,去这看看吧https://www.gitbook.com/book/tmtbe/swooledistributed。

0 人点赞