0%

再谈RPC

gRPC是一个高性能、通用的开源RPC框架,基于底层HTTP/2协议标准和协议层Protobuf序列化协议开发,支持众多的开发语言。

一、基础

  1. IPC、LPC、RPC

    • 进程间通信IPC(Inter-Process Communication),是指不同进程之间传递、交换数据的一些方法和技术。
      • 通信技术主要包括管道、信号、消息队列、共享内存、信号量、socket
      • 这些进程可以运行在同一计算机上或通过网络连接的不同计算机上
      • IPC通信目的
        • 数据传输:一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几兆字节之间。
        • 共享数据:多个进程想要操作共享数据,一个进程对共享数据的修改,别的进程应该立刻看到。
        • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
        • 资源共享:多个进程之间共享同样的资源。为了做到这一点,需要内核提供锁和同步机制。
        • 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
      • 有两种类型的进程间通信方式,即LPC和RPC。
    • 本地过程调用LPC(Local Procedure Call)
    • 远程过程调用RPC(Remote Procedure Call)
  2. RPC与HTTP

    • RPC是一种思想,或者说是一种设计,主要用于解决不同服务之间的调用问题,一般实现RPC会包含传输协议和序列化协议两个部分。
    • HTTP是一种超文本传输协议,主要用于规范通信双方的通信格式。
      • RPC的实现可以基于HTTP,也可以基于TCP或UDP等,主要看应用场景和不同的RPC框架底层选择。
  3. gRPC,谷歌出品,RPC框架的一种,是一个高性能、开源、多语言支持、通用RPC框架。

  4. protobuf,谷歌出品,一个语言无关平台无关、效率极高、二进制的数据序列化工具,在rpc或tcp通信等很多场景都可以使用。

    • 语法规则
    • 特性

二、实战

1
2
GOPATH="/Users/liuyulong/go"
GOVERSION="go1.18.1"

MacOS + Golang as server and client

  1. 安装protoc,protobuf文件的编译器(或者叫解释器、翻译器),用于将.proto文件编译为指定语言的类库

    • 源码安装,省略
    • brew安装
      • brew install protobuf
        • 安装完成后查看版本protoc --version,博主当前用的版本是libprotoc 3.13.0,想要安装别的版本可以brew search protobuf
  2. 安装插件

    • go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26
    • go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1
  3. 修改Path export PATH="$PATH:$(go env GOPATH)/bin"

  4. 创建项目目录 mkdir grpc

  5. 创建项目文件夹 cd grpc && mkdir client pb service

  6. 编写.proto文件 cd bp && vim my.proto

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
syntax = "proto3";

option go_package = "grpc/pb";

service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
string name = 1;
}

message HelloReply {
string message = 1;
}
  1. 编译.proto文件 protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative ./my.proto,会自动生成my_grpc.pb.gomy.pb.go

    • 报错:--go_out: protoc-gen-go: plugins are not supported; use 'protoc --go-grpc_out=...' to generate gRPC
    • 解决
      • 方案一:使用go-grpc_out=代替--go_out
      • 方案二:重新安装
        • 报错package google.golang.org/protobuf/types/descriptorpb: unrecognized import path "google.golang.org/protobuf/types/descriptorpb": https fetch: Get "https://google.golang.org/protobuf/types/descriptorpb?go-get=1": dial tcp 142.251.43.17:443: i/o timeout
        • 解决
  2. 编写server端代码 cd service && vim server.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package main

import (
"context"
"google.golang.org/grpc"
"log"
"net"

pb "grpc/pb"
)

const (
port = ":50051"
)

type server struct {
pb.UnimplementedGreeterServer
}

func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
log.Printf("Received: %v", in.GetName())
return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}

func main() {
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
// 服务注册
pb.RegisterGreeterServer(s, &server{})
log.Printf("server listening at %v", lis.Addr())
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
  1. 编写client端代码cd client && vim client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package main

import (
"context"
"google.golang.org/grpc"
"log"
"os"
"time"

pb "grpc/pb"
)

const (
address = ":50051"
defaultName = "world"
)

func main() {
conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewGreeterClient(conn)

name := defaultName
if len(os.Args) > 1 {
name = os.Args[1]
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})
if err != nil {
log.Fatalf("could not greet: %v", err)
}
log.Printf("Greeting: %s", r.GetMessage())
}
  1. 使用go mod下载相关依赖

    • cd grpc
    • go mod init grpc
    • go mod tidy
  2. 开始测试

    • go run service/server.go
    • go run client/client.go liusir

MacOS + Go as server + PHP as client

  1. 目录规划
    • cd /Users/liuyulong/go/src
    • mkdir grpc-go-php
    • cd grpc-go-php
  2. Go环境准备,省略
  3. PHP环境准备
    • PHP安装gRPC扩展
    • PHP安装protobuf扩展
  4. 编写proto文件
    • protoc –go-grpc_out=../services Prod.proto
  5. 编写server.go
  6. 编写client.php

MacOS + Go as server(net/rpc/jsonrpc) + PHP as client

  1. server.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package main

import (
"fmt"
"log"
"net"
"net/rpc"
"net/rpc/jsonrpc"
)

type Calc struct{}

type Args struct {
A float64 `json:"a"`
B float64 `json:"b"`
Op string `json:"op"`
}

type Reply struct {
Msg string `json:"msg"`
Data float64 `json:"data"`
}

func (c *Calc) Compute(args Args, reply *Reply) error {
var (
msg string = "ok"
)

switch args.Op {
case "+":
reply.Data = args.A + args.B
case "-":
reply.Data = args.A - args.B
case "*":
reply.Data = args.A * args.B
case "/":
if args.B == 0 {
msg = "in divide op, B can't be zero"
} else {
reply.Data = args.A / args.B
}
default:
msg = fmt.Sprintf("unsupported op:%s", args.Op)
}
reply.Msg = msg

if reply.Msg == "ok" {
return nil
}
return fmt.Errorf(msg)
}

func main() {
err := rpc.Register(new(Calc))

if err != nil {
panic(err)
}

listener, err := net.Listen("tcp", "127.0.0.1:8181")
if err != nil {
panic(err)
}

for {
conn, err := listener.Accept()

if err != nil {
log.Println(err)
continue
}

go jsonrpc.ServeConn(conn)
}
}
  1. client.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php
global $conn;
function Call($method, $params) {
$conn = fsockopen('127.0.0.1', 8181, $errno, $errstr, 3);
if (!$conn) {
return false;
}
$err = fwrite($conn, json_encode(array(
'method' => $method,
'params' => array($params),
'id' => 12345,
)) . "\n");
if ($err === false) {
return false;
}
stream_set_timeout($conn, 0, 3000);
$line = fgets($conn);
if ($line === false) {
return null;
}
return json_decode($line, true);
}

function Test() {
$res = Call("Calc.Compute", array('A' => 1, 'B' => 2, 'Op' => '*'));
return $res;
}
print_r(Test());
  1. go run server.go
  2. php client.php

三、参考

  1. 参考一
  2. 参考二
  3. 参考三
  4. 参考四
  5. 参考五