GRPC 服务调用实践(一)
服务化
- 最近在看微服务相关的设计,其中
grpc
算是微服务框架的标配所以要研究一下 - 本文适合对 grpc 有一点印象的,不知道的站内可以搜下,
golang
板块有人写的挺好的,我就不写了~ - 因为公司有使用 三种语言的团队,所以寻求一种除了,
http
之外更高效的协议,才有了之后的事情,最后挺一下swoft
跟hyperf
加油!
gin 实现 grpc 简单的应答
安装protobuf
1、安装相关软件 ,我用的是 contOS7+windows , mac应该更好装一点
yum install autoconf automake libtool gcc gcc-c++ zlib-devel
2、 下载protobuf,并安装
去到 Protocol Buffers 下载最新版本,然后解压到本地。
wget https://github.com/protocolbuffers/protobuf/releases/download/v3.10.1/protoc-3.10.1-linux-x86_64.zip
unzip protoc-3.10.1-linux-x86_64.zip
protoc --version # 能看到 版本信息就安装成功了
安装 protobuf golang 插件
go get -u github.com/golang/protobuf/{proto,protoc-gen-go}
前提条件
go 1.13 以上版本
go.mod 代理 ok
export GOPROXY=https://goproxy.cn export GO111MODULE=on
目录如下
- rpc-hello - pb - hello.proto - go.mod
hello.proto 内容如下
syntax = "proto3"; package hello; // 定义服务 service Hello { rpc SayHello (HelloRequest) returns (HelloReply) {} } // 请求体的结构体 message HelloRequest { string name = 1; } // 响应的结构体 message HelloReply { string message = 1; int64 code = 2; }
进入 pb 下 执行
protoc --go_out=plugins=grpc:. hello.proto
- 会发现 多了
rpc-hello\pb\hello.pb.go
这样的一个文件
grpc四种服务类型:
1、简单方式:这就是一般的rpc调用,一个请求对象对应一个返回对象
2、服务端流式(Sever-side streaming )
3、客户端流式(Client-side streaming RPC)
4、双向流式(Bidirectional streaming RPC)
简单方式的调用
更改目录为
rpc-hello - pb - hello.proto - hello.pb.go - go.mod - service - service.go - client - client.go
服务端 实现 service
package main import ( "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/reflection" "log" "net" pb "rpc-hello/pb" ) // server 用来实现 hello.HelloServer type server struct{} // 实现 hello.SayHello 方法 // (context.Context, *HelloRequest) (*HelloReply, error) func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { return &pb.HelloReply{Message: "Hello " + in.Name, Code: 200}, nil } func main() { lis, err := net.Listen("tcp", ":50051") if err != nil { log.Fatalf("failed to listen: %v", err) } s := grpc.NewServer() pb.RegisterHelloServer(s, &server{}) //在 server 中 注册 gRPC 的 reflection service reflection.Register(s) if err := s.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } }
客户端 实现 client.go
package main import ( "fmt" "github.com/gin-gonic/gin" "google.golang.org/grpc" "log" "net/http" pb "rpc-hello/pb" ) func main() { r := gin.Default() r.GET("/rpc/hello", func(c *gin.Context) { sayHello(c) }) // Run http server if err := r.Run(":8052"); err != nil { log.Fatalf("could not run server: %v", err) } } func sayHello(c *gin.Context) { // Set up a connection to the server. conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure()) if err != nil { log.Fatalf("did not connect: %v", err) } defer conn.Close() client := pb.NewHelloClient(conn) name := c.DefaultQuery("name","战士上战场") req := &pb.HelloRequest{Name: name} res, err := client.SayHello(c, req) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": err.Error(), }) return } c.JSON(http.StatusOK, gin.H{ "result": fmt.Sprint(res.Message), "code": fmt.Sprint(res.Code), }) }
先 执行
go run service.go
切换另一个终端执行go run client.go
访问http://xxx.xxx.xx.xx:8052/rpc/hello
就能看到效果~
php 原生实现客户端
安装 protoc-gen-php 扩展
git clone -b $(curl -L https://grpc.io/release) https://github.com/grpc/grpc
cd grpc
git submodule update --init # 这一步 要下4个小时,建议还是要挂git代理
make grpc_php_plugin # 警告没事,没error 就行
# 建议 国内用户换这个 ,再切换到对应版本分支,不然太难受了
git clone https://gitee.com/devin2019/grpc.git
cd grpc
git checkout xxxx # 对应分支
git submodule update --init #这一步 要下4个小时,建议还是要挂git代理
make grpc_php_plugin # 警告没事,没error 就行
- 最终
grpc_php_plugin
在 你的/pathto/grpc/bins/opt/grpc_php_plugin
安装 grpc 扩展
wget https://pecl.php.net/get/grpc-1.25.0.tgz
tar -zxvf grpc-1.25.0.tgz
/usr/bin/phpize #(这个根据`phpize`实际情况来)
./configure --with-php-config=/usr/bin/php-config #(这个根据`php-config`实际情况来)
make && make install
vim /etc/php.d/grpc.ini #这个根据实际情况去决定 是改`php.ini`还是别的什么
写入 extension=grpc.so
安装 protobuf 扩展
要想 gRPC获得更好的性能,就安装 protobuf 扩展
wget https://pecl.php.net/get/protobuf-3.10.0.tgz tar -zxvf protobuf-3.10.0.tgz /usr/bin/phpize #(这个根据`phpize`实际情况来) ./configure --with-php-config=/usr/bin/php-config #(这个根据`php-config`实际情况来) make && make install vim /etc/php.d/protobuf.ini #这个根据实际情况去决定 是改`php.ini`还是别的什么 写入 extension=protobuf.so
偷懒做法 就直接
composer
拉个包"require": { "google/protobuf": "^v3.10.0" },
生成文件 带客户端
使用命令为
protoc --php_out=./ --grpc_out=./ --plugin=protoc-gen-grpc=/root/go/bin/grpc_php_plugin hello.proto
这个是带Client
的做法这里 我 把
grpc_php_plugin
做了软链接 你按安装时的位置输出就好不带
Client
的 为protoc --php_out=plugins=grpc:. grpc.proto
|-- composer.json |-- composer.lock |-- GPBMetadata | |-- Hello.php |-- Hello | |-- HelloClient.php | |-- HelloReply.php | |-- HelloRequest.php |-- hello.proto |-- index.php |-- vendor
index.php
<?php
require_once __DIR__ . '/vendor/autoload.php';
use \Grpc\ChannelCredentials;
use \Hello\HelloClient;
use \Hello\HelloRequest;
use \Hello\HelloReply;
// 创建客户端实例
$helloClient = new HelloClient('127.0.0.1:50051', [
'credentials' => ChannelCredentials::createInsecure()
]);
$helloRequest = new HelloRequest();
$helloRequest->setName("有事别找我");
$request = $helloClient->SayHello($helloRequest)->wait();
//返回数组
/** @var array $status */
/** @var HelloReply $response */
list($response, $status) = $request;
var_dump($response->getMessage());
echo PHP_EOL;
var_dump($status);
composer.json
{
"name": "rpc/test",
"require": {
"grpc/grpc": "v1.25.0",
"google/protobuf": "^v3.10.0"
},
"autoload":{
"psr-4":{
"GPBMetadata\\":"GPBMetadata/",
"Hello\\":"Hello/"
}
},
"repositories": {
"packagist": {
"type": "composer",
"url": "https://mirrors.aliyun.com/composer/"
}
}
}
先做了缓存的,一定要在改过之后
composer dump -o
开启
service.go
运行php index.php
客户端就完成了
php swoole
框架 hyperf
实现 实现客户端 与服务端 与 golang
服务互调
- 官方文档
swoole
安装就不讲了,比起swoole2.x
现在的安装也很简单了hyperf
安装也很简单,唯一要 注意的是 需要 在php.ini
中 加入swoole.use_shortname=off
- 使用
composer create-project hyperf/hyperf-skeleton
来 构建基础代码 - 选中
grpc
服务其它的额外组件都不用选
服务端
我们先将 上一步 原生生成的文件 ,在项目根目录下加一个
grpc
目录,内部结构如下|-- GPBMetadata | |-- Hello.php |-- Hello | |-- HelloClient.php | |-- HelloReply.php | |-- HelloRequest.php
更改 composer.json 同样是在 psr-4 中 加入
"GPBMetadata\\": "grpc/GPBMetadata", "Grpc\\": "grpc/Grpc"
config/autoload/server
中 加入[ 'name' => 'grpc', 'type' => Server::SERVER_HTTP, 'host' => '0.0.0.0', 'port' => 50051, 'sock_type' => SWOOLE_SOCK_TCP, 'callbacks' => [ SwooleEvent::ON_REQUEST => [\Hyperf\GrpcServer\Server::class, 'onRequest' ], ], ],
在
config/routes.php
里加入Router::addServer("grpc", function () { Router::addGroup('/hello.Hello', function () { Router::post('/SayHello', 'App\Controller\IndexController@sayHello'); }); });
在
app/Controller/IndexController.php
中 加入public function sayHello(HelloRequest $request) { $message = new HelloReply(); $message->setMessage("Hello {$request->getName()}"); $message->setCode(200); return $message; }
这里要注意的是 .proto 文件中的定义和 gRPC server 路由的对应关系:
/{package}.{service}/{rpc}
服务端封装详情可以看源码,也不难
客户端
在
app/Controller/IndexController.php
中修改use Hello\HelloClient; use Hello\HelloReply; use Hello\HelloRequest; use Hyperf\HttpServer\Contract\RequestInterface; public function index(RequestInterface $request) { $client = new HelloClient("127.0.0.1:50051", ['credentials' => null]); $name = $request->input("name","战士上战场"); $helloRequest = new HelloRequest(); $helloRequest->setName($name); /** * @var HelloReply $reply */ list($reply, $status) = $client->sayHello($helloRequest); $message = $reply->getMessage(); $code = $reply->getCode(); $client->close(); var_dump(memory_get_usage(true)); return [ 'message' => $message, 'code' => $code, ]; }
上一步中 用了
HelloClient
看看 框架封装,跟原生的区别封装的
namespace Hello; use Hyperf\GrpcClient\BaseClient; /** * 定义服务 */ class HelloClient extends BaseClient{ public function sayHello(HelloRequest $argument) { return $this->simpleRequest( '/hello.Hello/SayHello', $argument, [HelloReply::class, 'decode'] ); } }
自动生成的
namespace Hello; /** * 定义服务 */ class HelloClient extends \Grpc\BaseStub { /** * @param string $hostname hostname * @param array $opts channel options * @param \Grpc\Channel $channel (optional) re-use channel object */ public function __construct($hostname, $opts, $channel = null) { parent::__construct($hostname, $opts, $channel); } /** * @param \Hello\HelloRequest $argument input argument * @param array $metadata metadata * @param array $options call options */ public function SayHello(\Hello\HelloRequest $argument, $metadata = [], $options = []) { return $this->_simpleRequest( '/hello.Hello/SayHello', $argument, ['\Hello\HelloReply', 'decode'], $metadata, $options); } }
结论是其实差不了多少
然后 你可以试试
php grpc
服务端 跟golang grpc
客户端的互调果然装环境才是学习程序最难的地方,特别是这个下了4个小时的git命令,太南了0.0
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: