tech| 再探 grpc

date: 2019-04-25 22:16:01
title: tech| 再探 grpc

折騰 grpc 過幾次, 都沒有大規(guī)模的用起來, 熟悉程度多停留在官網(wǎng)的 helloworld 上, 對(duì)原理的理解不夠深入, 所以經(jīng)常會(huì)卡住.

grpc| python 實(shí)戰(zhàn) grpc

這里有介紹過我 卡住 的點(diǎn), 按照官網(wǎng)的 quick start 文檔:

  • 使用 php: 配置 PHP 的環(huán)境麻煩, 尤其 grpc/grpc 代碼庫(kù)編譯出 grpc_php_plugin 這一步
  • 使用 go: 安裝 golang 包, 經(jīng)常 撞墻(go get 失敗)
  • 最后 偷懶 使用 python 跑了一遍, 最大的收獲是 grpc 除了 單向請(qǐng)求, 還有 雙向通信(stream, 流式通信), 把環(huán)境的問題繞過去后跑通了 demo

來自 PHPer 的靈魂叩問: 要么搞定環(huán)境, 要么用不了 grpc ?

就是陷入到這個(gè)問題里去了, 一直繞不出來. 但是理解了 grpc 基本原理, 換個(gè)思路, 就會(huì)發(fā)現(xiàn)非常的簡(jiǎn)單.

官方文檔的解讀

grpc - quickstart - php: https://grpc.io/docs/quickstart/php/

官方 php quickstart 介紹的步驟:

  • grpc 環(huán)境
    • ext-grpc
    • github.com/grpc/grpc 源碼庫(kù)中編譯出 grpc_php_plugin, 此擴(kuò)展用來配合 protoc, 來自動(dòng)生成代碼
  • protobuf 環(huán)境
    • proto 文件, 基于 IDL 文件定義服務(wù), 目前使用 proto3 語(yǔ)法(語(yǔ)法很簡(jiǎn)單, 一刻鐘內(nèi)就可以看完)
    • protoc, protobuf compile, proto 文件編譯器, 可以理解 proto 文件基于不同開發(fā)語(yǔ)言進(jìn)行 翻譯
    • protobuf runtime, protobuf 格式的運(yùn)行時(shí)支持, protobuf 序列化后的信息, 需要 protobuf runtime

有 2 點(diǎn)容易讓人產(chǎn)生誤讀的地方:

  • 順序: 先理解了 protobuf 環(huán)境, 進(jìn)一步再來構(gòu)建 grpc
  • 官網(wǎng)自動(dòng)生成的代碼, 只是能跑通 grpc 服務(wù)調(diào)用. 但現(xiàn)實(shí)是, rpc 服務(wù), 需要一整套的服務(wù)框架進(jìn)行支持, 比如說: 微服務(wù)

理解 grpc

從幾個(gè)基礎(chǔ)的點(diǎn), 一點(diǎn)一點(diǎn)來看 grpc.

  • protobuf: 序列化, 編碼的基礎(chǔ)知識(shí)
  • rpc, tcp 基礎(chǔ)上的通信: tcp 通信為什么需要協(xié)議, 協(xié)議設(shè)計(jì)簡(jiǎn)單
  • grpc 的通信協(xié)議細(xì)節(jié)

protobuf

protobuf 環(huán)境:

  • proto 文件, 基于 IDL 文件定義服務(wù), 目前使用 proto3 語(yǔ)法(語(yǔ)法很簡(jiǎn)單, 一刻鐘內(nèi)就可以看完)
  • protoc, protobuf compile, proto 文件編譯器, 可以理解 proto 文件基于不同開發(fā)語(yǔ)言進(jìn)行 翻譯
  • protobuf runtime, protobuf 格式的運(yùn)行時(shí)支持, protobuf 序列化后的信息, 需要 protobuf runtime

通過時(shí)序來理解:

  • proto 文件 -> protc 編譯 -> 自動(dòng)生成不同語(yǔ)言的代碼(gen code)
  • gen code + protobuf runtime -> 信息序列化/反序列化

補(bǔ)充一點(diǎn), 信息的序列化/反序列化, 就涉及到編碼的知識(shí), 包括: 進(jìn)制轉(zhuǎn)換 -> 字符集(為什么會(huì)亂碼) -> 大端序/小端序/網(wǎng)絡(luò)序(php pack()/unpack() 函數(shù))

具體到 PHP 中, 以官網(wǎng)的 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 發(fā)行版, 使用相應(yīng)包管理工具安裝
apk add protobuf
protoc --version # 驗(yàn)證 protoc 是否安裝成功

# 使用 protoc 生成代碼
protoc --php_out=grpc/ game.proto # 使用 --php_out 選項(xiàng), 指定生成 PHP 代碼的路徑
  • protobuf runtime

PHP 中其實(shí)很簡(jiǎn)單 ext-protobuf / google/protobuf package, 二選一

// ext-protobuf
pecl install protobuf

// google/protobuf
composer require google/protobuf

到這里, 就把 protobuf 這部分的內(nèi)容都解決了, 下面是生成的例子

// proto
message HelloRequest {
    string greeting = 1;
}
<?php
# Generated by the protocol buffer compiler.  DO NOT EDIT!
# source: hello.proto

namespace Grpc;

use Google\Protobuf\Internal\GPBType;
use Google\Protobuf\Internal\RepeatedField;
use Google\Protobuf\Internal\GPBUtil;

/**
 * Generated from protobuf message <code>grpc.HelloRequest</code>
 */
class HelloRequest extends \Google\Protobuf\Internal\Message
{
    /**
     * Generated from protobuf field <code>string greeting = 1;</code>
     */
    private $greeting = '';

    public function __construct() {
        \GPBMetadata\Hello::initOnce();
        parent::__construct();
    }

    /**
     * Generated from protobuf field <code>string greeting = 1;</code>
     * @return string
     */
    public function getGreeting()
    {
        return $this->greeting;
    }

    /**
     * Generated from protobuf field <code>string greeting = 1;</code>
     * @param string $var
     * @return $this
     */
    public function setGreeting($var)
    {
        GPBUtil::checkString($var, True);
        $this->greeting = $var;

        return $this;
    }

}

rpc, tcp 基礎(chǔ)上的通信

tcp/ip 4 層網(wǎng)絡(luò)通信:

  • 物理層/數(shù)據(jù)鏈路層: 網(wǎng)線/路由器/交換機(jī)/網(wǎng)卡 -> mac地址
  • ip 層: ip 地址, 4 種網(wǎng)絡(luò)地址類型
  • tcp/udp層: 端口, 端口上綁定的服務(wù)
  • 協(xié)議層: 各種熟悉的協(xié)議, http/ftp

為什么需要協(xié)議: tcp 是流式(stream)傳輸數(shù)據(jù)的, 需要協(xié)議來確定數(shù)據(jù)邊界
簡(jiǎn)單協(xié)議設(shè)計(jì): EOF結(jié)束符 / 固定包頭

swoole wiki - 網(wǎng)絡(luò)通信協(xié)議設(shè)計(jì): https://wiki.swoole.com/wiki/page/484.html

有了 swoole, tcp 通信, 編程十分簡(jiǎn)單:

  • server.php: tcp 協(xié)程 server
<?php

use Swoole\Server;

// swoole>=v4.0 開始默認(rèn)開啟協(xié)程
$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 協(xié)程 client
<?php

use Swoole\Coroutine\Client;

$c = new Client(SWOOLE_SOCK_TCP);
$c->connect('127.0.0.1', '9502');
$c->send('hello');
echo $c->recv();
$c->close();
  • 加上協(xié)議處理, 簡(jiǎn)單的協(xié)議只需要修改配置就可以實(shí)現(xiàn)
<?php

use Swoole\Coroutine\Client;

$c = new Client(SWOOLE_SOCK_TCP);
// 協(xié)議處理
$client->set([
    'open_length_check'     => 1,
    'package_length_type'   => 'N',
    'package_length_offset' => 0,       //第N個(gè)字節(jié)是包長(zhǎng)度的值
    'package_body_offset'   => 4,       //第幾個(gè)字節(jié)開始計(jì)算長(zhǎng)度
    'package_max_length'    => 2000000,  //協(xié)議最大長(zhǎng)度
]);
$c->connect('127.0.0.1', '9502');
$c->send('hello');
echo $c->recv();
$c->close();

grpc = http2 + protobuf

grpc 基于 http2 協(xié)議進(jìn)行通信, 理解上面的基礎(chǔ)知識(shí), 再來看 grpc 使用的 http2 協(xié)議通信細(xì)節(jié), 完全可以簡(jiǎn)單實(shí)現(xiàn):

<?php

$http = new \Swoole\Http\Server('0.0.0.0', 9501);
$http->set([
    'open_http2_protocol' => true,
]);
$http->on('workerStart', function (\Swoole\Http\Server $server) {
    echo "workerStart \n";
});
$http->on('request', function (\Swoole\Http\Request $request, \Swoole\Http\Response $response) {
    // request_uri 和 proto 文件中 rpc 對(duì)應(yīng)關(guān)系: /{package}.{service}/{rpc}
    $path = $request->server['request_uri'];

    if ($path == '/grpc.HelloService/SayHello') {
        // decode, 獲取 rpc 中的請(qǐng)求
        $request_message = \Grpc\Parser::deserializeMessage([HelloRequest::class, null], $request->rawContent());

        // encode, 返回 rpc 中的應(yīng)答
        $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));
    }
});

這里包括四部分:

  • \Swoole\Http\Server: 使用 swoole 實(shí)現(xiàn)的 http2 server
  • .proto 文件中定義的 grpc 服務(wù)名: request_uri 和 proto 文件中 rpc 對(duì)應(yīng)關(guān)系: /{package}.{service}/{rpc}
  • \Grpc\Parser: grpc 信息的解析類, 根據(jù) grpc 使用的 http2 協(xié)議細(xì)節(jié)封裝一個(gè)類就 搞定了
  • HelloRequest / HelloReply: .ptoto 文件 + protoc 自動(dòng)生成的 protobuf 自動(dòng)解析文件

server 的示例代碼有了, client 也可以使用 swoole http2 協(xié)程 client 相應(yīng)封裝了

  • \Grpc\Parser 示例代碼:
<?php

namespace Grpc;

use Google\Protobuf\Internal\Message;

class Parser
{

    public static function pack(string $data): string
    {
        return $data = pack('CN', 0, strlen($data)) . $data;
    }

    public static function unpack(string $data): string
    {
        return $data = substr($data, 5);
    }

    public static function serializeMessage($data)
    {
        if (method_exists($data, 'encode')) {
            $data = $data->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 的簡(jiǎn)單原理, 都在上面寫的例子中展示出來了, 能將自己以前積累的知識(shí)融會(huì)貫通起來, 喜悅之情噴涌而出!

值得一提的點(diǎn)

一開始卡住就是拋開原理跑 demo, 不斷在折騰環(huán)境, 折騰代碼自動(dòng)生成, 跑官網(wǎng) demo 上越走越遠(yuǎn). 之前遇到的一個(gè)例子再提一下, 希望能有所啟發(fā).

alipay ILLEGAL_SIGN 錯(cuò)誤解決: http://www.reibang.com/p/28585a6454b2

整個(gè)調(diào)用鏈路非常長(zhǎng), debug 問題的時(shí)候前前后后 trace 了很久, 盡其所能的做了各種嘗試, 但是回歸到本質(zhì): http 協(xié)議

所以赁严,翻開了《http 權(quán)威指南》昂羡,仔細(xì)查閱之后,你就會(huì)發(fā)現(xiàn)渴语,在 http協(xié)議里面怔揩,只有 2 個(gè)地方會(huì)影響到 charset:

  • 客戶端:accept-charset='utf-8'
  • 服務(wù)端:content-type: text/plain;charset:utf-8

補(bǔ)充 && 更多

更多:

  • grpc 序列化機(jī)制(protobuf) && grpc 安全性設(shè)計(jì)
  • 我是如何在 swoft2 中輕松使用 grpc 的
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末丈探,一起剝皮案震驚了整個(gè)濱河市冗尤,隨后出現(xiàn)的幾起案子库正,更是在濱河造成了極大的恐慌口锭,老刑警劉巖朦前,帶你破解...
    沈念sama閱讀 222,183評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異鹃操,居然都是意外死亡韭寸,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門荆隘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來恩伺,“玉大人,你說我怎么就攤上這事椰拒【” “怎么了?”我有些...
    開封第一講書人閱讀 168,766評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵燃观,是天一觀的道長(zhǎng)褒脯。 經(jīng)常有香客問我,道長(zhǎng)缆毁,這世上最難降的妖魔是什么番川? 我笑而不...
    開封第一講書人閱讀 59,854評(píng)論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上颁督,老公的妹妹穿的比我還像新娘践啄。我一直安慰自己,他們只是感情好沉御,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評(píng)論 6 398
  • 文/花漫 我一把揭開白布屿讽。 她就那樣靜靜地躺著,像睡著了一般吠裆。 火紅的嫁衣襯著肌膚如雪伐谈。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,457評(píng)論 1 311
  • 那天硫痰,我揣著相機(jī)與錄音衩婚,去河邊找鬼窜护。 笑死效斑,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的柱徙。 我是一名探鬼主播缓屠,決...
    沈念sama閱讀 40,999評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼护侮!你這毒婦竟也來了敌完?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,914評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤羊初,失蹤者是張志新(化名)和其女友劉穎滨溉,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體长赞,經(jīng)...
    沈念sama閱讀 46,465評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡晦攒,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了得哆。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片脯颜。...
    茶點(diǎn)故事閱讀 40,675評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖贩据,靈堂內(nèi)的尸體忽然破棺而出栋操,到底是詐尸還是另有隱情,我是刑警寧澤饱亮,帶...
    沈念sama閱讀 36,354評(píng)論 5 351
  • 正文 年R本政府宣布矾芙,位于F島的核電站,受9級(jí)特大地震影響近上,放射性物質(zhì)發(fā)生泄漏剔宪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望歼跟。 院中可真熱鬧和媳,春花似錦、人聲如沸哈街。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)骚秦。三九已至她倘,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間作箍,已是汗流浹背硬梁。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留胞得,地道東北人荧止。 一個(gè)月前我還...
    沈念sama閱讀 49,091評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像阶剑,于是被迫代替她去往敵國(guó)和親跃巡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評(píng)論 2 360

推薦閱讀更多精彩內(nèi)容

  • gRPC 是一個(gè)高性能牧愁、通用的開源RPC框架素邪,基于HTTP/2協(xié)議標(biāo)準(zhǔn)和Protobuf序列化協(xié)議開發(fā),支持眾多的...
    小波同學(xué)閱讀 19,498評(píng)論 6 19
  • 原文出處:gRPC gRPC分享 概述 gRPC 一開始由 google 開發(fā)猪半,是一款語(yǔ)言中立兔朦、平臺(tái)中立、開源的遠(yuǎn)...
    小波同學(xué)閱讀 7,239評(píng)論 0 18
  • 概述 gRPC是一個(gè)高性能磨确、通用的開源RPC框架沽甥,其由Google主要面向移動(dòng)應(yīng)用開發(fā)并基于HTTP/2協(xié)議標(biāo)準(zhǔn)而...
    JCone閱讀 4,375評(píng)論 1 3
  • date: 2018-5-15 22:12:32title: grpc| python 實(shí)戰(zhàn) grpcdescri...
    daydaygo閱讀 78,247評(píng)論 9 44
  • 突然,還愿從天跌到地俐填。很遺憾安接,一個(gè)這么好的故事就這么被掩蓋在了那個(gè)“符”之下,所有的文字果然擁有蠱惑人心的力量英融。 ...
    招財(cái)小能手閱讀 167評(píng)論 2 0