Merge branch 'gh-pages' of https://github.com/hyperf-cloud/hyperf into gh-pages

This commit is contained in:
huangzhhui 2019-05-20 00:35:36 +08:00
commit e36fda1f38
8 changed files with 322 additions and 7 deletions

View File

@ -62,6 +62,7 @@
* [开发者工具](zh/devtool.md)
* [辅助类](zh/utils.md)
* [自动化测试](zh/testing.md)
* [限流器](zh/rate-limit.md)
* Awesome Hyperf

View File

@ -18,24 +18,26 @@ config
├── autoload // 此文件夹内的配置文件会被配置组件自己加载,并以文件夹内的文件名作为第一个键值
│   ├── amqp.php // 用于管理 AMQP 组件
│   ├── annotations.php // 用于管理注解
│   ├── apollo.php // 用于管理基于 Apollo 实现的配置中心
│   ├── aspects.php // 用于管理 AOP 切面
│   ├── async_queue.php // 用于管理基于 Redis 实现的简易队列服务
│   ├── cache.php // 用于管理缓存组件
│   ├── commands.php // 用于管理自定义命令
│   ├── config-center.php // 用于管理配置中心
│   ├── consul.php // 用于管理 Consul 客户端
│   ├── databases.php // 用于管理数据库客户端
│   ├── devtool.php // 用于管理开发者工具
│   ├── exceptions.php // 用于管理异常处理器
│   ├── listeners.php // 用于管理事件监听者
│   ├── logger.php // 用于管理日志
│   ├── middlewares.php // 用于管理中间件
│   ├── opentracing.php // 用于管理调用链追踪
│   ├── queue.php // 用于管理基于 Redis 实现的简易队列服务
│   └── redis.php // 用于管理 Redis 客户端
│   ├── processes.php // 用于管理自定义进程
│   ├── redis.php // 用于管理 Redis 客户端
│   └── server.php // 用于管理 Server 服务
├── config.php // 用于管理用户或框架的配置,如配置相对独立亦可放于 autoload 文件夹内
├── container.php // 负责容器的初始化,作为一个配置文件运行并最终返回一个 Psr\Container\ContainerInterface 对象
├── dependencies.php // 用于管理 DI 的依赖关系和类对应关系
├── routes.php // 用于管理路由
└── server.php // 用于管理 Server 服务
└── routes.php // 用于管理路由
```
### `config.php``autoload` 文件夹内的配置文件的关系

View File

@ -1,6 +1,6 @@
# Consul 协程客户端
Hyperf 提供了一个 [Consul](https://www.consul.io/api/index.html) 的协程客户端,由于 Consul 本身的 API 比较简单,也支持 HTTP 的请求方法,该组件仅对 API 进行了一些封装上的简化,基于 [hyperf/guzzle](https://github.com/hyperf-cloud/guzzle) 提供的协程 HTTP 客户端支持。
Hyperf 提供了一个 [Consul](https://www.consul.io/api/index.html) 的协程客户端,由于 Consul 本身的 API 比较简单,也支持 HTTP 的请求方法,该组件仅对 API 进行了一些封装上的简化,基于 [hyperf/guzzle](https://github.com/hyperf-cloud/guzzle) 提供的协程 HTTP 客户端支持。
> `ConsulResponse` 类指的是 `Hyperf\Consul\ConsulResponse`

View File

@ -53,6 +53,9 @@ Swoole 协程也是对异步回调的一种解决方案,在 PHP 语言下Sw
都说协程是一个轻量级的线程,协程和线程都适用于多任务的场景下,从这个角度上来说,协程与线程很相似,都有自己的上下文,可以共享全局变量,但不同之处在于,在同一时间可以有多个线程处于运行状态,但对于 Swoole 协程来说只能有一个,其它的协程都会处于暂停的状态。此外,普通线程是抢占式的,那个线程能得到资源由操作系统决定,而协程是协作式的,执行权由用户态自行分配。
- [swoole 中的协程原理](https://www.jianshu.com/p/745b0b3ffae7)
- [swoole 中的协程用法](https://www.jianshu.com/p/b620836c461a)
## 协程编程注意事项
### 不能存在阻塞代码
@ -74,7 +77,9 @@ Swoole 协程也是对异步回调的一种解决方案,在 PHP 语言下Sw
- 全局周期,我们只需要创建一个静态变量供全局调用即可,静态变量意味着在服务启动后,任意协程和代码逻辑均共享此静态变量内的数据,也就意味着存放的数据不能是特别服务于某一个请求或某一个协程;
- 协程周期,由于 Hyperf 会为每个请求自动创建一个协程来处理,那么一个协程周期在此也可以理解为一个请求周期,在协程内,所有的状态数据均应存放于 `Hyperf\Utils\Context` 类中,通过该类的 `get`、`set` 来读取和存储任意结构的数据,这个 `Context(协程上下文)` 类在执行任意协程时读取或存储的数据都是仅限对应的协程的,同时在协程结束时也会自动销毁相关的上下文数据。
### 协程 server 可开启的协程数限制
`swoole server``set` 方法中增加了一个配置参数 `max_coroutine`,用于配置一个 `Worker` 进程最多同时处理的协程数目。因为随着 `Worker` 进程处理的协程数目的增加,其占用的内存也会增加,为了避免超出 php 的 `memory_limit` 限制,请根据实际业务的压测结果设置该值,默认为 swoole 中设置的默认值为 3000, hyperf 中修改为 100000.
## 使用协程

View File

@ -0,0 +1,28 @@
# devtool 开发者工具
## 安装
```
# 引入
composer require hyperf/devtool
# 查看支持的命令
php bin/hyperf.php
gen
gen:amqp-consumer Create a new amqp consumer class
gen:amqp-producer Create a new amqp producer class
gen:aspect Create a new aspect class
gen:command Create a new command class
gen:controller Create a new controller class
gen:job Create a new job class
gen:listener Create a new listener class
gen:middleware Create a new middleware class
gen:process Create a new process class
vendor
vendor:publish Publish any publishable configs from vendor packages.
```
## 使用
```

View File

@ -63,4 +63,171 @@ class DemoService
$this->logger->info("Your log message.");
}
}
```
```
## 关于 monolog 的基础知识
结合代码来看 monolog 中涉及的基础概念:
```php
use Monolog\Formatter\LineFormatter;
use Monolog\Handler\FirePHPHandler;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
// create log channel
$log = new Logger('log');
// create log handler
$stream = new StreamHandler('test.log', Logger::WARNING);
$fire = new FirePHPHandler();
// custom log format
// the default date format is "Y-m-d H:i:s"
$dateFormat = "Y n j, g:i a";
// the default output format is "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n"
$output = "%datetime%||%channel||%level_name%||%message%||%context%||%extra%\n";
// finally, create a formatter
$formatter = new LineFormatter($output, $dateFormat);
// set log format
$stream->setFormatter($formatter);
// add handler to log channel
$log->pushHandler($stream);
$log->pushHandler($fire);
// clone new log channel
$log2 = $log->withName('log2');
// add records to the log
$log->warning('Foo');
// add extra data to record
// 1. log context
$log->error('a new user', ['username' => 'daydaygo']);
// 2. processor
$log->pushProcessor(function ($record) {
$record['extra']['dummy'] = 'hello';
return $record;
});
$log->pushProcessor(new \Monolog\Processor\MemoryPeakUsageProcessor());
$log->alert('czl');
```
- 首先, 实例化一个 `Logger`, 取个名字, 名字对应的就是 `channel`
- 可以为 `Logger` 绑定多个 `Handler`, `Logger` 打日志, 交由 `Handler` 来处理
- `Handler` 可以指定需要处理那些 **日志级别** 的日志, 比如 `Logger::WARNING`, 只处理日志级别 `>=Logger::WARNING` 的日志
- 谁来格式化日志? `Formatter`, 设置好 Formatter 并绑定到相应的 `Handler`
- 日志包含那些部分: `"%datetime%||%channel||%level_name%||%message%||%context%||%extra%\n"`
- 区分一下日志中添加的额外信息 `context``extra`: `context` 由用户打日志时额外指定, 更加灵活; `extra` 由绑定到 `Logger` 上的 `Processor` 固定添加, 比较适合收集一些 **常见信息**
## hyperf/logger 的高级用法
### 封装 Log 类
```php
namespace App;
use Hyperf\Logger\Logger;
use Hyperf\Utils\ApplicationContext;
/**
* @method static Logger get($name)
* @method static log($level, $message, array $context = array())
* @method static info($message, array $context = array())
* @method static error($message, array $context = array())
*/
class Log
{
public static function __callStatic($name, $arguments)
{
$container = ApplicationContext::getContainer();
$log = $container->get(\Hyperf\Logger\LoggerFactory::class);
if ($name == 'get') {
return $log->get(...$arguments);
}
$log = $log->get('app');
$log->$name(...$arguments);
}
}
```
- `__callStatic()` 使用魔术方法实现 **静态方法调用**
- 默认使用 `app` channel(回忆一下上面提到的 monolog 基础概念) 来打日志,
- 使用 `Log::get()` 就可以切换到不同 channel, 强大的 `container` 解决了这一切
### stdout 日志
默认 `StdoutLoggerInterface` 接口的实现类 `StdoutLogger`, 其实并没有使用 monolog, 如果想要使用 monolog 保持一致呢?
是的, 还是强大的 `container`.
- 首先, 实现一个 `StdoutLoggerFactory`
```php
namespace App;
use Psr\Container\ContainerInterface;
class StdoutLoggerFactory
{
public function __invoke(ContainerInterface $container)
{
return Log::get('sys');
}
}
```
- 申明依赖, 使用 `StdoutLoggerInterface` 的地方, 由实际依赖的 `StdoutLoggerFactory` 实例化的类来完成
```php
// config/dependencies.php
return [
'dependencies' => [
\Hyperf\Contract\StdoutLoggerInterface::class => \App\StdoutLoggerFactory::class,
],
];
```
### 不同环境下输出不同格式的日志
上面这么多的使用, 都还只在 monolog 中的 `Logger` 这里打转, 这里来看看 `Handler``Formatter`
```php
// config/autoload/logger.php
$app_env = env('APP_ENV', 'dev');
if ($app_env == 'dev') {
$formatter = [
'class' => \Monolog\Formatter\LineFormatter::class,
'constructor' => [
'format' => "||%datetime%||%channel%||%level_name%||%message%||%context%||%extra%\n",
'allowInlineLineBreaks' => true,
'includeStacktraces' => true,
],
];
} else {
$formatter = [
'class' => \Monolog\Formatter\JsonFormatter::class,
'constructor' => [],
];
}
return [
'default' => [
'handler' => [
'class' => \Monolog\Handler\StreamHandler::class,
'constructor' => [
'stream' => 'php://stdout',
'level' => \Monolog\Logger::INFO,
],
],
'formatter' => $formatter,
],
]
```
- 默认配置了名为 `default``Handler`, 并包含了此 `Handler` 及其 `Formatter` 的信息
- 获取 `Logger` 时, 如果没有指定 `Handler`, 底层会自动把 `default` 这一 `Handler` 绑定到 `Logger`
- dev(开发)环境: 日志使用 `php://stdout` 输出到 stdout, 并且 `Formatter` 中设置 `allowInlineLineBreaks`, 方便查看多行日志
- 非 dev 环境: 日志使用 `JsonFormatter`, 会被格式为 json, 方便投递到第三方日志服务

View File

@ -0,0 +1,108 @@
# 令牌桶限流器
## 安装
```bash
composer require hyperf/rate-limit
```
## 默认配置
| 配置 | 默认值 | 备注 |
|:--------------:|:------:|:-------------------:|
| create | 1 | 每秒生成令牌数 |
| consume | 1 | 每次请求消耗令牌数 |
| capacity | 2 | 令牌桶最大容量 |
| limitCallback | NULL | 触发限流时回调方法 |
| key | NULL | 生成令牌桶的key |
| waitTimeout | 3 | 排队超时时间 |
```php
<?php
return [
'create' => 1,
'consume' => 1,
'capacity' => 2,
'limitCallback' => null,
'key' => null,
'waitTimeout' => 3,
];
```
## 使用限流器
组件提供 `Hyperf\RateLimit\Annotation\RateLimit` 注解,作用于类、类方法,可以覆盖配置文件。 例如,
```php
<?php
namespace App\Controller;
use Hyperf\HttpServer\Annotation\Controller;
use Hyperf\HttpServer\Annotation\RequestMapping;
use Hyperf\RateLimit\Annotation\RateLimit;
/**
* @Controller(prefix="rate-limit")
*/
class RateLimitController
{
/**
* @RequestMapping(path="test")
* @RateLimit(create=1, capacity=3)
*/
public function test()
{
return ["QPS 1, 峰值3"];
}
/**
* @RequestMapping(path="test2")
* @RateLimit(create=2, consume=2, capacity=4)
*/
public function test2()
{
return ["QPS 2, 峰值2"];
}
}
```
配置优先级`默认配置 < 配置文件 < 类注解 < 方法注解`
## 触发限流
当限流被触发时, 默认会抛出`Hyperf\RateLimit\Exception\RateLimitException` 异常
或者配置`limitCallback`限流回调来处理。例如:
```php
<?php
namespace App\Controller;
use Hyperf\Di\Aop\ProceedingJoinPoint;
use Hyperf\HttpServer\Annotation\Controller;
use Hyperf\HttpServer\Annotation\RequestMapping;
use Hyperf\RateLimit\Annotation\RateLimit;
/**
* @Controller(prefix="rate-limit")
* @RateLimit(limitCallback={RateLimitController, 'limitCallback'})
*/
class RateLimitController
{
/**
* @RequestMapping(path="test")
* @RateLimit(create=1, capacity=3)
*/
public function test()
{
return ["QPS 1, 峰值3"];
}
public function limitCallback(float $seconds, ProceedingJoinPoint $proceedingJoinPoint)
{
}
}
```
回调方法中的`$seconds` 参数是下次生成Token 的间隔, `$proceedingJoinPoint` 则是此次请求要执行的切入点, 可以通过调用`$proceedingJoinPoint->process()`继续执行或者自行处理。
## 限流

View File

@ -0,0 +1,4 @@
- Dapper, 大规模分布式系统的跟踪系统: https://bigbully.github.io/Dapper-translation/
- 分布式跟踪系统, zipkin 的背景和设计: https://blog.csdn.net/manzhizhen/article/details/52811600
- zipkin 官网: https://zipkin.io/