Home  >  Article  >  Backend Development  >  Detailed example of php-msf source code

Detailed example of php-msf source code

小云云
小云云Original
2017-12-27 14:12:361493browse

This article mainly introduces the meaning of php-msf source code and usage-related issues. Friends in need can follow it for reference and study. Hope it helps everyone.

I have been doing source code interpretation for a while. Let me summarize my experience:

Seize the life cycle and let the code run in your mind

Analysis Architecture, keyword layered boundary isolation

A good framework, after clarifying the life cycle and architecture, you have basically reached a familiar state, and then you have to fill in the details and become proficient in coding

Here are a few more important insights:

Figure out what this tool is good at and what it is suitable for. This information is also very easy to obtain. The tool's documentation is usually prominently marked, and you can use these functions/ Features, try to see it from point to point

Looking at this project from an engineering perspective, it is mainly distinguished from the above architecture. In addition to handling the core business, that is, the above functions/features, engineering also involves In terms of security/testing/coding standards/language features, etc., these are also the parts that I usually think less about and practice less when writing business code.

The use of tools, I recommend the combination I use now: phpstorm + Baidu mind map + Markdown notes + the origin of blog and php-msf, etc. I will write blogs related to technical life. Let’s talk to you again and serve directly.

Life cycle & architecture

The official document has produced a very good picture: Processing request flow chart. I recommend colleagues to make similar pictures when they have free time, which is very helpful for thinking.

Based on this picture Thinking about life cycle & architecture, I won’t go into details here. Here is an analysis of some technical points in msf:

Coroutine related knowledge

Excerpts from technical points in msf

Coroutine

I will explain it in my own way. If you need to know more, you can read the resources I recommend later.

Class vs object is a very important group Concept. Class represents our abstraction of things. This abstract ability will be used in the future. I hope everyone can consciously cultivate this awareness, which can at least provide an analogy. Objects are instantiated classes and are the real things that can be done. Living, the coroutine we are going to discuss is such a role that really works.

Where does the coroutine come from, where does it go, and what does it do?

Think Thinking about these simple questions, maybe your understanding of coroutines will be deeper. Remember these keywords:

Generate. There needs to be a place to generate coroutines, and you may not need to know Details, but you need to know when

scheduling occurs. There must be many coroutines working together, so scheduling is needed. How to schedule?

Destroy. Will it be destroyed? When will it be destroyed?

Now, let’s take a look at the comparison of how coroutines are used. Note here that I did not compare the implementation of coroutines, because many times, the actual needs It's like this:

I don't care how to implement it, I choose the best one.

// msf - 单次协程调度
$response = yield $this->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('/');

It's roughly an equation like this: Use Coroutine = add yield, so just figure out where you need to add yield - there are places that block IO, such as file IO, network IO (redis/mysql/http), etc.

Of course, Roughly speaking, there are still things to pay attention to

The coroutine scheduling sequence. If you don't pay attention, it may degenerate into synchronous calls.

Call chain: Use the yield call chain, Yield needs to be added. For example, as follows:

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

Compare the coroutine solution of swoole2.0:

$server = new Swoole\Http\Server("127.0.0.1", 9502, SWOOLE_BASE);
$server->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();

The benefits of using swoole2.0's coroutine solution are obvious:

No need to add yield

No need to pay attention to the order of yield for concurrent calls. Use defer() to delay packet collection

However, there is no way to directly use a simple equation such as coroutine = plus yield. The above example requires the use of swoole coroutine server + swoole coroutine client:

server generates the coroutine when the asynchronous callback is triggered

client triggers the coroutine scheduling

Destroys the coroutine when the asynchronous callback execution ends Program

This leads to 2 problems: What to do if

is not in the asynchronous callback of the swoole coroutine server: Use Swoole\Coroutine::create() to explicitly generate the coroutine

What to do if you need to use other coroutine Client: This is the goal of Swoole3. Swoole2.0 can consider using coroutine tasks to disguise it

It looks like this Is it simpler to use coroutines = plus yield? I don’t think so. I’ll add some opinions and you can decide for yourself:

Use the yield method, based on the PHP generator + implement the PHP coroutine scheduler by yourself, I think To use it without making mistakes, such as the above coroutine scheduling sequence, you still need to figure out the native way of implementing

Swoole2.0. It is actually easier to understand. You only need to know the coroutine. You can make good use of the timing of generation/scheduling/destruction

Swoole2.0 Does frequent creation and destruction of coroutines in asynchronous callbacks consume performance? -- No, it is actually some memory operations. , much smaller than processes/objects

Excerpts from technical points in msf

msf has many outstanding features in design, and many codes are worth learning from.

Request ContextContext

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

fpm worker : http request = 1 : 1
swoole worker : http request = 1 : n

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

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

swoole 本身是实现了隔离的:

$http = new swoole_http_server("127.0.0.1", 9501);
$http->on('request', function ($request, $response) {
  $response->end("<h1>Hello Swoole. #".rand(1000, 9999)."</h1>");
});
$http->start();

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

// 你几乎可以用这种方式, 完成任何需要的逻辑
$this->getContext()->xxxModule->xxxModuleFunction();

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

对象池

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

// getObject() 就可以了
/** @var DemoModel $demoModel */
$demoModel = $this->getObject(DemoModel::class, [1, 2]);

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

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

public function get($class, ...$args)
{
  $poolName = trim($class, &#39;\\&#39;);

  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 来管理对象

private function applyNewPool($poolName)
{
  if (array_key_exists($poolName, $this->map)) {
    throw new Exception(&#39;the name is exists in pool map&#39;);
  }
  $this->map[$poolName] = new \SplStack();

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

连接池 & 代理

连接池 Pools

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

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

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

代理 Proxy

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

主从 master slave

集群 cluster

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

class Redis extends Controller
{
  // Redis连接池读写示例
  public function actionPoolSetGet()
  {
    yield $this->getRedisPool(&#39;p1&#39;)->set(&#39;key1&#39;, &#39;val1&#39;);
    $val = yield $this->getRedisPool(&#39;p1&#39;)->get(&#39;key1&#39;);

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

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

    $val = yield $this->getRedisProxy(&#39;master_slave&#39;)->get(&#39;M66&#39;);
    $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

@PostMapping("/delete")
public Map<String, Object> delete(long id, String lang) {
 Map<String, Object> data = new HashMap<String, Object>();
 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

@PostMapping("/delete")
public ResultBean<Boolean> delete(long id) {
 return new ResultBean<Boolean>(configService.delete(id));
}

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

配置文件管理

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

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

考虑不同环境: dev test production

方便使用

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

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

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

msf 中使用配置:

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

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

写在最后

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

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

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

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

The above is the detailed content of Detailed example of php-msf source code. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn