从单体到微服务:Go语言实战入门指南

林芳
2025-12-22 09:35
阅读 1038

大家好,我是一名从培训班出来的前端开发,但这些年因为项目需要,也深入接触了后端和架构设计。当初刚听说“微服务”这个词时,我以为是某种高深莫测的黑科技,吓得不敢碰。后来才发现,它其实没那么可怕——只要你有清晰的路径和一个能跑起来的小例子。

今天我就用自己踩过的坑、走过的弯路,手把手带你用 Go 语言 从零开始,把一个简单的单体应用拆成微服务。这篇教程不讲大道理,只讲你能马上动手试的东西。无论你是前端转后端,还是纯新手,只要会写点代码,就能跟着做。


为什么我们要学微服务?

想象你开了个小餐馆,一开始只有你一个人:买菜、洗菜、炒菜、收钱、擦桌子……全包。这就是 单体应用(Monolith) ——所有功能都在一个程序里。

生意好了,每天100桌,你忙不过来了。于是你雇人:一个管厨房,一个管前台,一个管采购。他们各自独立工作,通过传菜口、对讲机沟通。这就是 微服务架构 ——把大系统拆成多个小服务,每个服务只干一件事。

好处很明显

  • 某个服务挂了(比如支付出问题),其他功能还能用(比如还能浏览菜单)
  • 团队可以并行开发不同服务
  • 技术栈可以灵活选择(订单用 Go,用户用 Python)

但也有代价:调试变复杂了、部署要管多个服务、网络调用可能失败……

所以,不是所有项目都需要微服务。如果你只是做个个人博客,单体就够了。但如果你在做电商平台、SaaS 系统,或者想进大厂,微服务是绕不开的一课。


开发环境准备:5分钟搞定

我们用 Go 语言 来写服务,因为它简洁、高性能,特别适合微服务。先确保你的电脑装好了以下工具:

工具 版本建议 安装方式
Go 1.20+ 官网下载
Docker 最新版 Docker Desktop
curl 或 Postman - 用于测试 API

验证安装

打开终端,输入:

go version
docker --version

如果看到类似 go version go1.21.0Docker version 24.x.x,说明环境 OK。

💡 新手避坑:别用 Windows CMD 写 Go 项目!推荐用 VS Code + Go 插件,或者 Goland。路径不要带中文,否则可能编译报错。


核心概念:用大白话讲清楚

1. 什么是微服务?

微服务 = 多个独立的小程序,通过网络(通常是 HTTP 或 gRPC)互相调用。

比如电商系统可以拆成:

  • 用户服务(管理注册/登录)
  • 商品服务(管理商品信息)
  • 订单服务(处理下单)

每个服务有自己的数据库、代码库、部署流程。

2. 服务怎么通信?

最简单的方式是 HTTP REST API。比如订单服务要查用户信息,就向用户服务发一个 HTTP 请求:

GET http://user-service:8080/users/123

注意:这里 user-service 是服务名,不是 IP。后面我们会用 Docker Compose 实现这种“名字解析”。

3. 服务发现 & 配置中心?

先别被吓到!初期我们可以用 硬编码地址环境变量。等你熟悉后再学 Consul、Nacos 这些高级玩意儿。


实战项目:把单体用户系统拆成微服务

我们来做一个极简的“用户管理系统”。先写成单体,再拆!

第一步:单体版本(all-in-one)

创建项目目录:

mkdir user-system && cd user-system
go mod init user-system

创建 main.go

package main

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

type User struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
	Age  int    `json:"age"`
}

var users = []User{
	{ID: 1, Name: "张三", Age: 25},
	{ID: 2, Name: "李四", Age: 30},
}

func getUsers(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(users)
}

func main() {
	http.HandleFunc("/users", getUsers)
	log.Println("单体服务启动,访问 http://localhost:8080/users")
	log.Fatal(http.ListenAndServe(":8080", nil))
}

运行:

go run main.go

浏览器打开 http://localhost:8080/users,看到用户列表就成功了!


第二步:拆!拆成两个微服务

现在我们把“用户数据”单独拎出来,做成 user-service,主服务变成 api-gateway(网关),只负责转发请求。

1. 创建 user-service

新建文件夹 user-service,里面放:

user-service/main.go

package main

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

type User struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
	Age  int    `json:"age"`
}

var users = []User{
	{ID: 1, Name: "张三", Age: 25},
	{ID: 2, Name: "李四", Age: 30},
}

func getUsers(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(users)
}

func main() {
	http.HandleFunc("/users", getUsers)
	log.Println("用户服务启动,端口 8081")
	log.Fatal(http.ListenAndServe(":8081", nil))
}

注意:这里监听 :8081,和之前不一样。

2. 创建 api-gateway

新建 api-gateway/main.go

package main

import (
	"io"
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"
)

func main() {
	// 用户服务的地址
	userServiceURL, _ := url.Parse("http://localhost:8081")

	// 创建反向代理
	proxy := httputil.NewSingleHostReverseProxy(userServiceURL)

	// 所有 /users 请求都转发给 user-service
	http.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) {
		proxy.ServeHTTP(w, r)
	})

	log.Println("API 网关启动,端口 8080")
	log.Fatal(http.ListenAndServe(":8080", nil))
}

这个网关的作用很简单:收到 /users 请求,就原封不动转给 localhost:8081

3. 分别启动两个服务

终端1

cd user-service
go run main.go

终端2

cd api-gateway
go run main.go

现在访问 http://localhost:8080/users,依然能看到用户数据!但背后其实是两个独立进程在协作。

恭喜!你已经实现了最基础的微服务拆分。


第三步:用 Docker 容器化(告别端口冲突)

现实中,服务不会总跑在 localhost。我们用 Docker 模拟真实环境。

编写 Dockerfile

user-service/Dockerfile

FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY go.mod .
COPY . .
RUN go build -o user-service .

FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/user-service .
EXPOSE 8081
CMD ["./user-service"]

api-gateway/Dockerfile 类似,只需改端口为 8080,输出名为 api-gateway

用 Docker Compose 编排

根目录创建 docker-compose.yml

version: '3'

services:
  user-service:
    build: ./user-service
    ports:
      - "8081:8081"
    networks:
      - app-network

  api-gateway:
    build: ./api-gateway
    ports:
      - "8080:8080"
    depends_on:
      - user-service
    networks:
      - app-network

networks:
  app-network:
    driver: bridge

关键点:

  • networks 让两个容器在同一个虚拟网络里
  • 容器内可以直接用服务名通信(比如 http://user-service:8081

修改网关代码,用服务名调用

api-gateway/main.go 中的 URL 改成:

userServiceURL, _ := url.Parse("http://user-service:8081")

然后构建并启动:

docker-compose up --build

访问 http://localhost:8080/users,依然正常!但这次,两个服务跑在隔离的容器里,互不影响。

💡 我当初学的时候,总搞不清容器内怎么通信,后来明白:只要在同一个 network,就能用 服务名:端口 直接访问,不用记 IP!


新手常见问题 & 解决方案

问题 原因 解决办法
connection refused 服务没启动或端口不对 先单独测试 user-service 能否访问
Docker 构建慢 每次都重下依赖 Dockerfile 中先 COPY go.modRUN go mod download
网关转发后返回空 忘了设置 Header 反向代理会自动处理,但如果是手动 http.Get,记得复制响应头
本地能跑,服务器不行 防火墙/安全组没开端口 云服务器要手动开放 8080、8081 端口

调试技巧

  • 在服务里加 log.Printf("收到请求 %s", r.URL.Path)
  • curl -v http://localhost:8080/users 查看完整请求过程
  • Docker 容器日志:docker-compose logs user-service

下一步学习建议

你现在掌握了微服务的“骨架”,但真实项目还需要很多“肌肉”:

1. 加上错误处理

现在的代码没有处理 user-service 挂掉的情况。可以在网关加超时和重试:

client := &http.Client{
    Timeout: 5 * time.Second,
}
resp, err := client.Get("http://user-service:8081/users")

2. 引入 gRPC(更高效)

HTTP JSON 有性能损耗。Go 的 gRPC 很成熟,适合内部服务通信。

3. 用配置文件代替硬编码

把服务地址写进 config.yaml,用 Viper 库读取。

4. 学习服务注册与发现

当服务实例动态增减时,不能靠写死地址。可以试试 Consul + Go-kit。

5. 监控 & 日志聚合

用 Prometheus + Grafana 看服务指标,用 ELK 收集日志。


免费资源推荐

别花冤枉钱报班!这些资源足够你入门:

我当初就是靠这些免费资源,从连 Docker 都不会,到能独立搭微服务集群。关键是动手敲代码,而不是只看不练。


最后的话

微服务不是银弹,但它是现代软件工程的重要思维。今天我们用不到 100 行 Go 代码,完成了从单体到分布式的跨越。你不需要一开始就搞懂 Kubernetes、Service Mesh,先让两个服务能通信,你就已经超过 80% 的初学者了

记住:架构是演进而非设计出来的。先写单体,等业务复杂了再拆。别为了微服务而微服务。

如果你跟着教程跑通了代码,欢迎在评论区留言“已跑通”!遇到问题也尽管问,我会尽力解答。

祝你 coding happy!

评论 0

最热最新
暂无评论
林芳Lv.1
0
影响力
0
文章
0
粉丝