gRPC 使用protobuf 构建微服务

本文目录:

gRPC 使用protobuf 构建微服务

gRPC 使用protobuf 构建微服务

 

微服务架构

单一的代码库

以前使用Laravel 做web 项目时,是根据MVC 去划分目录结构的,即Controller 层处理业务逻辑,Model 层处理数据库的CURD,View 层处理数据渲染与页面交互。以及MVP、MVVM 都是将整个项目的代码是集中在一个代码库中,进行业务处理。这种单一聚合代码的方式在前期实现业务的速度很快,但在后期会暴露很多问题:

  • 开发与维护困难:随着业务复杂度的增加,代码的耦合度往往会变高,多个模块相互耦合后不易横向扩展
  • 效率和可靠性低:过大的代码量将降低响应速度,应用潜在的安全问题也会累积

拆分的代码库

微服务是一种软件架构,它将一个大且聚合的业务项目拆解为多个小且独立的业务模块,模块即服务,各服务间使用高效的协议(protobuf、JSON 等)相互调用即是RPC 。这种拆分代码库的方式有以下特点:

  • 每个服务应作为小规模的、独立的业务模块在运行,类似Unix 的Do one thing and do it well
  • 每个服务应在进行自动化测试和(分布式)部署时,不影响其他服务
  • 每个服务内部进行细致的错误检查和处理,提高了健壮性

二者对比

本质上,二者只是聚合与拆分代码的方式不同。

gRPC 使用protobuf 构建微服务

gRPC 使用protobuf 构建微服务

参考:微服务架构的优势与不足

构建微服务

UserInfoService 微服务

接下来创建一个处理用户信息的微服务:UserInfoService,客户端通过name 向服务端查询用户的年龄、职位等详细信息,需先安装gRPC 与protoc 编译器:

<span style="color:rgba(0, 0, 0, 0.86)"><span style="color:rgba(0, 0, 0, 0.68)">go get -u google.golang.org/grpc
go get -u github.com/golang/protobuf/protoc-gen-go
</span></span>

目录结构

<span style="color:rgba(0, 0, 0, 0.86)"><span style="color:rgba(0, 0, 0, 0.68)">├── proto
│ ├── user.proto // 定义客户端请求、服务端响应的数据格式
│ └── user.pb.go // protoc 为gRPC 生成的读写数据的函数
├── server.go // 实现微服务的服务端
└── client.go // 调用微服务的客户端
</span></span>

调用流程

gRPC 使用protobuf 构建微服务

gRPC 使用protobuf 构建微服务

Protobuf 协议

每个微服务有自己独立的代码库,各自之间在通信时需要高效的协议,要遵循一定的数据结构来解析和编码要传输的数据,在微服务中常使用protobuf 来定义。

Protobuf(protocal buffers)是谷歌推出的一种二进制数据编码格式,相比XML和JSON的文本数据编码格式更有优势:

读写更快、文档体积更小

它没有XML的标签名或JSON的字段名,更为轻量,更多参考

gRPC 使用protobuf 构建微服务

gRPC 使用protobuf 构建微服务

语言中立

只需定义一份.proto 文档,即可使用各语言对应的protobuf 编译器对其编译,生成的文档中有对message 编码、解码的函数

对于JSON

  • 在PHP中需使用json_encode()json_decode()去编解码,在Golang中需使用json标准库的Marshal()Unmarshal()…每次解析和编码比较繁琐
  • 优点:可读性好、开发成本低
  • 缺点:相比protobuf 的读写速度更慢、存储空间更多

对于Protobuf

  • .proto可生成 .php或*.pb.go …在项目中可直接引用该文档中编译器生成的编码、解码函数
  • 优点:高效轻量、一处定义多处使用
  • 缺点:可读性差、开发成本高

定义微服务的user.proto 文档

<span style="color:rgba(0, 0, 0, 0.86)"><span style="color:rgba(0, 0, 0, 0.68)">syntax = "proto3"; // 指定语法格式,注意proto3 不再支持proto2 的required 和optional
package proto; // 指定生成的user.pb.go 的包名,防止命名冲突


// service 定义开放调用的服务,即UserInfoService 微服务
service UserInfoService {
    // rpc 定义服务内的GetUserInfo 远程调用
    rpc GetUserInfo (UserRequest) returns (UserResponse) {
    }
}


// message 对应生成代码的struct
// 定义客户端请求的数据格式
message UserRequest {
 // [修饰符] 类型字段名= 标识符;
 string name = 1;
}


// 定义服务端响应的数据格式
message UserResponse {
    int32 id = 1;
    string name = 2;
    int32 age = 3;
    repeated string title = 4; // repeated 修饰符表示字段是可变数组,即slice 类型
}
</span></span>

编译user.proto 文档

<span style="color:rgba(0, 0, 0, 0.86)"><span style="color:rgba(0, 0, 0, 0.68)"># protoc 编译器的grpc 插件会处理service 字段定义的UserInfoService
# 使service 能编码、解码message
$ protoc -I . --go_out=plugins=grpc:. ./user.proto
</span></span>

生成user.pb.go

<span style="color:rgba(0, 0, 0, 0.86)"><span style="color:rgba(0, 0, 0, 0.68)">package proto

import (
 context "golang.org/x/net/context"
 grpc "google.golang.org/grpc"
)

// 请求结构
type UserRequest struct {
 Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
}

// 为字段自动生成的Getter
func (m *UserRequest) GetName() string {
 if m != nil {
  return m.Name
 }
 return ""
}

// 响应结构
type UserResponse struct {
 Id int32 `protobuf:"varint,1,opt,name=id" json:"id,omitempty"`
 Name string `protobuf:"bytes,2,opt,name=name" json:"name,omitempty"`
 Age int32 `protobuf:"varint,3,opt,name=age" json:"age,omitempty"`
 Title []string `protobuf:"bytes,4,rep,name=title" json:"title,omitempty"`
}
// ...

// 客户端需实现的接口
type UserInfoServiceClient interface {
 GetUserInfo(ctx context.Context, in *UserRequest, opts ...grpc.CallOption) (*UserResponse, error)
}


// 服务端需实现的接口
type UserInfoServiceServer interface {
 GetUserInfo(context.Context, *UserRequest) (*UserResponse, error)
}

// 将微服务注册到grpc 
func RegisterUserInfoServiceServer(s *grpc.Server, srv UserInfoServiceServer) {
 s.RegisterService(&_UserInfoService_serviceDesc, srv)
}
// 处理请求
func _UserInfoService_GetUserInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {...}
</span></span>

服务端实现微服务

实现流程

gRPC 使用protobuf 构建微服务

gRPC 使用protobuf 构建微服务

代码参考

<span style="color:rgba(0, 0, 0, 0.86)"><span style="color:rgba(0, 0, 0, 0.68)">package main
import (...)

// 定义服务端实现约定的接口
type UserInfoService struct{}
var u = UserInfoService{}

// 实现interface
func (s *UserInfoService) GetUserInfo(ctx context.Context, req *pb.UserRequest) (resp *pb.UserResponse, err error) {
 name := req.Name

 // 模拟在数据库中查找用户信息
 // ...
 if name == "wuYin" {
  resp = &pb.UserResponse{
   Id: 233,
   Name: name,
   Age: 20,
   Title: []string{"Gopher", "PHPer"}, // repeated 字段是slice 类型
  }
 }
 err = nil
 return
}

func main() {
 port := ":2333"
 l, err := net.Listen("tcp", port)
 if err != nil {
  log.Fatalf("listen error: %v\n", err)
 }
 fmt.Printf("listen %s\n", port)
 s := grpc.NewServer()

    // 将UserInfoService 注册到gRPC
    // 注意第二个参数UserInfoServiceServer 是接口类型的变量
 // 需要取地址传参
 pb.RegisterUserInfoServiceServer(s, &u)
 s.Serve(l)
}
</span></span>

运行监听:

gRPC 使用protobuf 构建微服务

gRPC 使用protobuf 构建微服务

客户端调用

实现流程

gRPC 使用protobuf 构建微服务

gRPC 使用protobuf 构建微服务

代码参考

<span style="color:rgba(0, 0, 0, 0.86)"><span style="color:rgba(0, 0, 0, 0.68)">package main
import (...)

func main() {
 conn, err := grpc.Dial(":2333", grpc.WithInsecure())
 if err != nil {
  log.Fatalf("dial error: %v\n", err)
 }
 defer conn.Close()

    // 实例化UserInfoService 微服务的客户端
 client := pb.NewUserInfoServiceClient(conn)

 // 调用服务
 req := new(pb.UserRequest)
 req.Name = "wuYin"
 resp, err := client.GetUserInfo(context.Background(), req)
 if err != nil {
  log.Fatalf("resp error: %v\n", err)
 }

 fmt.Printf("Recevied: %v\n", resp)
}
</span></span>

运行调用成功:

gRPC 使用protobuf 构建微服务

gRPC 使用protobuf 构建微服务

 

原文转载至:https://hk.saowen.com/a/1a39eed946c90253d178f693012da948f5a0e51859433e5c3963e8b18b2fb8b6

展开阅读全文

没有更多推荐了,返回首页