从零开始的RPC(四):初入微服务
2026/1/31大约 2 分钟
gRPC构建简易微服务
代码实现查询,客户端向服务端查询用户的信息
定义消息
syntax = "proto3";
package query;
option go_package = "./querypb";
// 用户查询
message UserRequest {
string name = 1;
}
message UserResponse {
int32 id = 1;
string name = 2;
int32 age = 3;
repeated string hobbies = 4;
}
// service定义方法
service UserInfoService {
rpc GetUserInfo(UserRequest) returns (UserResponse);
}生成.go文件
protoc -I . --go_out=. --go-grpc_out=. ./query.proto编写服务端
package main
import (
"context"
"errors"
"from-rpc-to-microservice/querypb"
"log"
"net"
"sync"
"google.golang.org/grpc"
)
var (
ErrUsernameNotExist = errors.New("用户名不存在")
)
// 1.需要监听
// 2.需要实例化gRPC服务端
// 3.在gRPC上注册微服务
// 4.启动服务端
// 实现服务端
type UserInfoService struct {
// 今时不同往日, 现在是要手动嵌入这个结构体来继承那个空方法了
querypb.UnimplementedUserInfoServiceServer
}
func (u UserInfoService) GetUserInfo(ctx context.Context, request *querypb.UserRequest) (*querypb.UserResponse, error) {
name := request.Name
if v, exists := users[name]; exists {
return &v, nil
} else {
return nil, ErrUsernameNotExist
}
}
// 服务端实例
var u = UserInfoService{}
// 模拟数据库
var users = map[string]querypb.UserResponse{
"Tom": {
Id: 1,
Name: "Tom",
Hobbies: []string{"coding", "gaming"},
},
"Jerry": {
Id: 2,
Name: "Jerry",
Hobbies: []string{"gaming", "drawing"},
},
"Mike": {
Id: 3,
Name: "Mike",
Hobbies: []string{"coding", "swimming"},
},
}
func runServer(wg *sync.WaitGroup) {
defer wg.Done()
addr := "127.0.0.1:8080"
listener, err := net.Listen("tcp", addr)
if err != nil {
log.Panicf("TCP监听失败: %v", err)
}
log.Printf("已占用地址: %s", addr)
s := grpc.NewServer() // 实例化gRPC Server
querypb.RegisterUserInfoServiceServer(s, &u)
if err := s.Serve(listener); err != nil {
log.Panicf("启动服务失败: %v", err)
}
}编写客户端
package main
import (
"context"
"from-rpc-to-microservice/querypb"
"log"
"sync"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
// 1.连接服务端
// 2.实例gRPC客户端
// 3.调用
func runClient(wg *sync.WaitGroup) {
defer wg.Done()
// grpc.WithInsecure已经被废弃
// grpc.Dial也被废弃了
conn, err := grpc.NewClient("127.0.0.1:8080", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Panicf("连接服务端失败: %v", err)
}
defer func() {
_ = conn.Close()
log.Printf("已关闭连接")
}()
// 实例化客户端
client := querypb.NewUserInfoServiceClient(conn)
req := &querypb.UserRequest{
Name: "Kirk",
}
resp, err := client.GetUserInfo(context.Background(), req)
if err != nil {
log.Panicf("调用服务端失败: %v", err)
}
log.Printf("服务端返回: %v", resp)
}
gRPC API 编写练习
optional字段测试
注
optional: (recommended) An optional field is in one of two possible states:
- the field is set, and contains a value that was explicitly set or parsed from the wire. It will be serialized to the wire.
- the field is unset, and will return the default value. It will not be serialized to the wire.


不传值的时候可以是nil(最好把数据模型里的类型也换成*类型,这样Gorm扫描时可以忽略nil值);有传值的时候才能被序列化
业务逻辑根据自己需要编写,反正只是测试