从单体到微服务:零基础也能搞懂的分布式实战指南
大家好,我是你们的老朋友阿哲,目前在某一线大厂做后端开发,业余时间也在B站分享技术干货。最近很多刚入门的同学私信问我:“微服务到底是什么?我连单体应用都没写明白,怎么就突然要学分布式了?”
我当初学的时候也是一头雾水——文档里全是“服务发现”“熔断降级”“API 网关”,听起来像天书。但其实,微服务没那么玄乎。今天这篇教程,我就用一个真实的小项目,带你从零开始,亲手把一个单体应用拆成微服务架构。过程中我们会用到 Java、Go 和 React,让你看到不同语言在微服务中的角色。
别担心没基础,只要你会写简单的 Hello World,就能跟上!
为什么我们要从单体走向微服务?
想象你开了一家小餐馆(单体应用):
- 厨房、收银、点餐全在一个屋子里
- 刚开业时效率很高,改个菜单只要喊一声就行
但生意火爆后问题来了:
- 厨房太挤,厨师互相挡路
- 收银系统崩了,整个店都得关门
- 想加个外卖功能?得重装修整个店!
微服务就像把餐馆拆成独立部门:
- 点餐部(前端)
- 厨房(订单服务)
- 财务(支付服务)
- 配送(物流服务)
各部门独立运作,互不影响。这就是微服务的核心思想:拆分、解耦、自治。
环境准备:5 分钟搭好开发环境
我们用以下技术栈:
- 前端:React(展示界面)
- 后端服务1:Java + Spring Boot(处理订单)
- 后端服务2:Go(处理用户信息)
- 工具:Docker(简化部署)、Postman(测试接口)
安装清单
| 工具 | 版本建议 | 安装命令(Mac/Linux) |
|---|---|---|
| Node.js | ≥18.x | brew install node |
| Java | JDK 17 | sdk install java 17-open |
| Go | ≥1.21 | brew install go |
| Docker | 最新版 | 官网下载安装 |
💡 小贴士:Windows 用户建议用 WSL2,避免环境配置踩坑。
验证安装是否成功:
node -v # 应输出 v18.x
java -version # 应显示 openjdk 17.x
go version # 应显示 go1.21.x
docker --version # 应显示 Docker version x.x.x
核心概念:用“点外卖”理解微服务
1. 单体应用(Monolith)
所有功能打包在一个程序里,比如一个 JAR 包包含用户、订单、支付逻辑。
✅ 优点:开发简单、部署方便
❌ 缺点:代码臃肿、故障影响全局、团队协作困难
2. 微服务(Microservices)
每个功能拆成独立服务,通过 HTTP/gRPC 通信。
✅ 优点:独立部署、技术栈灵活、容错性强
❌ 缺点:运维复杂、网络延迟、数据一致性难
3. 关键组件(新手必知)
- API 网关:所有请求的“总前台”,路由到对应服务
- 服务注册与发现:服务自动登记地址,其他服务能动态找到它
- 配置中心:统一管理各服务的配置文件
- 熔断器:某个服务挂了,自动降级不拖垮整个系统
我当初第一次听到“熔断”以为是电路保险丝……其实它就像餐厅的“备用菜单”——主厨请假了,就先推简餐,不让客人饿着。
实战项目:把“简易电商”从单体拆成微服务
我们将实现一个极简电商系统,包含:
- 用户查看商品(React 前端)
- 创建订单(Java 服务)
- 查询用户信息(Go 服务)
第一步:创建单体版本(对比用)
先快速搭一个单体版,感受“痛点”。
// Java 单体示例:App.java
@RestController
public class MonolithController {
@GetMapping("/product/{id}")
public String getProduct(@PathVariable int id) {
return "Product " + id; // 模拟商品查询
}
@PostMapping("/order")
public String createOrder(@RequestBody Map<String, Object> order) {
return "Order created: " + order.get("userId"); // 模拟下单
}
@GetMapping("/user/{id}")
public String getUser(@PathVariable int id) {
return "User " + id; // 模拟用户查询
}
}
启动后访问 http://localhost:8080/product/1,一切正常。
但问题来了:如果用户服务慢,整个 /order 接口都会卡住!
第二步:拆分成两个微服务
服务1:订单服务(Java + Spring Boot)
- 创建新项目:
spring init --dependencies=web,actuator order-service
cd order-service
- 编写订单控制器:
// OrderController.java
@RestController
public class OrderController {
// 模拟调用用户服务(后续会改成真实HTTP调用)
@PostMapping("/orders")
public ResponseEntity<String> createOrder(@RequestBody OrderRequest req) {
// 这里应该调用 user-service
return ResponseEntity.ok("Order for user " + req.getUserId() + " created");
}
}
record OrderRequest(int userId, String product) {}
- 启动在 8081 端口(
application.properties):
server.port=8081
服务2:用户服务(Go)
- 初始化 Go 模块:
mkdir user-service && cd user-service
go mod init user-service
- 编写 HTTP 服务:
// main.go
package main
import (
"encoding/json"
"net/http"
"strconv"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
func getUser(w http.ResponseWriter, r *http.Request) {
// 从URL获取ID,如 /users/123
idStr := r.PathValue("id")
id, _ := strconv.Atoi(idStr)
user := User{ID: id, Name: "User_" + idStr}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
}
func main() {
http.HandleFunc("/users/{id}", getUser)
http.ListenAndServe(":8082", nil)
}
- 启动服务:
go run main.go
现在你有两个独立服务:
- 订单服务:
http://localhost:8081/orders - 用户服务:
http://localhost:8082/users/1
第三步:让服务互相通信(关键!)
在订单服务中调用用户服务:
// 在 OrderController 中添加
private final RestTemplate restTemplate = new RestTemplate();
@PostMapping("/orders")
public ResponseEntity<String> createOrder(@RequestBody OrderRequest req) {
// 调用 user-service
String url = "http://localhost:8082/users/" + req.getUserId();
try {
String userJson = restTemplate.getForObject(url, String.class);
// 这里可以解析 JSON,验证用户是否存在
return ResponseEntity.ok("Order created for user: " + userJson);
} catch (Exception e) {
return ResponseEntity.status(500).body("User service unavailable");
}
}
⚠️ 注意:硬编码
localhost:8082是反模式!生产环境要用服务发现(如 Nacos、Consul),但初学阶段先这样简化。
第四步:加个 React 前端
- 创建 React 应用:
npx create-react-app frontend
cd frontend
- 修改
App.js:
import { useState } from 'react';
function App() {
const [result, setResult] = useState('');
const handleOrder = async () => {
const response = await fetch('http://localhost:8081/orders', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userId: 1, product: 'Laptop' })
});
const text = await response.text();
setResult(text);
};
return (
<div>
<h1>Simple E-commerce</h1>
<button onClick={handleOrder}>Create Order</button>
<p>{result}</p>
</div>
);
}
export default App;
- 解决跨域问题(在
order-service的 Spring Boot 中加配置):
@CrossOrigin(origins = "http://localhost:3000")
@RestController
public class OrderController { ... }
- 启动前端:
npm start # 默认运行在 localhost:3000
点击按钮,你应该能看到订单创建成功的消息!
常见问题 & 避坑指南
❓ 问题1:服务调用失败,报连接拒绝
- 原因:被调用的服务没启动,或端口不对
- 解决:先单独测试
curl http://localhost:8082/users/1是否通
❓ 问题2:React 报 CORS 错误
- 原因:浏览器阻止了跨域请求
- 解决:后端加
@CrossOrigin(开发用),生产环境用 Nginx 反向代理
❓ 问题3:Java 服务调用 Go 服务超时
- 原因:网络延迟或 Go 服务处理慢
- 解决:给
RestTemplate设置超时:
@Bean
public RestTemplate restTemplate() {
return new RestTemplateBuilder()
.setConnectTimeout(Duration.ofSeconds(2))
.setReadTimeout(Duration.ofSeconds(3))
.build();
}
❓ 问题4:如何管理多个服务的配置?
- 初级方案:每个服务有自己的
application.properties - 进阶方案:用 Spring Cloud Config 或 Apollo 配置中心
我见过太多新人一上来就搞 Kubernetes,结果连本地多服务调试都不会。记住:先跑起来,再优化。
下一步学习建议
你已经迈出了微服务的第一步!接下来可以:
加服务注册中心
试试用 Nacos 或 Eureka,让服务自动发现彼此,告别硬编码 IP。引入 API 网关
用 Spring Cloud Gateway 统一入口,前端只调gateway:8080,由网关路由到具体服务。加熔断机制
用 Resilience4j(Java)或 Go 的 circuit breaker 库,防止级联故障。容器化部署
把 Java 和 Go 服务打包成 Docker 镜像,用docker-compose.yml一键启动所有服务。学分布式事务
当订单和库存要同时更新,如何保证数据一致?了解 Saga 模式 或 TCC。
最后说两句
微服务不是银弹,小项目用单体更高效。但当你遇到“改一行代码要全量回归测试”“一个 bug 导致全站瘫痪”时,就是该考虑拆分的时候了。
这篇教程故意省略了 Spring Cloud、gRPC、消息队列等复杂内容,因为对新手来说,先理解“为什么拆”比“怎么拆”更重要。
如果你跟着做完了这个小项目,恭喜你——你已经超过了 80% 只看理论不动手的人!
我在 B 站更新了配套视频(搜“阿哲 微服务实战”),包含完整代码演示和调试技巧。有问题欢迎评论区留言,我会一一回复。
技术路上,一起成长!

评论 0