86·实战项目高级

实战:微服务 demo

projectmicroservicegrpcdocker

第86课:实战:微服务Demo

学习目标

完成本课学习后,你将能够:

  1. 理解微服务架构的基本概念和Go语言在其中的优势
  2. 使用gRPC定义和实现服务间通信
  3. 编写Dockerfile容器化微服务
  4. 构建包含多个服务的完整微服务系统
  5. 实现服务间的并发调用和错误处理

核心概念

什么是微服务?

想象一下,一个大型电商网站把所有功能都放在一个应用里(单体架构),就像一家杂货店,所有商品都摆在一起。而微服务架构就像一家大型购物中心,每个店铺(服务)专门经营一类商品,通过统一的通道相互连接。

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:添加日志服务

要求

  1. 创建一个新的gRPC服务,用于记录用户操作日志
  2. 在用户服务的CreateUser方法中,调用日志服务记录创建事件
  3. 使用Docker Compose编排两个服务

练习2:实现并发用户查询

要求

  1. 实现一个批量查询接口,可以同时查询多个用户信息
  2. 使用goroutine并发处理多个查询请求
  3. 实现查询结果的聚合和错误处理

练习3:添加健康检查接口

要求

  1. 实现一个简单的健康检查gRPC接口
  2. 当服务启动后,在2秒后报告健康状态
  3. 使用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中定义服务名,通过服务名访问

小结

关键要点回顾

  1. 微服务架构:将大型应用拆分为独立、可扩展的小服务
  2. gRPC通信:使用Protocol Buffers定义接口,实现高效的服务间通信
  3. Docker容器化:标准化部署和运行环境
  4. 并发安全:在服务实现中注意并发访问的安全处理
  5. 错误处理:gRPC调用需要妥善处理网络错误和业务错误

进一步学习建议

  1. 学习服务发现机制(如Consul、etcd)
  2. 了解API网关的作用和实现
  3. 探索微服务监控和日志收集方案
  4. 学习配置管理和环境变量的使用
  5. 研究微服务的测试策略(单元测试、集成测试)

项目结构参考

microservice-demo/
├── proto/
│   └── user.proto
├── server/
│   └── main.go
├── client/
│   └── main.go
├── Dockerfile
├── docker-compose.yml
├── go.mod
└── go.sum

通过这个微服务Demo,你已经掌握了构建Go微服务的基础。接下来可以尝试扩展功能,添加更多服务,或者引入更高级的架构模式,如事件驱动架构或CQRS模式。

练习编辑器

go
Loading...

继续学习

完成本课后,建议继续学习下一课「错误处理最佳实践」 以巩固所学知识。