从单体到微服务:Go语言实战入门指南
大家好,我是一名从培训班出来的前端开发,但这些年因为项目需要,也深入接触了后端和架构设计。当初刚听说“微服务”这个词时,我以为是某种高深莫测的黑科技,吓得不敢碰。后来才发现,它其实没那么可怕——只要你有清晰的路径和一个能跑起来的小例子。
今天我就用自己踩过的坑、走过的弯路,手把手带你用 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.0 和 Docker 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.mod 再 RUN 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