微服务架构下,服务间的通信方式多样,REST适合跨语言、轻量级的外部调用,RPC则更适合内部服务间高性能的通信。Golang生态中有成熟的库可以分别实现这两种接口,且能将它们整合到同一个微服务实例中,避免维护多个服务实例的额外成本。

环境准备与依赖选择
实现REST接口可以选择gin框架,它性能优异、路由功能完善;实现RPC接口可以选择grpc,它是Google开源的高性能RPC框架,基于HTTP/2协议,支持多语言。首先初始化项目并安装依赖:
go mod init micro_service_demo go get -u github.com/gin-gonic/gin go get -u google.golang.org/grpc go get -u google.golang.org/protobuf
定义RPC服务协议
使用protobuf定义RPC服务的接口和数据结构,创建user.proto文件:
syntax = "proto3";
package user;
option go_package = "./user";
// 用户服务定义
service UserService {
// 获取用户信息RPC方法
rpc GetUser (GetUserRequest) returns (GetUserResponse);
}
// 请求参数
message GetUserRequest {
int64 user_id = 1;
}
// 响应参数
message GetUserResponse {
int64 user_id = 1;
string user_name = 2;
int32 age = 3;
}
执行以下命令生成对应的Golang代码:
protoc --go_out=. --go-grpc_out=. user.proto
实现RPC服务端逻辑
创建rpc_server.go文件,实现protobuf定义的接口:
package main
import (
"context"
"log"
"net"
"google.golang.org/grpc"
pb "micro_service_demo/user"
)
// 用户服务结构体
type UserServiceServer struct {
pb.UnimplementedUserServiceServer
}
// 实现GetUser方法
func (s *UserServiceServer) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.GetUserResponse, error) {
// 模拟查询数据库获取用户信息
return &pb.GetUserResponse{
UserId: req.UserId,
UserName: "张三",
Age: 25,
}, nil
}
// 启动RPC服务
func startRPCServer() {
listen, err := net.Listen("tcp", ":8081")
if err != nil {
log.Fatalf("RPC监听端口失败: %v", err)
}
server := grpc.NewServer()
pb.RegisterUserServiceServer(server, &UserServiceServer{})
log.Println("RPC服务启动,监听端口8081")
if err := server.Serve(listen); err != nil {
log.Fatalf("RPC服务启动失败: %v", err)
}
}
实现REST接口逻辑
创建rest_server.go文件,使用gin框架实现REST接口:
package main
import (
"log"
"net/http"
"github.com/gin-gonic/gin"
)
// 用户信息结构体,用于REST接口返回
type UserInfo struct {
UserID int64 `json:"user_id"`
UserName string `json:"user_name"`
Age int32 `json:"age"`
}
// 获取用户REST接口处理函数
func getUserHandler(c *gin.Context) {
// 获取路径参数
userID := c.Param("id")
// 模拟查询逻辑,实际场景可调用RPC客户端获取数据
c.JSON(http.StatusOK, UserInfo{
UserID: 1001,
UserName: "张三",
Age: 25,
})
}
// 启动REST服务
func startRESTServer() {
r := gin.Default()
// 定义REST路由
r.GET("/api/user/:id", getUserHandler)
log.Println("REST服务启动,监听端口8080")
if err := r.Run(":8080"); err != nil {
log.Fatalf("REST服务启动失败: %v", err)
}
}
整合两个服务启动
修改main.go文件,同时启动REST和RPC服务:
package main
import (
"log"
)
func main() {
// 启动RPC服务,使用goroutine避免阻塞
go startRPCServer()
// 启动REST服务
startRESTServer()
}
接口测试验证
测试REST接口,使用curl发送请求:
curl http://127.0.0.1:8080/api/user/1001
返回结果如下:
{"user_id":1001,"user_name":"张三","age":25}
测试RPC接口,需要编写简单的RPC客户端,创建rpc_client.go文件:
package main
import (
"context"
"log"
"time"
"google.golang.org/grpc"
pb "micro_service_demo/user"
)
func main() {
// 连接RPC服务
conn, err := grpc.Dial("127.0.0.1:8081", grpc.WithInsecure())
if err != nil {
log.Fatalf("连接RPC服务失败: %v", err)
}
defer conn.Close()
// 创建客户端
client := pb.NewUserServiceClient(conn)
// 设置超时
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
defer cancel()
// 调用RPC方法
resp, err := client.GetUser(ctx, &pb.GetUserRequest{UserId: 1001})
if err != nil {
log.Fatalf("调用RPC方法失败: %v", err)
}
log.Printf("RPC调用结果: 用户ID=%d, 用户名=%s, 年龄=%d", resp.UserId, resp.UserName, resp.Age)
}
运行客户端后会输出对应的用户信息,说明两种接口都正常工作。
注意事项
- 生产环境中RPC服务需要添加认证、限流、监控等中间件,避免服务被恶意调用
- REST接口的参数校验、错误处理需要完善,返回统一的错误格式方便前端处理
- 如果服务需要注册到服务发现组件,比如etcd、consul,可以在服务启动后添加对应的注册逻辑
- 两种接口的日志需要统一收集,方便后续问题排查