ホームページ  >  記事  >  バックエンド開発  >  php-msfソースコードの詳細な例

php-msfソースコードの詳細な例

小云云
小云云オリジナル
2017-12-27 14:12:361546ブラウズ

この記事では、主に php-msf のソース コードの意味と使用法に関する問題について説明します。必要な場合は、参考にして勉強してください。皆さんのお役に立てれば幸いです。

私はしばらくソースコード解釈を行ってきましたが、私の経験を要約したいと思います:

ライフサイクルを把握し、頭の中でコードを実行してみる

アーキテクチャを分析し、キーワードの階層境界分離を行う

良い ライフサイクルとアーキテクチャを明確にするフレームワークは、基本的に慣れ親しんだ状態に達しており、その後、詳細を入力してコーディングに習熟しています

さらに重要な洞察をいくつか示します:

このツールが何が得意かを理解します。この情報は、通常、ツールのドキュメントに目立つようにマークされているので、プロジェクトをポイントごとに確認してみてください。上記のアーキテクチャとは主に区別されるエンジニアリングの観点。エンジニアリングには、上記の機能/機能に加えて、セキュリティ/テスト/コーディング標準/言語機能などの側面も含まれます。ビジネスコードを書くときに考えることや練習が少なくて済む部分

ツール 用途としては、私が今使っている組み合わせをお勧めします: phpstorm + Baidu マインドマップ + Markdown ノート + ブログの原点と php-msf ブログを書きます。技術的な生活に関連して、みんなに直接話しましょう

ライフサイクルとアーキテクチャ

同僚に、時間があるときに同様の図を作成することをお勧めします。考えるのに非常に役立ちます

この図に基づいてライフサイクルとアーキテクチャを考えてください。ここでは詳細には触れません。msf のいくつかの技術的なポイントを分析します。

コルーチン関連の知識

。 msf

Coroutines

の技術的なポイントの抜粋です。私なりの方法で説明します。詳しく知りたい場合は、後で推奨するリソースを参照してください。

クラスとオブジェクトは非常に重要な概念です。私たちの物事の抽象化は、将来、誰もが意識的にこの側面を養えるようになることを願っています。オブジェクトはインスタンス化されたクラスであり、実際に機能します。これから説明するコルーチンは、実際に機能する役割です。

コルーチンはどこから来て、どこへ行くのですか?

これらの単純な疑問について考えてみてください。コルーチンをより深く理解するには、次のキーワードを覚えておいてください。コルーチンは連携して動作するため、スケジュールが必要です。どのようにスケジュールされますか?

破棄されるときは破棄されますか?


ここで、コルーチンの使用方法を比較してみましょう。コルーチンの実装を比較しないでください。多くの場合、実際の需要は次のようなものです:

実装方法は気にせず、最も有用なものを選択します

// 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('/');

は、おおよそ次のような方程式です。 : using coroutine = plus yield なので、どこに yield を追加する必要があるかを理解するだけです - ファイル IO、ネットワーク IO (redis/mysql/http) など、IO がブロックされる場所があります

もちろん、まだ注意が必要な点

コルーチンのスケジューリング順序に注意しないと、同期呼び出しに堕する可能性があります。

呼び出しチェーン: yield を使用する呼び出しチェーンに Yield を追加する必要があります。例: 次のように:

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


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();

swoole2.0 のコルーチン ソリューションを使用すると、利点は明らかです:

yield を追加する必要はありません

同時呼び出しを行うときに、yield の順序に注意を払う必要はありません。単に defer() を使用してパケットの収集を遅らせます

ただし、このようにコルーチンを直接使用する方法はありません = yield を追加するのは上記の単純な式です。この例では、swoole コルーチン サーバー + swoole コルーチン クライアントの使用が必要です:

サーバーは、非同期コールバックがトリガーされるとコルーチンを生成します

クライアントは、コルーチンのスケジューリングをトリガーします

非同期コールバックの実行が終了すると、コルーチンを破棄します


これがつながります2 つの問題:

が swoole コルーチン サーバーの非同期コールバックにない場合の対処方法: SwooleCoroutine::create() を使用してコルーチンを明示的に生成します

他のコルーチン クライアントを使用する必要があります 対処方法: これはSwoole3の目標は、コルーチンタスクを使用して偽装することを検討できます

。そうではないと思いますが、皆さん。自分で決定してください:

PHP ジェネレーターに基づいて、yield メソッドを使用し、PHP コルーチン スケジューラーを自分で実装します。上記のコルーチンのスケジューリング シーケンスなど、間違いを犯さずに使用したい場合は、まだ理解する必要があります。この部分の実装

Swoole2.0のネイティブメソッドは、コルーチンの生成・スケジューリング・破棄のタイミングを知るだけで十分に活用できます

。非同期コールバックでのコルーチンの頻繁な作成と破棄はパフォーマンスに大きな損失をもたらしますか? -- いいえ、実際にはプロセス/オブジェクトよりもはるかに小さいメモリ操作です

msf の技術的なポイントからの抜粋

msf には多くの優れた点がありますデザインにおいて、多くのコードは学ぶ価値があります。


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, 大家根据文档自己探索即可。

以上がphp-msfソースコードの詳細な例の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。