本文前置内容 需要学过java微服务开发,至少知道使用过openfeign和dubbo等rpc微服务组件的相关内容
相信已经学习到微服务开发的对grpc或多或少都有了解,高效的性能和protobuf这样轻量序列化的方式 无论是go开发必学还是java 使用dubbo或者其他深入也是需要了解的
相关概念
Protocol Buffers(简称 Protobuf)是一种由 Google 开发的数据序列化协议,用于高效地定义和交换结构化数据。它允许定义数据的结构和类型,并生成不同编程语言的代码来处理这些数据。
gRPC 是一个高性能、开源和通用的 RPC 框架,由 Google 开发。它基于 HTTP/2 协议,支持多种编程语言。gRPC 使用 Protobuf 作为默认的序列化协议。
核心逻辑
对于grpc 其实和微服务开发的方式有很大的相同,java中是采取将rpc服务单独拆分为一个模块,每个服务有很多未实现的接口定义,然后上传中央仓库或者jar包 由被调用的服务方实现,调用方只需要引入该模块 jar包即可完成调用的开发思想
go中的核心逻辑也是如此,只是将单独抽离的rpc模块这一步在gpc中是编写protobuf文件 ,然后插件生成代码,服务方实现接口,消费调用方 引入客户端调用
gRPC 框架的核心流程
编写 Protobuf 文件:
- 定义服务:使用 service 关键字定义远程调用服务及其方法。
- 定义消息:使用 message 关键字定义服务的请求和响应消息的结构。
生成代码:
使用 protoc 工具根据 .proto 文件生成客户端和服务端的代码。
具体来说:
- 消息类型:生成表示请求和响应数据结构的代码。
- 服务接口:生成服务接口的代码,供服务端实现。
- 客户端代码:生成客户端代码,用于调用服务端的方法。
- 实现服务端:实现 Protobuf 定义的服务接口,处理客户端请求并返回响应。
- 实现客户端:使用生成的客户端代码连接到服务端,调用远程方法并处理结果。
所以前置条件就是需要安装把protobuf生成go 代码的插件
安装插件
https://github.com/protocolbuffers/protobuf/releases/download/v3.9.0/protoc-3.9.0-win64.zip
go get github.com/golang/protobuf/proto
go get google.golang.org/grpc
go install github.com/golang/protobuf/protoc-gen-go
配置PATH环境变量 (go指令安装的文件都在项目的gopath目录)
认识protobuf文件
核心关键字
- syntax:指定 Protobuf 版本。
- package:定义命名空间。
- option:指定生成代码的配置。
- message:定义数据结构。
- enum:定义枚举类型。
- service:定义服务和 RPC 方法。
- repeated:定义列表类型的字段。
- oneof:定义一个字段组,表示只能设置一个字段。
比如:编写的user.proto
syntax = "proto3";
package example;
option go_package = "go-grpc-fast/protos;protos";
// 通用的结果封装
message Result {
bool success = 1; // 操作是否成功
string message = 2; // 错误信息或提示信息
}
// 用户信息
message User {
int32 id = 1; // 用户 ID
string name = 2; // 用户名称
string email = 3; // 用户邮箱
}
// 请求参数
message GetUserRequest {
int32 user_id = 1; // 请求的用户 ID
}
// 响应体
message GetUserResponse {
Result result = 1; // 操作结果
User user = 2; // 用户信息
}
// 定义 RPC 服务
service UserService {
// 获取用户信息
rpc GetUser (GetUserRequest) returns (GetUserResponse);
// 更新用户信息
rpc UpdateUser (UpdateUserRequest) returns (UpdateUserResponse);
}
// 更新请求参数
message UpdateUserRequest {
User user = 1; // 要更新的用户信息
}
// 更新响应体
message UpdateUserResponse {
Result result = 1; // 操作结果
}
在代码中 使用message定义了很多结构体 user,result ,俩个接口对应的请求体和相关的响应体,类型 参数名=1 这个数字是标识 只需要当前结构体唯一就好了
service 表示是一个rpc服务 rpc申明的 就是生成代码后的接口和返回值
使用插件生成
命令如下:
protoc -I . --go_out=paths=source_relative:. --go-grpc_out=paths=source_relative:. user.proto
生成模块代码
可以看到服务和客户端申明 以及方法接口申明
解释 protoc
指令
这个命令用于将 Protocol Buffers (.proto
) 文件编译成 Go 语言代码,并生成 gRPC 相关的代码。让我们分解一下这个命令的各个部分:
protoc
: Protocol Buffers 编译器命令行工具,用于将.proto
文件转换成各种编程语言的代码。-I .
: 指定了protoc
编译器的导入路径。在这里,.
表示当前目录。protoc
会在这个路径下查找.proto
文件中的依赖文件。--go_out=paths=source_relative:.
: 指定了如何生成 Go 语言的代码。--go_out
表示生成的代码类型是 Protocol Buffers 消息和服务的基础 Go 代码。paths=source_relative
表示生成的 Go 文件将相对于.proto
文件的位置。这意味着生成的文件将保留与.proto
文件相对的路径结构。.
表示生成的代码将放在当前目录。
--go-grpc_out=paths=source_relative:.
: 指定了如何生成 gRPC 的代码。--go-grpc_out
表示生成的代码类型是 gRPC 的 Go 代码,例如服务的客户端和服务器接口。paths=source_relative
和.
的含义与--go_out
中的相同。
user.proto
: 要编译的 Protocol Buffers 文件名。
生成的俩个文件
-
user.pb.go
包含了消息和数据结构的定义。 -
user_grpc.pb.go
包含了 gRPC 服务的接口和客户端代码。
实现生成的rpc服务接口 构造微服务
这里的服务端就是用原生http 模拟 ,当然也可以使用gin 和go-zero
服务端
package main
import (
"context"
"fmt"
//生成的代码包
"go-grpc-fast/protos/hello_grpc"
"go-grpc-fast/protos/user"
"google.golang.org/grpc"
"google.golang.org/grpc/grpclog"
"net"
)
// HelloServer 得有一个结构体,需要实现这个服务的全部方法,叫什么名字不重要
type HelloServer struct {
hello_grpc.UnimplementedHelloServiceServer
}
type UserrpcServer struct {
user.UnimplementedUserServiceServer
}
func (s *UserrpcServer) UpdateUser(ctx context.Context, in *user.UpdateUserRequest) (*user.UpdateUserResponse, error) {
fmt.Println("user update request received")
fmt.Println(in)
return nil, nil
}
// GetUser 实现了 UserServiceServer 接口中的 GetUser 方法
func (s *UserrpcServer) GetUser(ctx context.Context, in *user.GetUserRequest) (*user.GetUserResponse, error) {
// 这里你可以根据业务逻辑处理请求
fmt.Println("Received request for user ID:", in.UserId)
// 创建并返回响应
return &user.GetUserResponse{
Result: &user.Result{
Success: true,
Message: "成功查找用户",
},
User: &user.User{
Id: in.UserId,
Name: "乔治",
Email: "john.doe@example.com",
},
}, nil
}
func (c *HelloServer) SayHello(ctx context.Context, request *hello_grpc.HelloRequest) (*hello_grpc.HelloResponse, error) {
fmt.Println("入参:", request.Name, request.Message)
return &hello_grpc.HelloResponse{
Name: "server",
Message: "hello " + request.Name,
}, nil
}
func main() {
// 监听端口
listen, err := net.Listen("tcp", ":4040")
if err != nil {
grpclog.Fatalf("Failed to listen: %v", err)
}
// 创建一个gRPC服务器实例。
s := grpc.NewServer()
server := HelloServer{}
userServer := new(UserrpcServer)
// 将server结构体注册为gRPC服务。
hello_grpc.RegisterHelloServiceServer(s, &server)
user.RegisterUserServiceServer(s, userServer)
fmt.Println("启动一个搭建rpc服务的程序,监听端口 :4040")
// 开始处理客户端请求。
err = s.Serve(listen)
}
这样就实现了服务端 响应体直接引入生成的go文件
客户端
import (
"context"
"fmt"
"go-grpc-fast/protos/hello_grpc"
"go-grpc-fast/protos/user"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"log"
)
func main() {
addr := ":4040"
// 使用 grpc.Dial 创建一个到指定地址的 gRPC 连接。
// 此处使用不安全的证书来实现 SSL/TLS 连接
conn, err := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf(fmt.Sprintf("grpc connect addr [%s] 连接失败 %s", addr, err))
}
defer conn.Close()
// 初始化客户端
client := hello_grpc.NewHelloServiceClient(conn)
userServiceClient := user.NewUserServiceClient(conn)
result, err := client.SayHello(context.Background(), &hello_grpc.HelloRequest{
Name: "你好",
Message: "hello",
})
//用户客户端发起请求
if getUser, err := userServiceClient.GetUser(context.Background(), &user.GetUserRequest{
UserId: 454545,
}); err == nil {
fmt.Println(getUser)
}
fmt.Println(result, err)
}
使用protobuf 生成的实列化注入rpc连接
userServiceClient := user.NewUserServiceClient(conn)
服务端 为什么要内嵌一个接口
type UserrpcServer struct {
user.UnimplementedUserServiceServer
}
在 gRPC 中,服务端实现需要嵌入一个自动生成的基类类型,称为 UnimplementedServer。这个基类类型是由 protoc 工具生成的,目的是为了简化服务的实现,并提供一些便利功能。以下是详细解释:
UnimplementedServer 的作用
- 简化实现:
UnimplementedServer 是由 protoc 生成的一个空接口,它包含了所有 RPC 方法的默认实现,通常是空的实现。
当你创建一个服务实现时,你可以嵌入这个类型,从而避免自己手动实现所有方法的空方法,减少样板代码。
- 向后兼容性:
如果 gRPC 的库更新或者 .proto 文件中的服务定义被修改(例如,添加了新的方法),UnimplementedServer 类型会自动提供这些新方法的空实现。这样,当你的服务实现结构体嵌入了这个类型时,你的代码可以在不修改的情况下兼容未来的更新。
如果将来你的服务接口增加了新方法,UnimplementedServer 会自动提供这些新方法的空实现,从而避免了编译错误。
- 提供默认实现:
UnimplementedServer 的存在意味着你可以只实现你关注的那些方法,而不需要提供所有方法的实现。对于那些尚未实现的方法,gRPC 框架会调用默认的空实现,而不会因缺少实现而崩溃。
所以使用grpc是很简单的 得益于protobuf ,grpc的性能和开发都是很指的学习的,就算是javer dubbo 官方也推荐这样实现
当然grpc 还可以生成其他语言的接口代码grpc官网
protobuf的编写规范
请查看go-zero微服务开发框架中的引用go-zero