第86课:实战:微服务Demo
学习目标
完成本课学习后,你将能够:
- 理解微服务架构的基本概念和Go语言在其中的优势
- 使用gRPC定义和实现服务间通信
- 编写Dockerfile容器化微服务
- 构建包含多个服务的完整微服务系统
- 实现服务间的并发调用和错误处理
核心概念
什么是微服务?
想象一下,一个大型电商网站把所有功能都放在一个应用里(单体架构),就像一家杂货店,所有商品都摆在一起。而微服务架构就像一家大型购物中心,每个店铺(服务)专门经营一类商品,通过统一的通道相互连接。
Go在微服务中的优势
Go语言天生适合微服务开发:
- 高性能:编译型语言,执行效率高
- 并发支持:goroutine和channel简化并发编程
- 跨平台:一次编译,到处运行
- 标准库强大:net/http、encoding/json等开箱即用
技术栈选择
- 通信协议:gRPC(高效、强类型、跨语言)
- 容器化:Docker(标准化部署)
- 服务间通信:gRPC + Protocol Buffers
代码示例
我们将构建一个简单的用户服务Demo,包含用户创建和查询功能。
1. 定义Proto文件
// user.proto
syntax = "proto3";
package user;
option go_package = "./proto";
// 用户服务定义
service UserService {
rpc CreateUser (CreateUserRequest) returns (CreateUserResponse);
rpc GetUser (GetUserRequest) returns (User);
}
// 消息定义
message CreateUserRequest {
string name = 1;
string email = 2;
}
message CreateUserResponse {
string id = 1;
bool success = 2;
}
message GetUserRequest {
string id = 1;
}
message User {
string id = 1;
string name = 2;
string email = 3;
}
2. 生成Go代码
# 安装protoc-gen-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 --go_out=. --go-grpc_out=. user.proto
3. 实现服务端
// server/main.go
package main
import (
"context"
"fmt"
"log"
"net"
"sync"
"google.golang.org/grpc"
pb "你的项目路径/proto"
)
// UserServiceServer 实现用户服务
type UserServiceServer struct {
pb.UnimplementedUserServiceServer
users map[string]*pb.User
mu sync.RWMutex
}
// NewUserServiceServer 创建服务实例
func NewUserServiceServer() *UserServiceServer {
return &UserServiceServer{
users: make(map[string]*pb.User),
}
}
// CreateUser 实现创建用户RPC
func (s *UserServiceServer) CreateUser(ctx context.Context, req *CreateUserRequest) (*CreateUserResponse, error) {
s.mu.Lock()
defer s.mu.Unlock()
// 生成简单ID(实际生产环境应使用UUID)
id := fmt.Sprintf("user_%d", len(s.users)+1)
// 创建用户对象
user := &pb.User{
Id: id,
Name: req.Name,
Email: req.Email,
}
// 存储到内存
s.users[id] = user
log.Printf("创建用户: %s - %s\n", id, req.Name)
return &CreateUserResponse{
Id: id,
Success: true,
}, nil
}
// GetUser 实现获取用户RPC
func (s *UserServiceServer) GetUser(ctx context.Context, req *GetUserRequest) (*User, error) {
s.mu.RLock()
defer s.mu.RUnlock()
user, exists := s.users[req.Id]
if !exists {
return nil, fmt.Errorf("用户 %s 不存在", req.Id)
}
log.Printf("查询用户: %s\n", req.Id)
return user, nil
}
func main() {
// 创建监听端口
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("监听失败: %v", err)
}
// 创建gRPC服务器
grpcServer := grpc.NewServer()
// 注册服务
userServer := NewUserServiceServer()
pb.RegisterUserServiceServer(grpcServer, userServer)
log.Println("用户服务启动在 :50051")
// 启动服务
if err := grpcServer.Serve(lis); err != nil {
log.Fatalf("服务启动失败: %v", err)
}
}
4. 实现客户端
// client/main.go
package main
import (
"context"
"log"
"time"
"google.golang.org/grpc"
pb "你的项目路径/proto"
)
func main() {
// 连接到gRPC服务器
conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
if err != nil {
log.Fatalf("连接失败: %v", err)
}
defer conn.Close()
// 创建客户端
client := pb.NewUserServiceClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
// 创建用户
log.Println("正在创建用户...")
createResp, err := client.CreateUser(ctx, &pb.CreateUserRequest{
Name: "张三",
Email: "[email protected]",
})
if err != nil {
log.Fatalf("创建用户失败: %v", err)
}
log.Printf("用户创建成功: ID=%s\n", createResp.Id)
// 查询用户
log.Println("正在查询用户...")
user, err := client.GetUser(ctx, &pb.GetUserRequest{
Id: createResp.Id,
})
if err != nil {
log.Fatalf("查询用户失败: %v", err)
}
log.Printf("用户信息: ID=%s, 姓名=%s, 邮箱=%s\n",
user.Id, user.Name, user.Email)
}
5. Docker容器化
# Dockerfile
FROM golang:1.21-alpine AS builder
# 设置工作目录
WORKDIR /app
# 复制Go模块文件
COPY go.mod go.sum ./
RUN go mod download
# 复制源代码
COPY . .
# 构建二进制文件
RUN CGO_ENABLED=0 GOOS=linux go build -o user-service ./server
# 最终镜像
FROM alpine:latest
# 安装必要工具
RUN apk --no-cache add ca-certificates
# 复制二进制文件
COPY --from=builder /app/user-service /usr/local/bin/
# 暴露端口
EXPOSE 50051
# 运行服务
CMD ["user-service"]
实践练习
练习1:添加日志服务
要求:
- 创建一个新的gRPC服务,用于记录用户操作日志
- 在用户服务的CreateUser方法中,调用日志服务记录创建事件
- 使用Docker Compose编排两个服务
练习2:实现并发用户查询
要求:
- 实现一个批量查询接口,可以同时查询多个用户信息
- 使用goroutine并发处理多个查询请求
- 实现查询结果的聚合和错误处理
练习3:添加健康检查接口
要求:
- 实现一个简单的健康检查gRPC接口
- 当服务启动后,在2秒后报告健康状态
- 使用Docker HEALTHCHECK指令进行健康检查
常见错误
1. Proto文件编译错误
// 错误:忘记设置go_package
option go_package = "./proto"; // 必须设置
// 正确做法:确保go_package与目录结构一致
2. 并发访问map未加锁
// 错误:直接访问map
s.users[id] = user // 多goroutine访问会导致panic
// 正确:使用sync.RWMutex
s.mu.Lock()
s.users[id] = user
s.mu.Unlock()
3. 忘记处理gRPC连接错误
// 错误:忽略Dial错误
conn, _ := grpc.Dial(...) // 可能连接失败
// 正确:检查错误
conn, err := grpc.Dial(...)
if err != nil {
log.Fatalf("连接失败: %v", err)
}
4. Docker网络问题
# 错误:服务间使用localhost连接
在Docker容器中,localhost指向容器自身
# 正确:使用Docker网络或服务发现
在docker-compose.yml中定义服务名,通过服务名访问
小结
关键要点回顾
- 微服务架构:将大型应用拆分为独立、可扩展的小服务
- gRPC通信:使用Protocol Buffers定义接口,实现高效的服务间通信
- Docker容器化:标准化部署和运行环境
- 并发安全:在服务实现中注意并发访问的安全处理
- 错误处理:gRPC调用需要妥善处理网络错误和业务错误
进一步学习建议
- 学习服务发现机制(如Consul、etcd)
- 了解API网关的作用和实现
- 探索微服务监控和日志收集方案
- 学习配置管理和环境变量的使用
- 研究微服务的测试策略(单元测试、集成测试)
项目结构参考
microservice-demo/
├── proto/
│ └── user.proto
├── server/
│ └── main.go
├── client/
│ └── main.go
├── Dockerfile
├── docker-compose.yml
├── go.mod
└── go.sum
通过这个微服务Demo,你已经掌握了构建Go微服务的基础。接下来可以尝试扩展功能,添加更多服务,或者引入更高级的架构模式,如事件驱动架构或CQRS模式。
练习编辑器
go
Loading...