tech|再探grpc

11211 ワード

date:2019-04-25 22:16:01 title:tech|再探grpc
grpcを振り回すのは何度もあって、すべて大規模な使うことがなくて、熟知の程度は多く公式サイトのhelloworldの上で滞在して、原理に対する理解は深くなくて、だからいつも引っかかります.
grpc|python実戦grpc
ここで私が引っかかった点を紹介します.公式サイトのquick startドキュメントに従います.
  • phpを使用する:PHPを構成する環境が面倒で、特にgrpc/grpcコードライブラリがgrpc_php_pluginをコンパイルするステップ
  • .
  • go:golangパッケージをインストールし、壁にぶつかることが多い(gogetに失敗)
  • 最後にpythonをサボって走ったが、最大の収穫はgrpcが一方向要求のほかに双方向通信(stream、ストリーミング通信)があり、環境の問題を迂回してdemo
  • に通じたことだ.
    PHPerからの魂は「環境を解決するか、grpcを使えないか」と尋ねた.
    この問題に陥って、ずっと迂回できなかった.しかしgrpcの基本原理を理解し、考え方を変えると、非常に簡単であることがわかる.
    公式文書の解読
    grpc - quickstart - php: https://grpc.io/docs/quickstart/php/

    公式php quickstartで説明する手順:
  • grpc環境
  • ext-grpc
  • github.com/grpc/grpcソースライブラリからgrpc_php_pluginがコンパイルされ、protocと連携するコード
  • が自動的に生成される.
  • protobuf環境
  • protoファイルIDLファイルに基づいてサービスを定義し、現在proto 3文法(文法は簡単で、15分以内に見ることができる)
  • を使用している.
  • protoc,protobuf compile,protoファイルコンパイラは、protoファイルが異なる開発言語に基づいて翻訳されていることを理解することができる
  • protobuf runtime、protobufフォーマットの実行時サポート、protobufシーケンス化後の情報、protobuf runtime
  • が必要

    誤読されやすい点は2つあります.
  • 順序:protobuf環境を理解してからgrpc
  • を構築する
  • 公式サイトが自動的に生成したコードは、grpcサービス呼び出しに通じるだけだ.しかし、現実的には、rpcサービスは、マイクロサービス
  • など、一連のサービスフレームワークをサポートする必要があります.
    理解grpc
    いくつかの基礎的な点からgrpc.を少しずつ見てみましょう.
  • protobuf:シーケンス化、符号化の基礎知識
  • rpc,tcpベースの通信:tcp通信はなぜプロトコルが必要なのか、プロトコル設計は簡単
  • grpcの通信プロトコル詳細
  • protobuf
    protobuf環境:
  • protoファイルIDLファイルに基づいてサービスを定義し、現在proto 3文法(文法は簡単で、15分以内に見ることができる)
  • を使用している.
  • protoc,protobuf compile,protoファイルコンパイラは、protoファイルが異なる開発言語に基づいて翻訳されていることを理解することができる
  • protobuf runtime、protobufフォーマットの実行時サポート、protobufシーケンス化後の情報、protobuf runtime
  • が必要
    時系列的に理解する:
  • protoファイル->protcコンパイル->異なる言語を自動的に生成するコード
  • gen code+protobuf runtime->情報シーケンス化/逆シーケンス化
  • 補足すると、情報のシーケンス化/逆シーケンス化は、符号化の知識に関連し、進数変換->文字セット(なぜ文字化されないのか)->大端シーケンス/小端シーケンス/ネットワークシーケンス(php pack()/unpack()関数)を含む.
    具体的にPHPでは、公式サイトのhelloworldを例に挙げます.
  • protoファイル
  • syntax = "proto3";
    
    package grpc;
    
    service HelloService {
        rpc SayHello (HelloRequest) returns (HelloResponse);
    }
    
    message HelloRequest {
        string greeting = 1;
    }
    
    message HelloResponse {
        string reply = 1;
    }
    
  • protoc
  • # alpine linux   ,    linux    ,            
    apk add protobuf
    protoc --version #    protoc       
    
    #    protoc     
    protoc --php_out=grpc/ game.proto #    --php_out   ,      PHP      
    
  • protobuf runtime

  • PHPの中で実はとても簡単なext-protobuf / google/protobuf package、2は1を選びます
    // ext-protobuf
    pecl install protobuf
    
    // google/protobuf
    composer require google/protobuf
    

    ここまで、protobufという部分の内容をすべて解決しました.以下は生成の例です.
    // proto
    message HelloRequest {
        string greeting = 1;
    }
    
    grpc.HelloRequest
     */
    class HelloRequest extends \Google\Protobuf\Internal\Message
    {
        /**
         * Generated from protobuf field string greeting = 1;
         */
        private $greeting = '';
    
        public function __construct() {
            \GPBMetadata\Hello::initOnce();
            parent::__construct();
        }
    
        /**
         * Generated from protobuf field string greeting = 1;
         * @return string
         */
        public function getGreeting()
        {
            return $this->greeting;
        }
    
        /**
         * Generated from protobuf field string greeting = 1;
         * @param string $var
         * @return $this
         */
        public function setGreeting($var)
        {
            GPBUtil::checkString($var, True);
            $this->greeting = $var;
    
            return $this;
        }
    
    }
    

    rpc, tcp 基础上的通信

    tcp/ip 4 层网络通信:

    • 物理层/数据链路层: 网线/路由器/交换机/网卡 -> mac地址
    • ip 层: ip 地址, 4 种网络地址类型
    • tcp/udp层: 端口, 端口上绑定的服务
    • 协议层: 各种熟悉的协议, http/ftp

    为什么需要协议: tcp 是流式(stream)传输数据的, 需要协议来确定数据边界
    简单协议设计: EOF结束符 / 固定包头

    swoole wiki - 网络通信协议设计: https://wiki.swoole.com/wiki/page/484.html

    有了 swoole, tcp 通信, 编程十分简单:

    • server.php: tcp 协程 server
    =v4.0         
    $s = new Server('0.0.0.0', '9502', SWOOLE_BASE, SWOOLE_TCP);
    $s->set([
        'worker_num' => 4,
        'daemonize' => true,
        'backlog' => 128,
    ]);
    $s->on('connect', 'on_connect');
    $s->on('receive', 'on_receive');
    $s->on('close', 'on_close');
    $s->start();
    
  • client.php:tcp協程client
  • connect('127.0.0.1', '9502');
    $c->send('hello');
    echo $c->recv();
    $c->close();
    
  • プロトコル処理に加えて、簡単なプロトコルは構成を変更するだけで
  • を実現することができる.
    set([
        'open_length_check'     => 1,
        'package_length_type'   => 'N',
        'package_length_offset' => 0,       // N         
        'package_body_offset'   => 4,       //           
        'package_max_length'    => 2000000,  //      
    ]);
    $c->connect('127.0.0.1', '9502');
    $c->send('hello');
    echo $c->recv();
    $c->close();
    

    grpc = http2 + protobuf
    grpcはhttp 2プロトコルに基づいて通信を行い、上記の基礎知識を理解し、grpcが使用するhttp 2プロトコルの通信の詳細を見ると、完全に簡単に実現することができる.
    set([
        'open_http2_protocol' => true,
    ]);
    $http->on('workerStart', function (\Swoole\Http\Server $server) {
        echo "workerStart 
    "; }); $http->on('request', function (\Swoole\Http\Request $request, \Swoole\Http\Response $response) { // request_uri proto rpc : /{package}.{service}/{rpc} $path = $request->server['request_uri']; if ($path == '/grpc.HelloService/SayHello') { // decode, rpc $request_message = \Grpc\Parser::deserializeMessage([HelloRequest::class, null], $request->rawContent()); // encode, rpc $response_message = new HelloReply(); $response_message->setMessage('Hello ' . $request_message->getName()); $response->header('content-type', 'application/grpc'); $response->header('trailer', 'grpc-status, grpc-message'); $trailer = [ "grpc-status" => "0", "grpc-message" => "" ]; foreach ($trailer as $trailer_name => $trailer_value) { $response->trailer($trailer_name, $trailer_value); } $response->end(\Grpc\Parser::serializeMessage($response_message)); } });

    ここには4つの部分が含まれています.
  • \Swoole\Http\Server:swooleを使用して実装されたhttp 2 server
  • .protoファイルで定義されたgrpcサービス名:request_uri proto rpc : /{package}.{service}/{rpc}
  • \Grpc\Parser:grpc情報の解析クラスは、grpcが使用するhttp 2プロトコルの詳細に基づいてクラスをカプセル化すると
  • になる.
  • HelloRequest/HelloReply: .ptotoファイル+protoc自動生成protobuf自動解析ファイル
  • serverのサンプルコードがあり、clientはswoole http 2コヒーレントclientを使用してパッケージ化することもできます.
  • \Grpc\Parserサンプルコード:
  • encode();
            } else if (method_exists($data, 'serializeToString')) {
                $data = $data->serializeToString();
            } else {
                /** @noinspection PhpUndefinedMethodInspection */
                $data = $data->serialize();
            }
            return self::pack($data);
        }
    
        public static function deserializeMessage($deserialize, string $value)
        {
            if (empty($value)) {
                return null;
            } else {
                $value = self::unpack($value);
            }
            if (is_array($deserialize)) {
                list($className, $deserializeFunc) = $deserialize;
                /** @var $obj Message */
                $obj = new $className();
                if ($deserializeFunc && method_exists($obj, $deserializeFunc)) {
                    $obj->$deserializeFunc($value);
                } else {
                    $obj->mergeFromString($value);
                }
                return $obj;
            }
    
            return call_user_func($deserialize, $value);
        }
    
        public static function parseToResultArray($response, $deserialize): array
        {
            if (!$response) {
                return ['No response', GRPC_ERROR_NO_RESPONSE, $response];
            } else if ($response->statusCode !== 200) {
                return ['Http status Error', $response->errCode ?: $response->statusCode, $response];
            } else {
                $grpc_status = (int)($response->headers['grpc-status'] ?? 0);
                if ($grpc_status !== 0) {
                    return [$response->headers['grpc-message'] ?? 'Unknown error', $grpc_status, $response];
                }
                $data = $response->data;
                $reply = self::deserializeMessage($deserialize, $data);
                $status = (int)($response->headers['grpc-status'] ?? 0 ?: 0);
                return [$reply, $status, $response];
            }
        }
    }
    

    最後に書く
    ここまで、基本的にgrpcの簡単な原理は、上に書いた例に示されていて、自分が以前蓄積した知識を融通して貫通することができて、喜びの気持ちが湧き出ています!
    特筆すべき点
    最初は原理を舍ててdemoを走って、绝えず环境を振り回して、コードを振り回して自动的に生成して、公式サイトdemoの上でますます远くなります.前に出会った例をもう一度挙げて、啓発してほしい.
    alipay ILLEGAL_SIGNエラー解決:https://www.jianshu.com/p/28585a6454b2

    呼び出しリンク全体が非常に長く、debug問題の前後にtraceが長く、できる限り様々な試みをしたが、本質に戻った:httpプロトコル
    だから、「http権威ガイド」を開いて、よく調べてみると、httpプロトコルの中でcharsetに影響を与えるのは2つの場所だけです.
  • クライアント:accept-charset='utf-8'
  • サービス:content-type: text/plain;charset:utf-8
  • 補足&&その他
  • swoole/grpc:参考になる項目は、swoole/grpc/examples/grpc/greeter_server.phpswoole/grpc/examples/grpc/greeter_client.php
  • のみを見ることをお勧めします.
  • protobufの実践について、私の前のblog:サーバー開発シリーズ1
  • nginxによるgrpcのサポート:https://www.nginx.com/blog/nginx-1-13-10-grpc/
  • 極客時間-深入浅出grpc:https://time.geekbang.org/column/intro/75

  • 詳細:
  • grpcシーケンス化メカニズム&grpcセキュリティ設計
  • swoft 2でgrpcの
  • を簡単に使用する方法