2. Go 微服务项目初始化:使用 Go Module 创建项目、目录结构设计、基础 HTTP 服务框架搭建

本章是微服务压测闭环工程实践的起点。我们将从零开始,使用 Go Module 初始化项目,设计符合微服务治理规范的目录结构,并搭建一个可运行的基础 HTTP 服务框架。这个框架将作为后续所有压测、监控与部署实验的载体。

2.1 使用 Go Module 初始化项目

Go Module 是 Go 1.11 引入的官方依赖管理方案,也是现代 Go 项目的标准配置。我们使用 go mod init 命令创建模块,模块路径通常采用公司域名+项目名的形式,例如 github.com/company/project。

初始化命令:
mkdir go-microservice-demo
cd go-microservice-demo
go mod init github.com/example/go-microservice-demo

执行后,项目根目录下会生成 go.mod 文件,内容如下:

module github.com/example/go-microservice-demo

go 1.22.0

go.mod 文件记录了模块路径、Go 版本以及依赖项。后续每添加一个外部依赖,Go 都会自动更新此文件并生成 go.sum 用于校验。

2.2 微服务项目目录结构设计

合理的目录结构是微服务项目可维护性的基石。我们采用业界广泛使用的 分层架构 + 领域驱动设计(DDD) 风格,将代码按职责划分为清晰的层级。

目录/文件 职责 说明
cmd/ 应用入口 每个微服务对应一个子目录,如 cmd/server/main.go
internal/ 内部业务逻辑 禁止外部包导入,包含 handler、service、repository 等
pkg/ 可复用的公共库 如 logger、config、middleware 等
api/ API 定义 存放 protobuf、OpenAPI 规范文件
config/ 配置文件 YAML/JSON 配置文件,按环境拆分
deploy/ 部署相关 Dockerfile、K8s 清单、CI/CD 脚本
test/ 集成测试与压测脚本 包含 benchmark、loadtest 等
Makefile 项目构建与任务编排 统一管理 build、test、lint 等命令
最佳实践: internal 目录是 Go 编译器强制的包隔离机制。放在 internal 下的包只能被其父目录及兄弟目录导入,有效防止外部项目滥用内部 API。

以下是我们为本课程设计的完整目录结构:

go-microservice-demo/
├── cmd/
│   └── server/
│       └── main.go
├── internal/
│   ├── handler/
│   │   └── user.go
│   ├── service/
│   │   └── user.go
│   ├── repository/
│   │   └── user.go
│   └── model/
│       └── user.go
├── pkg/
│   ├── config/
│   │   └── config.go
│   ├── logger/
│   │   └── logger.go
│   └── middleware/
│       └── recovery.go
├── api/
│   └── proto/
│       └── user.proto
├── config/
│   ├── default.yaml
│   └── production.yaml
├── deploy/
│   ├── Dockerfile
│   └── k8s/
│       └── deployment.yaml
├── test/
│   ├── integration/
│   └── loadtest/
├── go.mod
├── go.sum
└── Makefile

2.3 基础 HTTP 服务框架搭建

我们选择 Go 标准库 net/http 作为基础 HTTP 框架,不引入第三方路由库,以保持最小依赖和最高透明度。后续章节会逐步引入 gin、grpc 等框架进行对比压测。

首先在 cmd/server/main.go 中编写入口代码:

package main

import (
    "context"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"

    "github.com/example/go-microservice-demo/internal/handler"
    "github.com/example/go-microservice-demo/pkg/config"
    "github.com/example/go-microservice-demo/pkg/logger"
)

func main() {
    // 1. 加载配置
    cfg := config.Load()

    // 2. 初始化日志
    log := logger.New(cfg.LogLevel)

    // 3. 注册路由
    mux := http.NewServeMux()
    mux.HandleFunc("GET /api/v1/health", handler.HealthCheck)
    mux.HandleFunc("GET /api/v1/users/{id}", handler.GetUser)
    mux.HandleFunc("POST /api/v1/users", handler.CreateUser)

    // 4. 创建 HTTP 服务
    srv := &http.Server{
        Addr:         ":" + cfg.Port,
        Handler:      mux,
        ReadTimeout:  5 * time.Second,
        WriteTimeout: 10 * time.Second,
        IdleTimeout:  15 * time.Second,
    }

    // 5. 优雅启停
    go func() {
        log.Infof("Server starting on port %s", cfg.Port)
        if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            log.Fatalf("Server failed: %v", err)
        }
    }()

    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit

    log.Info("Shutting down server...")
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()
    if err := srv.Shutdown(ctx); err != nil {
        log.Fatalf("Server forced to shutdown: %v", err)
    }
    log.Info("Server exited")
}
注意: Go 1.22 开始支持 mux.HandleFunc("GET /api/v1/users/{id}", handler) 这种带路径参数的模式匹配。如果你使用 Go 1.21 或更早版本,需要手动解析路径或使用第三方路由库。

接下来实现一个简单的 handler,位于 internal/handler/user.go:

package handler

import (
    "encoding/json"
    "net/http"
)

type UserResponse struct {
    ID   string `json:"id"`
    Name string `json:"name"`
}

func HealthCheck(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
}

func GetUser(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id")
    resp := UserResponse{ID: id, Name: "User_" + id}
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(resp)
}

func CreateUser(w http.ResponseWriter, r *http.Request) {
    var req struct {
        Name string `json:"name"`
    }
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        http.Error(w, "invalid request body", http.StatusBadRequest)
        return
    }
    resp := UserResponse{ID: "new-001", Name: req.Name}
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(resp)
}

2.4 项目初始化流程总览

下图展示了从创建项目到启动 HTTP 服务的完整流程,以及各组件之间的依赖关系:

Go 微服务项目初始化流程 1. go mod init 2. 目录结构设计 3. 编写 main.go pkg/config pkg/logger internal/handler internal/service internal/repository 路由注册 优雅启停 ✅ 可运行的 HTTP 微服务 💡 说明: 本流程强调最小依赖与清晰分层。后续章节将在此框架上叠加压测工具、监控埋点与容器化部署。

2.5 验证服务可运行

完成上述代码后,在项目根目录执行以下命令启动服务:

go run ./cmd/server/main.go

服务启动后,使用 curl 验证接口:

# 健康检查
curl http://localhost:8080/api/v1/health
# 输出: {"status":"ok"}

# 获取用户
curl http://localhost:8080/api/v1/users/42
# 输出: {"id":"42","name":"User_42"}

# 创建用户
curl -X POST http://localhost:8080/api/v1/users \
  -H "Content-Type: application/json" \
  -d '{"name":"Alice"}'
# 输出: {"id":"new-001","name":"Alice"}
压测准备: 这个基础服务虽然简单,但已经包含了 HTTP 路由、JSON 序列化、请求解析等核心路径。后续章节将直接使用此服务作为压测目标,对比不同框架、不同配置下的性能差异。

2.6 本章小结

我们完成了以下关键工作:

  • 使用 go mod init 创建了模块化项目
  • 设计了符合微服务治理规范的分层目录结构
  • 基于标准库 net/http 搭建了可运行的基础 HTTP 服务
  • 实现了健康检查、用户查询与创建三个核心接口
  • 配置了优雅启停与超时控制,为生产级部署打下基础