gRPC与Protobuf集成详解—从服务定义到跨语言通信(含Go和Java示例)

在前几篇文章中,我们已经掌握了 Protobuf 的基础语法、高级特性和序列化反序列化操作。本篇文章将深入讲解 gRPC 与 Protobuf 的集成,重点介绍如何通过 .proto 文件定义服务接口,并在 Go 和 Java 中实现 gRPC 服务与客户端的完整交互流程。我们将通过详细代码示例分步解析,帮助你彻底掌握微服务架构中的通信设计。


一、gRPC 简介与核心概念

1. 什么是 gRPC?

gRPC 是一个高性能、开源的远程过程调用(RPC)框架,基于 HTTP/2 协议Protobuf 数据格式 构建。它支持多种语言,并提供了同步/异步调用流式通信等特性。

2. gRPC 的核心优势

特性 描述
高效通信 基于二进制协议(Protobuf),比 JSON 更快、更小
多语言支持 支持 Go、Java、Python、C++、Rust 等
双向流式通信 支持客户端/服务端流式数据传输
自动代码生成 通过 .proto 文件自动生成客户端和服务端代码
强大的工具链 提供调试工具(如 grpcurl)、插件系统

二、通过 .proto 定义 gRPC 服务

1. 示例 .proto 文件

ini 复制代码
syntax = "proto3";

package user;//新版本有了下面的option go_package 这里的pacage就可以去掉了(当然留着也不影响)
option go_package = "/user;user"; // 指定生成的 Go 包路径(生成源码的路径和包名,前面是路径后面是包名,可以自己定义)
//option go_package = ".;user"; //这个可以生成在当前目录下

// 定义服务接口
service UserService {
    // 1. 单向调用(Unary RPC)
    rpc GetUser (GetUserRequest) returns (UserResponse);

    // 2. 服务端流式调用(Server Streaming)
    rpc ListUsers (ListUsersRequest) returns (stream UserResponse);

    // 3. 客户端流式调用(Client Streaming)
    rpc CreateUsers (stream CreateUserRequest) returns (CreateUsersResponse);

    // 4. 双向流式调用(Bidirectional Streaming)
    rpc UpdateUsers (stream UpdateUserRequest) returns (stream UserResponse);
}

// 消息定义
message GetUserRequest {
    int32 id = 1;
}

message UserResponse {
    int32 id = 1;
    string name = 2;
    string email = 3;
}

message ListUsersRequest {
    string filter = 1;
}

message CreateUserRequest {
    string name = 1;
    string email = 2;
}

message CreateUsersResponse {
    int32 count = 1;
}

message UpdateUserRequest {
    int32 id = 1;
    string name = 2;
}

要注意下面这里有了变化(以后会讲解为什么要用option go_package):

package user;//新版本有了下面的option go_package 这里的pacage就可以去掉了(当然留着也不影响)

option go_package = "/user;user"; // 指定生成的 Go 包路径(生成源码的路径和包名,前面是路径后面是包名,可以自己定义)

//option go_package = ".;user"; //这个可以生成在当前目录下


三、生成 gRPC 代码

1. 安装 gRPC 工具

go 复制代码
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

Java

ini 复制代码
protoc --java_out=. \
       --grpc-java_out=. \
       --plugin=protoc-gen-grpc-java=protoc-gen-grpc-java \
       user.proto

2. 生成代码命令

Go

ini 复制代码
protoc --go_out=. --go-grpc_out=. user.proto

//protoc --go_out=. --go-grpc_out=. user.proto
//这个命令使用了两个输出插件:--go_out=. 和 --go-grpc_out=.。它分别调用了 Go 相关的 Protobuf 插件和 gRPC Go 插件来生成对应的目标文件。其中:
//--go_out=. 表示使用 Go 的 Protobuf 编译插件生成对应的 Go 文件。
//--go-grpc_out=. 表示使用 Go 的 gRPC 编译插件生成 gRPC 服务相关的 Go 文件。

Java

css 复制代码
protoc --java_out=. --grpc-java_out=. user.proto

四、Go 实现 gRPC 服务端与客户端

1. 服务端代码详解

go 复制代码
package main

import (
	"context"
	"fmt"
	"log"
	"net"

	pb "./user_go_proto"
	"google.golang.org/grpc"
)

type userService struct {
	pb.UnimplementedUserServiceServer
}

// 单向调用
func (s *userService) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.UserResponse, error) {
	return &pb.UserResponse{
		Id:    req.Id,
		Name:  "Alice",
		Email: "alice@example.com",
	}, nil
}

// 服务端流式调用
func (s *userService) ListUsers(req *pb.ListUsersRequest, stream pb.UserService_ListUsersServer) error {
	users := []*pb.UserResponse{
		{Id: 1, Name: "Alice", Email: "alice@example.com"},
		{Id: 2, Name: "Bob", Email: "bob@example.com"},
	}
	for _, user := range users {
		if err := stream.Send(user); err != nil {
			return err
		}
	}
	return nil
}

// 客户端流式调用
func (s *userService) CreateUsers(stream pb.UserService_CreateUsersServer) error {
	count := 0
	for {
		req, err := stream.Recv()
		if err == io.EOF {
			break
		}
		if err != nil {
			return err
		}
		count++
	}
	return stream.SendAndClose(&pb.CreateUsersResponse{Count: int32(count)})
}

// 双向流式调用
func (s *userService) UpdateUsers(stream pb.UserService_UpdateUsersServer) error {
	for {
		req, err := stream.Recv()
		if err == io.EOF {
			break
		}
		if err != nil {
			return err
		}
		resp := &pb.UserResponse{
			Id:   req.Id,
			Name: req.Name,
		}
		if err := stream.Send(resp); err != nil {
			return err
		}
	}
	return nil
}

func main() {
	lis, err := net.Listen("tcp", ":50051")
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}
	s := grpc.NewServer()
	pb.RegisterUserServiceServer(s, &userService{})
	log.Printf("Server listening at %v", lis.Addr())
	if err := s.Serve(lis); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}
}

代码解析

  • 服务端实现 :通过 pb.RegisterUserServiceServer 注册服务。
  • 流式处理 :通过 stream 接口处理双向通信。
  • 错误处理 :捕获 io.EOF 结束流式调用。

2. 客户端代码详解

go 复制代码
package main

import (
	"context"
	"fmt"
	"log"

	pb "./user_go_proto"
	"google.golang.org/grpc"
)

func main() {
	conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
	if err != nil {
		log.Fatalf("did not connect: %v", err)
	}
	defer conn.Close()
	client := pb.NewUserServiceClient(conn)

	// 单向调用
	resp, err := client.GetUser(context.Background(), &pb.GetUserRequest{Id: 1})
	if err != nil {
		log.Fatalf("could not get user: %v", err)
	}
	fmt.Printf("User: %v\n", resp)

	// 服务端流式调用
	stream, err := client.ListUsers(context.Background(), &pb.ListUsersRequest{Filter: "IT"})
	if err != nil {
		log.Fatalf("could not list users: %v", err)
	}
	for {
		user, err := stream.Recv()
		if err == io.EOF {
			break
		}
		if err != nil {
			log.Fatalf("error receiving user: %v", err)
		}
		fmt.Printf("Received: %v\n", user)
	}

	// 客户端流式调用
	stream2, err := client.CreateUsers(context.Background())
	if err != nil {
		log.Fatalf("could not create users: %v", err)
	}
	for i := 0; i < 3; i++ {
		if err := stream2.Send(&pb.CreateUserRequest{
			Name:  fmt.Sprintf("User %d", i),
			Email: fmt.Sprintf("user%d@example.com", i),
		}); err != nil {
			log.Fatalf("error sending user: %v", err)
		}
	}
	resp2, err := stream2.CloseAndRecv()
	if err != nil {
		log.Fatalf("error closing stream: %v", err)
	}
	fmt.Printf("Created %d users\n", resp2.Count)

	// 双向流式调用
	stream3, err := client.UpdateUsers(context.Background())
	if err != nil {
		log.Fatalf("could not update users: %v", err)
	}
	for i := 0; i < 3; i++ {
		if err := stream3.Send(&pb.UpdateUserRequest{
			Id:   int32(i),
			Name: fmt.Sprintf("Updated User %d", i),
		}); err != nil {
			log.Fatalf("error sending update: %v", err)
		}
		resp3, err := stream3.Recv()
		if err != nil {
			log.Fatalf("error receiving update: %v", err)
		}
		fmt.Printf("Updated: %v\n", resp3)
	}
}

代码解析

  • 连接建立 :通过 grpc.Dial 连接服务端。
  • 流式调用 :通过 stream.Send()stream.Recv() 实现双向通信。
  • 错误处理 :捕获 io.EOF 结束流式调用。

五、Java 实现 gRPC 服务端与客户端

1. 服务端代码详解

java 复制代码
import user.UserServiceGrpc;
import user.GetUserRequest;
import user.UserResponse;
import user.ListUsersRequest;
import user.CreateUserRequest;
import user.CreateUsersResponse;
import user.UpdateUserRequest;

import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;

import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class UserServiceServer {
    public static void main(String[] args) throws IOException, InterruptedException {
        final Server server = ServerBuilder.forPort(50051)
                .addService(new UserServiceImpl())
                .build();

        server.start();
        System.out.println("Server started at port 50051");

        final CountDownLatch latch = new CountDownLatch(1);
        server.awaitTermination();
    }

    static class UserServiceImpl extends UserServiceGrpc.UserServiceImplBase {
        // 单向调用
        @Override
        public void getUser(GetUserRequest request, StreamObserver<UserResponse> responseObserver) {
            UserResponse response = UserResponse.newBuilder()
                    .setId(request.getId())
                    .setName("Alice")
                    .setEmail("alice@example.com")
                    .build();
            responseObserver.onNext(response);
            responseObserver.onCompleted();
        }

        // 服务端流式调用
        @Override
        public void listUsers(ListUsersRequest request, StreamObserver<UserResponse> responseObserver) {
            UserResponse user1 = UserResponse.newBuilder()
                    .setId(1)
                    .setName("Alice")
                    .setEmail("alice@example.com")
                    .build();
            UserResponse user2 = UserResponse.newBuilder()
                    .setId(2)
                    .setName("Bob")
                    .setEmail("bob@example.com")
                    .build();
            responseObserver.onNext(user1);
            responseObserver.onNext(user2);
            responseObserver.onCompleted();
        }

        // 客户端流式调用
        @Override
        public StreamObserver<CreateUserRequest> createUsers(StreamObserver<CreateUsersResponse> responseObserver) {
            return new StreamObserver<>() {
                int count = 0;

                @Override
                public void onNext(CreateUserRequest request) {
                    count++;
                }

                @Override
                public void onError(Throwable throwable) {
                    throwable.printStackTrace();
                }

                @Override
                public void onCompleted() {
                    CreateUsersResponse response = CreateUsersResponse.newBuilder()
                            .setCount(count)
                            .build();
                    responseObserver.onNext(response);
                    responseObserver.onCompleted();
                }
            };
        }

        // 双向流式调用
        @Override
        public StreamObserver<UpdateUserRequest> updateUsers(StreamObserver<UserResponse> responseObserver) {
            return new StreamObserver<>() {
                @Override
                public void onNext(UpdateUserRequest request) {
                    UserResponse response = UserResponse.newBuilder()
                            .setId(request.getId())
                            .setName(request.getName())
                            .build();
                    responseObserver.onNext(response);
                }

                @Override
                public void onError(Throwable throwable) {
                    throwable.printStackTrace();
                }

                @Override
                public void onCompleted() {
                    responseObserver.onCompleted();
                }
            };
        }
    }
}

代码解析

  • 服务端实现 :通过继承 UserServiceGrpc.UserServiceImplBase 实现接口。
  • 流式处理 :通过 StreamObserver 处理双向通信。
  • 错误处理 :通过 onError 捕获异常。

2. 客户端代码详解

java 复制代码
import user.UserServiceGrpc;
import user.GetUserRequest;
import user.UserResponse;
import user.ListUsersRequest;
import user.CreateUserRequest;
import user.CreateUsersResponse;
import user.UpdateUserRequest;

import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.StatusRuntimeException;

import java.util.concurrent.TimeUnit;

public class UserServiceClient {
    public static void main(String[] args) {
        ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 50051)
                .usePlaintext()
                .build();

        UserServiceGrpc.UserServiceBlockingStub blockingStub = UserServiceGrpc.newBlockingStub(channel);

        // 单向调用
        GetUserRequest request = GetUserRequest.newBuilder().setId(1).build();
        try {
            UserResponse response = blockingStub.getUser(request);
            System.out.println("User: " + response.getName());
        } catch (StatusRuntimeException e) {
            e.printStackTrace();
        }

        // 服务端流式调用
        ListUsersRequest listRequest = ListUsersRequest.newBuilder().setFilter("IT").build();
        UserServiceGrpc.UserServiceStub asyncStub = UserServiceGrpc.newStub(channel);
        asyncStub.listUsers(listRequest, new StreamObserver<>() {
            @Override
            public void onNext(UserResponse user) {
                System.out.println("Received: " + user.getName());
            }

            @Override
            public void onError(Throwable throwable) {
                throwable.printStackTrace();
            }

            @Override
            public void onCompleted() {
                System.out.println("Stream completed.");
            }
        });

        // 客户端流式调用
        UserServiceGrpc.UserServiceStub createStub = UserServiceGrpc.newStub(channel);
        createStub.createUsers(new StreamObserver<>() {
            @Override
            public void onNext(CreateUsersResponse response) {
                System.out.println("Created " + response.getCount() + " users");
            }

            @Override
            public void onError(Throwable throwable) {
                throwable.printStackTrace();
            }

            @Override
            public void onCompleted() {
                System.out.println("Create stream completed.");
            }
        }).forEachRemaining(user -> {
            if (user != null) {
                System.out.println("Sending: " + user.getName());
            }
        });

        // 双向流式调用
        UserServiceGrpc.UserServiceStub updateStub = UserServiceGrpc.newStub(channel);
        StreamObserver<UpdateUserRequest> requestStream = updateStub.updateUsers(new StreamObserver<>() {
            @Override
            public void onNext(UserResponse response) {
                System.out.println("Updated: " + response.getName());
            }

            @Override
            public void onError(Throwable throwable) {
                throwable.printStackTrace();
            }

            @Override
            public void onCompleted() {
                System.out.println("Update stream completed.");
            }
        });

        for (int i = 0; i < 3; i++) {
            UpdateUserRequest updateRequest = UpdateUserRequest.newBuilder()
                    .setId(i)
                    .setName("Updated User " + i)
                    .build();
            requestStream.onNext(updateRequest);
        }
        requestStream.onCompleted();

        try {
            channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

代码解析

  • 连接建立 :通过 ManagedChannelBuilder 连接服务端。
  • 流式调用 :通过 StreamObserver 实现双向通信。
  • 错误处理 :通过 onError 捕获异常。

六、多语言交互的最佳实践

1. 保持 .proto 文件统一

  • 所有语言共享同一个 .proto 文件,确保接口定义一致。
  • 使用 protoc 生成对应语言的代码。

2. 版本控制

  • .proto 文件中添加版本注释:

    ini 复制代码
    // Version 1.0.0
    message User {
        string name = 1;
    }

3. 依赖管理

  • 使用 go modMaven 管理依赖,确保不同语言的代码版本一致。

注意:

这篇文章中使用的Go和Java 实现 gRPC 服务端与客户端的例子是二者分开用的,而不是混合语言,其实在这里我更想做的是Go和Java放在一起使用,比如Go做服务端,Java做客户端。原因是我觉得Go更适合grpc,所以大家着重看Go的讲解即可。如果要混合的话也是以Go为主,Java为辅。

这次没有使用多语言的原因是,突然混合在一起的话怕大家不好理解,我在其他文章中也有讲解跨语言使用的例子,大家有兴趣的可以去看看。


七、总结

在本文中,我们详细讲解了 gRPC 与 Protobuf 的深度集成,包括:

  1. 通过 .proto 文件定义服务接口
  2. 在 Go 和 Java 中实现服务端与客户端
  3. 单向、流式通信的完整代码示例
  4. 多语言交互的最佳实践

通过这些内容,你已经能够构建高性能、可扩展的微服务系统,并在不同语言之间实现无缝通信。gRPC 与 Protobuf 的结合是现代分布式系统的基石,希望这篇文章能帮助你更自信地在项目中应用这些技术。

如果这篇文章对大家有帮助可以点赞关注,你的支持就是我的动力😊!

相关推荐
梦想很大很大1 天前
使用 Go + Gin + Fx 构建工程化后端服务模板(gin-app 实践)
前端·后端·go
lekami_兰1 天前
MySQL 长事务:藏在业务里的性能 “隐形杀手”
数据库·mysql·go·长事务
却尘1 天前
一篇小白也能看懂的 Go 字符串拼接 & Builder & cap 全家桶
后端·go
ん贤1 天前
一次批量删除引发的死锁,最终我选择不加锁
数据库·安全·go·死锁
mtngt112 天前
AI DDD重构实践
go
Minilinux20183 天前
Google ProtoBuf 简介
开发语言·google·protobuf·protobuf介绍
Grassto3 天前
12 go.sum 是如何保证依赖安全的?校验机制源码解析
安全·golang·go·哈希算法·go module
Grassto5 天前
11 Go Module 缓存机制详解
开发语言·缓存·golang·go·go module
程序设计实验室6 天前
2025年的最后一天,分享我使用go语言开发的电子书转换工具网站
go
我的golang之路果然有问题6 天前
使用 Hugo + GitHub Pages + PaperMod 主题 + Obsidian 搭建开发博客
golang·go·github·博客·个人开发·个人博客·hugo