您好,登录后才能下订单哦!
密码登录
登录注册
点击 登录注册 即表示同意《亿速云用户服务条款》
# GO版gRPC开发方法是什么
## 前言
在当今微服务架构盛行的时代,gRPC作为高性能、跨语言的RPC框架已成为服务间通信的重要选择。Go语言凭借其简洁的语法、出色的并发模型和卓越的性能,成为gRPC开发的首选语言之一。本文将深入探讨如何使用Go语言进行gRPC开发,从基础概念到高级实践,全面解析GO版gRPC的开发方法。
## 一、gRPC基础概念
### 1.1 什么是gRPC
gRPC是Google开发的高性能、开源、通用的RPC框架,基于HTTP/2协议和Protocol Buffers(protobuf)序列化协议。其主要特点包括:
- **跨语言支持**:支持多种编程语言
- **高效的二进制编码**:使用protobuf进行数据序列化
- **基于HTTP/2**:支持双向流、头部压缩等特性
- **强类型服务定义**:通过.proto文件明确定义服务接口
### 1.2 gRPC vs REST
| 特性 | gRPC | REST |
|---------------|-----------------------|--------------------|
| 协议 | HTTP/2 | HTTP/1.1 |
| 数据格式 | Protocol Buffers | JSON/XML |
| 性能 | 高 | 中等 |
| 流式支持 | 支持 | 有限支持 |
| 浏览器支持 | 有限(需要gRPC-Web) | 完全支持 |
## 二、GO版gRPC开发环境搭建
### 2.1 安装必要工具
```bash
# 安装Protocol Buffer编译器
brew install protobuf # macOS
apt-get install protobuf-compiler # Ubuntu
# 安装Go插件
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
# 验证安装
protoc --version
/mygrpcproject
├── proto/ # .proto文件目录
│ └── helloworld.proto
├── client/ # 客户端代码
│ └── main.go
├── server/ # 服务端代码
│ └── main.go
├── go.mod # Go模块文件
└── go.sum
syntax = "proto3";
option go_package = ".;helloworld";
package helloworld;
// 定义服务
service Greeter {
// 一元RPC
rpc SayHello (HelloRequest) returns (HelloReply) {}
// 服务端流式RPC
rpc LotsOfReplies (HelloRequest) returns (stream HelloReply) {}
// 客户端流式RPC
rpc LotsOfGreetings (stream HelloRequest) returns (HelloReply) {}
// 双向流式RPC
rpc BidiHello (stream HelloRequest) returns (stream HelloReply) {}
}
// 请求消息
message HelloRequest {
string name = 1;
}
// 响应消息
message HelloReply {
string message = 1;
}
protoc --go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
proto/helloworld.proto
执行后将生成:
- helloworld.pb.go
:包含消息结构的序列化代码
- helloworld_grpc.pb.go
:包含服务端和客户端代码
package main
import (
"context"
"log"
"net"
"google.golang.org/grpc"
pb "path/to/your/package/proto/helloworld"
)
type server struct {
pb.UnimplementedGreeterServer
}
// 实现SayHello方法
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", ":50051")
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)
}
}
// 服务端流式RPC实现
func (s *server) LotsOfReplies(in *pb.HelloRequest, stream pb.Greeter_LotsOfRepliesServer) error {
for i := 0; i < 5; i++ {
if err := stream.Send(&pb.HelloReply{
Message: fmt.Sprintf("Hello %s %d", in.GetName(), i),
}); err != nil {
return err
}
}
return nil
}
// 双向流式RPC实现
func (s *server) BidiHello(stream pb.Greeter_BidiHelloServer) error {
for {
in, err := stream.Recv()
if err == io.EOF {
return nil
}
if err != nil {
return err
}
if err := stream.Send(&pb.HelloReply{
Message: "Hello " + in.GetName(),
}); err != nil {
return err
}
}
}
package main
import (
"context"
"log"
"time"
"google.golang.org/grpc"
pb "path/to/your/package/proto/helloworld"
)
func main() {
conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewGreeterClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: "World"})
if err != nil {
log.Fatalf("could not greet: %v", err)
}
log.Printf("Greeting: %s", r.GetMessage())
}
// 服务端流式调用
func callLotsOfReplies(c pb.GreeterClient) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
stream, err := c.LotsOfReplies(ctx, &pb.HelloRequest{Name: "World"})
if err != nil {
log.Fatalf("error calling LotsOfReplies: %v", err)
}
for {
reply, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
log.Fatalf("error receiving: %v", err)
}
log.Printf("Reply: %s", reply.GetMessage())
}
}
// 双向流式调用
func callBidiHello(c pb.GreeterClient) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
stream, err := c.BidiHello(ctx)
if err != nil {
log.Fatalf("error calling BidiHello: %v", err)
}
waitc := make(chan struct{})
// 接收协程
go func() {
for {
in, err := stream.Recv()
if err == io.EOF {
close(waitc)
return
}
if err != nil {
log.Fatalf("Failed to receive: %v", err)
}
log.Printf("Received: %s", in.GetMessage())
}
}()
// 发送协程
names := []string{"Alice", "Bob", "Charlie"}
for _, name := range names {
if err := stream.Send(&pb.HelloRequest{Name: name}); err != nil {
log.Fatalf("Failed to send: %v", err)
}
}
stream.CloseSend()
<-waitc
}
// 客户端拦截器
func unaryClientInterceptor(ctx context.Context, method string, req, reply interface{},
cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
// 前置处理
start := time.Now()
// 调用RPC方法
err := invoker(ctx, method, req, reply, cc, opts...)
// 后置处理
log.Printf("Invoked RPC method=%s; Duration=%s; Error=%v", method, time.Since(start), err)
return err
}
// 服务端拦截器
func unaryServerInterceptor(ctx context.Context, req interface{},
info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
// 前置处理
start := time.Now()
// 调用处理器
resp, err := handler(ctx, req)
// 后置处理
log.Printf("Invoked RPC method=%s; Duration=%s; Error=%v",
info.FullMethod, time.Since(start), err)
return resp, err
}
// 使用拦截器
func main() {
// 客户端使用
conn, err := grpc.Dial(address,
grpc.WithUnaryInterceptor(unaryClientInterceptor),
grpc.WithStreamInterceptor(streamClientInterceptor))
// 服务端使用
s := grpc.NewServer(
grpc.UnaryInterceptor(unaryServerInterceptor),
grpc.StreamInterceptor(streamServerInterceptor))
}
// 服务端返回错误
func (s *server) SomeMethod(ctx context.Context, req *pb.Request) (*pb.Response, error) {
if req.Value < 0 {
return nil, status.Errorf(
codes.InvalidArgument,
"Value cannot be negative: %d", req.Value)
}
// ...正常处理...
}
// 客户端处理错误
resp, err := client.SomeMethod(ctx, &pb.Request{Value: -1})
if err != nil {
if st, ok := status.FromError(err); ok {
switch st.Code() {
case codes.InvalidArgument:
log.Printf("Invalid argument: %v", st.Message())
case codes.DeadlineExceeded:
log.Printf("Timeout!")
default:
log.Printf("RPC failed: %v", err)
}
} else {
log.Printf("Non-gRPC error: %v", err)
}
}
// 客户端发送元数据
md := metadata.Pairs(
"authorization", "Bearer some-token",
"client-version", "1.0.0",
)
ctx := metadata.NewOutgoingContext(context.Background(), md)
// 服务端接收元数据
func (s *server) SomeMethod(ctx context.Context, req *pb.Request) (*pb.Response, error) {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, status.Errorf(codes.Unauthenticated, "metadata missing")
}
tokens := md.Get("authorization")
if len(tokens) == 0 {
return nil, status.Errorf(codes.Unauthenticated, "authorization token missing")
}
// ...验证token...
}
// 客户端负载均衡
conn, err := grpc.Dial(
"dns:///my-service.example.com",
grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy":"round_robin"}`),
grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{})),
)
// 服务发现集成
resolverBuilder := mycustomresolver.NewBuilder()
conn, err := grpc.Dial(
resolverBuilder.Scheme()+"://authority/my-service",
grpc.WithResolvers(resolverBuilder),
grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy":"round_robin"}`),
)
// 创建带连接池的客户端
var (
connPool *grpc.ClientConn
once sync.Once
)
func getClient() pb.GreeterClient {
once.Do(func() {
var err error
connPool, err = grpc.Dial("localhost:50051",
grpc.WithInsecure(),
grpc.WithKeepaliveParams(keepalive.ClientParameters{
Time: 30 * time.Second,
Timeout: 10 * time.Second,
PermitWithoutStream: true,
}))
if err != nil {
log.Fatalf("did not connect: %v", err)
}
})
return pb.NewGreeterClient(connPool)
}
// 服务端配置
s := grpc.NewServer(
grpc.MaxRecvMsgSize(1024*1024*10), // 10MB
grpc.MaxSendMsgSize(1024*1024*10),
)
// 客户端配置
conn, err := grpc.Dial(address,
grpc.WithDefaultCallOptions(
grpc.MaxCallRecvMsgSize(1024*1024*10),
grpc.MaxCallSendMsgSize(1024*1024*10),
))
// 服务端启用压缩
s := grpc.NewServer(
grpc.RPCCompressor(grpc.NewGZIPCompressor()),
grpc.RPCDecompressor(grpc.NewGZIPDecompressor()),
)
// 客户端调用时压缩
resp, err := client.SomeMethod(ctx, req,
grpc.UseCompressor("gzip"))
import (
"testing"
"google.golang.org/grpc/test/bufconn"
)
const bufSize = 1024 * 1024
func TestSayHello(t *testing.T) {
// 创建内存监听器
lis := bufconn.Listen(bufSize)
s := grpc.NewServer()
pb.RegisterGreeterServer(s, &server{})
go func() {
if err := s.Serve(lis); err != nil {
t.Fatalf("Server exited with error: %v", err)
}
}()
defer s.Stop()
// 创建客户端连接
ctx := context.Background()
conn, err := grpc.DialContext(ctx, "bufnet",
grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) {
return lis.Dial()
}),
grpc.WithInsecure())
if err != nil {
t.Fatalf("Failed to dial bufnet: %v", err)
}
defer conn.Close()
client := pb.NewGreeterClient(conn)
resp, err := client.SayHello(ctx, &pb.HelloRequest{Name: "test"})
if err != nil {
t.Fatalf("SayHello failed: %v", err)
}
expected := "Hello test"
if resp.GetMessage() != expected {
t.Errorf("Expected %q, got %q", expected, resp.GetMessage())
}
}
# 安装grpcurl
go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest
# 列出服务
grpcurl -plaintext localhost:50051 list
# 调用方法
grpcurl -plaintext -d '{"name": "World"}' localhost:50051 helloworld.Greeter/SayHello
BloomRPC是一个类似Postman的gRPC GUI客户端,支持: - 导入.proto文件 - 可视化调用方法 - 查看请求/响应 - 保存历史记录
// health.proto
service Health {
rpc Check(HealthCheckRequest) returns (HealthCheckResponse);
rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse);
}
// 服务端实现
import "google.golang.org/grpc/health/grpc_health_v1"
healthServer := health.NewServer()
healthServer.SetServingStatus("", grpc_health_v1.HealthCheckResponse_SERVING)
grpc_health_v1.RegisterHealthServer(s, healthServer)
import "github.com/grpc-ecosystem/go-grpc-prometheus"
// 注册指标
grpc_prometheus.EnableHandlingTimeHistogram()
grpc_prometheus.Register(s)
// 暴露/metrics端点
http.Handle("/metrics", promhttp.Handler())
go http.ListenAndServe(":9090", nil)
”`yaml
apiVersion: apps/v1 kind: Deployment metadata: name: grpc-server spec: replicas: 3 selector: matchLabels: app: grpc-server template: metadata: labels:
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。