hyperf笔记


hyperf环境搭建

使用docker配置环境

docker run --name hyperf \
-v ~/pythonschool/hyperf:/data/project \
-p 9501:9501 -it \
--privileged -u root \
--entrypoint /bin/sh \
hyperf/hyperf:7.4-alpine-v3.11-swoole
cd /data
composer config -g repo.packagist composer https://mirrors.aliyun.com/composer
composer create-project hyperf/hyperf-skeleton project

本地安装php-redis

wget http://pecl.php.net/get/redis-5.3.4.tgz
tar xf redis-5.3.4.tgz
cd redis-5.3.4
phpize
./configure
make
sudo make install
php -m | grep redis

vim /etc/php.ini

extension=redis.so

安装hyperf

用composer自动创建项目和执行脚本

composer config repo.packagist composer https://mirrors.aliyun.com/composer/
composer create-project hyperf/hyperf-skeleton

手动处理

echo {}>composer.json
composer config repo.packagist composer https://mirrors.aliyun.com/composer/
composer require hyperf/hyperf-skeleton
cp -R ./vendor/hyperf/hyperf-skeleton/ .
composer config repo.packagist composer https://mirrors.aliyun.com/composer/
composer update
// 一路回车,默认配置安装
cp .env.example .env
git add .
git commit -am "add hyperf"

如果端口被占用则修改端口,/config/autoload/server.php => port => 9502

// localhost:9502
php bin/hyperf.php start

hyperf调试

因为hyperf使用了swoole,使用常驻内存的方法,会导致代码修改好不能立即生效.

在本地调试的时候,可以更改一些配置.

/config/autoload/server.php => OPTION_MAX_REQUEST => 1
'max_request' => 1, // 设置worker进程的最大任务数

这样hyperf执行完一个任务后,会新建一个worker,代码就会被自动加载.

hyperf生命周期

Self-calling自调用函数闭包

当函数被创建的时候就会自动执行

(function($s) {
    echo $s;
})('Hello Moments');

__invoke

当尝试以调用函数的方式调用一个对象时,__invoke()方法会被自动调用.

class Invoke
{
    public function __invoke()
    {
        echo 'Hello Moments';
    }
}
$invoke = new Invoke();
$invoke(); // 没有__invoke方法则会报错

AOP切面编程

通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术.

php bin/hyperf.php 命令来生成所有代理类
然后再更改config/config.php的SCAN_CACHEABLE为true
可以加快启动速度,减少内存消耗

自定义注释功能,注解

composer require doctrine/annotations
AnnotationRegistry::registerLoader('class_exists');

hyperf执行过程

// 加载composer组件包
require BASE_PATH . '/vendor/autoload.php';
// 切面编程代理类路径
$proxyFileDirPath = BASE_PATH . '/runtime/container/proxy/';
// 配置文件路径
$configDir = BASE_PATH . '/config/';
// 返回所有已注册的 __autoload() 函数
$loaders = spl_autoload_functions();
// 注册注解功能
AnnotationRegistry::registerLoader(function ($class) use ($composerClassLoader) {
    return (bool) $composerClassLoader->findFile($class);
});
// 将composer的类加载改成hyperf
$loader[0] = new static($composerClassLoader, $proxyFileDirPath, $configDir);
// .env环境变量加载处理
$this->loadDotenv();
// 处理配置文件,可以使用class_map替换相应的类
// config/autoload/annotations.php => scan => class_map
$config = ScanConfig::instance($configDir);
$classLoader->addClassMap($config->getClassMap());
// 创建容器
$container = require BASE_PATH . '/config/container.php';
// ApplicationFactory进行注册然后返回Symfony\Component\Console\Application
$application = $container->get(Hyperf\Contract\ApplicationInterface::class);
$application->run();

debug模式下,控制台输出的信息

$container->get(Hyperf\Contract\ApplicationInterface::class);
Hyperf\Di\Container::class->make(Hyperf\Contract\ApplicationInterface::class);
Hyperf\Di\Resolver\FactoryResolver::class->resolve();
Hyperf\Framework\ApplicationFactory::class->__invoke($container);
$eventDispatcher->dispatch(new BootApplication());
Hyperf\Event\EventDispatcher::class->dispatch();
$this->dump($listener, $event);
$this->logger->debug(sprintf('Event %s handled by %s listener.', $eventName, $listenerName));
  • 获取StartServer类的配置
// Hyperf\Di\ClassLoader::class->init()
$loader[0] = new static($composerClassLoader, $proxyFileDirPath, $configDir);
// Hyperf\Di\ClassLoader::class->__construct($composerClassLoader, $proxyFileDirPath, $configDir)
// Hyperf\Di\Annotation\ScanConfig
$config = ScanConfig::instance($configDir);
[$config, $serverDependencies, $cacheable] = static::initConfigByFile($configDir);
$configFromProviders = ProviderConfig::load();
// Hyperf\Config\ProviderConfig
static::$providerConfigs = static::loadProviders($providers);
$providerConfigs[] = (new $provider())();
// Hyperf\Server\ConfigProvider
'commands' => [
    StartServer::class,
],
  • 然后执行指定的命令类
$application = $container->get(Hyperf\Contract\ApplicationInterface::class);
return $this->resolvedEntries[$name] = $this->make($name);
// Hyperf\Di\Resolver\FactoryResolver
resolve()
$object = call($callable, [$this->container]);
// Hyperf\Framework\ApplicationFactory
__invoke()
$application = new Application();
// 到这里时已经把本地命令行添加到Symfony\Component\Console中
$application->add($container->get($command));
// bin/hyperf.php start
$application->run();
// Symfony\Component\Console::class->run();
// Symfony\Component\Console\Input\ArgvInput类对参数做了访问控制(私有),所以看不到
// $_SERVER['argv'][1]取出start命令

php bin/hyperf.php

框架初始化

php bin/hyperf.php start

框架初始化后,执行start命令类StartServer

hyperf路由

基本上都延用了laravel的规范,虽然精简了很多,直接用就好.

Router::post('/post', 'App\Controller\IndexController::post');

自定义路由config/autoload/annotations.php

将\Hyperf\HttpServer\Router\DispatcherFactory::class复制到class_map文件夹下即可.

'class_map' => [
    \Hyperf\HttpServer\Router\DispatcherFactory::class => BASE_PATH . '/class_map/Hyperf/HttpServer/Router/DispatcherFactory.php'
]

使用注解定义路由

关闭config/config.php里的参数SCAN_CACHEABLE = false

新建的类更新一下composer以防找不到,composer dump

php bin/hyperf.php gen:controller AnnotateController
use Hyperf\HttpServer\Annotation\AutoController;
/**
 * @AutoController()
 */
class AnnotateController
/**
 * @Controller()
 */
class AnnotateController

/**
 * @RequestMapping(path="index", methods="get,post")
 */
public function index(RequestInterface $request, ResponseInterface $response)

hyperf注意事项

尽量不要用全局变量,因为要自己手动释放.

使用上下文来进行逻辑处理.

使用注解一定要重启服务!

服务调用jsonrpc-http

基础实现

分成两个主体,服务提供者(TestService:9504),服务消费者(TestServiceConsumer:9502).

通过服务契约来定义和约束接口的调用(TestServiceInterface).

服务提供者:TestServiceInterface,TestService,config/autoload/server.php

服务消费者:TestServiceInterface,TestServiceConsumer(自动生成可省),config/autoload/services.php

路由指定节点信息(nodes),路由负载均衡(registry)

服务提供者

composer require hyperf/json-rpc
composer require hyperf/rpc-server
mkdir app/JsonRpc
// TestServiceInterface.php
// TestService.php
// 添加server:9504
@RpcService(name="TestService",protocol="jsonrpc-http",server="jsonrpc-http")

服务消费者

composer require hyperf/json-rpc
composer require hyperf/rpc-client
services.php->consumers->service(自动生成要指定接口)

服务网关

// http://localhost:9502/rpc

Router::get('/rpc', function(){
    $client = \Hyperf\Utils\ApplicationContext::getContainer()->get(\App\JsonRpc\TestServiceInterface::class);
    $result = $client->sum(11,22);
    return 'rpc' . $result;
});

手动生成服务消费者

// TestServiceConsumer
// dependencies.php注入一下对象

config/autoload/services.php

'consumers' => [
    [
        'name' => 'TestService',
        'nodes' => [
            ['host' => '127.0.0.1', 'port' => 9504]
        ]
    ]
]

路由负载均衡

服务提供者(publishTo)->服务注册(consul)

服务消费者(registry)->负载均衡(consul)

服务调用grpc

需要安装如下软件.

  • php-grpc扩展
  • php-protobuf扩展
  • protoc命令行工具
  • grpc_php_plugin模板生成工具
  • swoole开启openssl,http2

protoc生成规范

根目录下新建模板文件,grpc.proto

vim grpc.proto

syntax = "proto3";
package grpc;
service Hi {
    rpc sayHello (HiUser) returns (HiReply) {}
}
message HiUser {
    string name = 1;
}
message HiReply {
    string message = 1;
}
// 根目录添加目录,并加入命名空间
mkdir grpc
// 只生成接口规范
protoc --php_out=grpc/ grpc.proto
// 生成php类模板
protoc -I=. grpc.proto --proto_path=grpc/ --php_out=grpc/ --grpc_out=grpc/ --plugin=protoc-gen-grpc=../grpc_php_plugin

grpc目录结构

grpc
├── GPBMetadata
│   └── Grpc.php
└── Grpc
    ├── HiClient.php // 由grpc_php_plugin生成
    ├── HiReply.php
    └── HiUser.php

添加命名空间

"autoload": {
    "psr-4": {
        "App\\": "app/",
        "GPBMetadata\\": "grpc/GPBMetadata",
        "Grpc\\": "grpc/Grpc"
    },

修改grpc/Grpc/HiClient.php的基类为Hyperf\GrpcClient\BaseClient;

class HiClient extends \Grpc\BaseStub
==> 
class HiClient extends BaseClient

服务提供者

composer require hyperf/grpc-server

添加服务器配置:9505

[
    'name' => 'grpc',
    'type' => Server::SERVER_HTTP,
    'host' => '0.0.0.0',
    'port' => 9505,
    'sock_type' => SWOOLE_SOCK_TCP,
    'callbacks' => [
        Event::ON_REQUEST => [Hyperf\GrpcServer\Server::class, 'onRequest'],
    ],
]

配置控制器

php bin/hyperf.php gen:controller HiController

app/Controller/HiController.php

use Grpc\HiReply;
use Grpc\HiUser;
class HiController
{
    public function sayHello(HiUser $user)
    {
        $message = new HiReply();
        $message->setMessage(sprintf('Hello %s(%s)', $user->getName(), time()));
        return $message;
    }
}

配置路由,要注意大小写,以protoc生成的接口文档为准.

在HiClient中,如'/grpc.Hi/sayHello'.

Router::addServer('grpc', function () {
    Router::addGroup('/grpc.Hi', function () {
        Router::post('/sayHello', [App\Controller\HiController::class, 'sayHello']);
    });
});

服务消费者

composer require hyperf/grpc-client

直接在路由里测试,

Router::get('/grpc', function () {
    $client = new \Grpc\HiClient('127.0.0.1:9505', ['credentials' => null]);

    $request = new \Grpc\HiUser();
    $request->setName('hyperf');

    /**
     * @var \Grpc\HiReply $reply
     */
    list($reply, $state) = $client->sayHello($request);

    return $reply->getMessage();
});

测试

php bin/hyperf.php s

http://localhost:9502/grpc

服务中心

必须要先了解微服务再去看hyperf框架,毕竟是国内开源的可以参考了解一下,辅助学习,看一下此框架如何整合微服务的组件.

不要光看文档,文档不会面面俱到,先看原理,多搜索,借用框架实际操作一下.

安装服务中心

https://learn.hashicorp.com/

brew tap hashicorp/tap
brew install hashicorp/tap/consul
consul -v

运行服务中心

consul agent -http-port=8500 -dev
http://localhost:8500/

配置服务中心

composer require hyperf/service-governance
composer require hyperf/consul
composer require hyperf/guzzle

config/autoload/consul.php

php bin/hyperf.php vendor:publish hyperf/consul

return [
    'uri' => 'http://127.0.0.1:8500',
];

发布服务到服务中心publishTo="consul"

/**
 * @RpcService(name="TestService",protocol="jsonrpc-http",server="jsonrpc-http",publishTo="consul")
 */
class TestService implements TestServiceInterface

config/autoload/services.php

'registry' => [
    'protocol' => 'consul',
    'address' => 'http://127.0.0.1:8500',
],

测试地址 http://127.0.0.1:9502/consul

consul键值对存储

Router::get('/consul', function(){
    $container = \Hyperf\Utils\ApplicationContext::getContainer();
    $clientFactory = $container->get(\Hyperf\Guzzle\ClientFactory::class);
    $consulServer = 'http://127.0.0.1:8500';
    // consul键值对存储
    $kv = new \Hyperf\Consul\KV(function () use ($clientFactory, $consulServer) {
        return $clientFactory->create([
            'base_uri' => $consulServer,
        ]);
    });
    $kv->put('consul', 'Hello World!');
    $result = $kv->get('consul')->getBody()->read(1024);
    $kv->delete('consul');
    var_dump(base64_decode(json_decode($result)[0]->Value));

    return 'consule ' . $result;
});

代理操作

$agent = new \Hyperf\Consul\Agent(function () use ($clientFactory, $consulServer) {
    return $clientFactory->create([
        'base_uri' => $consulServer,
    ]);
});
var_dump($agent->services()->getBody()->read(1024));
return $agent->services()->getBody();
{
    "TestService-0": {
        "ID": "TestService-0",
        "Service": "TestService",
        "Tags": [],
        "Meta": {
            "Protocol": "jsonrpc-http"
        },
        "Port": 9504,
        "Address": "192.168.1.5",
        "SocketPath": "",
        "TaggedAddresses": {
            "lan_ipv4": {
                "Address": "192.168.1.5",
                "Port": 9504
            },
            "wan_ipv4": {
                "Address": "192.168.1.5",
                "Port": 9504
            }
        },
        "Weights": {
            "Passing": 1,
            "Warning": 1
        },
        "EnableTagOverride": false,
        "Datacenter": "dc1"
    }
}