Heim  >  Artikel  >  Backend-Entwicklung  >  Detailliertes Beispiel für PHP-MSF-Quellcode

Detailliertes Beispiel für PHP-MSF-Quellcode

小云云
小云云Original
2017-12-27 14:12:361546Durchsuche

Dieser Artikel stellt hauptsächlich die Bedeutung des PHP-MSF-Quellcodes und nutzungsbezogene Probleme vor. Freunde in Not können ihm als Referenz und Studium folgen. Ich hoffe, es hilft allen.

Ich beschäftige mich schon seit einiger Zeit mit der Interpretation von Quellcode:

Erfassen Sie den Lebenszyklus und lassen Sie den Code in Ihrem Kopf ablaufen

Analysearchitektur, Stichwort Layered Boundary Isolation

Ein gutes Framework. Nachdem Sie den Lebenszyklus und die Architektur geklärt haben, befinden Sie sich im Grunde in einem vertrauten Zustand und müssen dann die Details ausfüllen und sich mit der Codierung auskennen

Hier noch ein paar wichtige Erkenntnisse:

Verstehen Sie, was dieses Tool kann und wofür es geeignet ist. Diese Informationen sind auch sehr einfach zu erhalten, und die Dokumentation des Tools ist normalerweise gut sichtbar Markiert. Sie können diese Funktionen/Features nutzen. Versuchen Sie, die Punkte zu erfüllen.

Betrachten Sie dieses Projekt aus einer technischen Perspektive, die sich hauptsächlich von der oben genannten Architektur unterscheidet. Das heißt, die oben genannten Funktionen/Features umfassen auch das Engineering. In Bezug auf Sicherheit/Tests/Codierungsstandards/Sprachfunktionen usw. sind dies auch die Teile, über die ich normalerweise weniger nachdenke und die ich weniger übe, wenn ich Geschäftscode schreibe

Für die Verwendung von Tools empfehle ich die Kombination, die ich jetzt verwende: PHPstorm + Baidu Mind Map + Markdown-Notizen + Blog-Ursprung und PHP-MSF usw., um Blogs im Zusammenhang mit dem technischen Leben zu schreiben noch einmal und direkt servieren.

Lebenszyklus & Architektur

Das offizielle Dokument hat ein sehr gutes Bild ergeben: Verarbeitungsanfrage-Flussdiagramm, wenn Sie haben Freizeit, was zum Nachdenken sehr hilfreich ist.

Basierend auf diesem Bild. Wenn ich über Lebenszyklus und Architektur nachdenke, werde ich hier nicht auf Details eingehen. Hier ist eine Analyse einiger technischer Punkte in msf:

Wissen im Zusammenhang mit Coroutinen

Auszüge aus technischen Punkten in msf

Coroutinen

Ich werde es auf meine eigene Weise erklären Wenn Sie mehr darüber wissen möchten, können Sie sich die von mir empfohlenen Ressourcen später ansehen.

Klasse vs. Objekt ist ein sehr wichtiges Gruppenkonzept. Diese abstrakte Fähigkeit wird in Zukunft verwendet Ich hoffe, dass jeder dieses Bewusstsein bewusst kultivieren kann, was zumindest eine Analogie liefern kann. Objekte sind die realen Dinge, die getan werden können. Die Coroutine, die wir besprechen werden, ist eine solche Rolle, die wirklich funktioniert.

Woher kommt die Coroutine, wohin geht sie und was macht sie?

Denken Sie nach. Wenn Sie über diese einfachen Fragen nachdenken, wird Ihr Verständnis von Coroutinen vielleicht tiefer gehen Diese Schlüsselwörter:

Es muss einen Ort zum Generieren von Coroutinen geben, und Sie müssen möglicherweise keine Details kennen, aber Sie müssen wissen, wann

die Planung erfolgt Da viele Coroutinen zusammenarbeiten, ist eine Planung erforderlich.

Wann wird es zerstört? Schauen Sie sich den Vergleich der Verwendung von Coroutinen an. Beachten Sie, dass ich die Implementierung von Coroutinen nicht verglichen habe, da die tatsächlichen Anforderungen oft so sind:

Es ist mir egal, wie man es implementiert. Ich wähle das Beste aus.

// 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('/');
Es ist ungefähr eine Gleichung wie diese: Coroutinen verwenden = Ertrag hinzufügen, also finden Sie einfach heraus, wo Sie Ertrag hinzufügen müssen - Es gibt Orte, die E/A blockieren, wie Datei-E/A, Netzwerk-E/A (Redis/MySQL/http) usw.

Natürlich gibt es generell Dinge, auf die man achten muss

Koroutinen-Planungsreihenfolge kann zu synchronen Aufrufen führen.

Aufrufkette: Yield-Aufrufkette verwenden Oben müssen Sie yield hinzufügen.

function a_test() {
  return yield $this->getRedisPool('tw')->get('apiCacheForABCoroutine');
}
$res = yield a_test(); // 如果不加 yield, 就变成了同步执行
Vergleichen Sie die Coroutine-Lösung von 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();
Bei Verwendung der swoole2.0-Coroutine-Lösung liegen die Vorteile auf der Hand:

Keine Notwendigkeit, Yield hinzuzufügen

Kein Grund zur Sorge über gleichzeitige Aufrufe. Achten Sie auf die Reihenfolge der Yield-Verzögerung () um die Paketsammlung zu verzögern

Es gibt jedoch keine Möglichkeit, eine einfache Gleichung wie coroutine = plus yield direkt zu verwenden. Das obige Beispiel muss koordiniert werden. Verwenden Sie swoole coroutine server + swoole coroutine client:

Server generiert Coroutine, wenn ein asynchroner Rückruf ausgelöst wird

Client löst Coroutinenplanung aus

asynchron Die Coroutine

wird zerstört, wenn Die Rückrufausführung endet. Dies führt zu zwei Problemen: Was soll ich tun, wenn

nicht im asynchronen Rückruf des Swoole-Coroutine-Servers enthalten ist? Verwenden Sie SwooleCoroutine::create(), um anzuzeigen, was bei Bedarf zu tun ist Verwenden Sie andere Coroutine-Clients, um Coroutinen zu generieren

: Dies ist das Ziel von Swoole3. Swoole2.0 kann erwägen, Coroutine-Aufgaben zu verwenden, um

so zu verschleiern Ist es einfacher, Coroutine = plus Yield zu verwenden? Ich werde einige Meinungen hinzufügen, die jeder berücksichtigen sollte:

Verwenden Sie die Yield-Methode, basierend auf dem PHP-Generator + implementieren Sie PHP-Coroutine selbst, wenn Sie sie verwenden möchten B. die obige Coroutine-Planungssequenz, müssen Sie noch die Implementierung dieses Teils herausfinden

Der native Weg von Swoole2.0 ist tatsächlich einfacher zu verstehen, nur Sie brauchen Den Zeitpunkt der Generierung/Planung/Zerstörung von Coroutinen kennen, um

Swoole2.0 zu nutzen. Wird die häufige Erstellung und Zerstörung von Coroutinen in asynchronen Rückrufen die Leistung beeinträchtigen? einige Speicheroperationen, viel kleiner als Prozesse/Objekte

Auszüge aus technischen Punkten in MSF

MSF hat viele herausragende Punkte im Design und viele Codes sind es wert, daraus zu lernen.

Kontext anfordernContext

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

Das obige ist der detaillierte Inhalt vonDetailliertes Beispiel für PHP-MSF-Quellcode. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn