从单体到微服务:零基础也能搞懂的分布式实战指南

Linux夜行者
2026-01-14 20:36
阅读 1995

大家好,我是你们的老朋友阿哲,目前在某一线大厂做后端开发,业余时间也在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)

  1. 创建新项目:
spring init --dependencies=web,actuator order-service
cd order-service
  1. 编写订单控制器:
// 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) {}
  1. 启动在 8081 端口(application.properties):
server.port=8081

服务2:用户服务(Go)

  1. 初始化 Go 模块:
mkdir user-service && cd user-service
go mod init user-service
  1. 编写 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)
}
  1. 启动服务:
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 前端

  1. 创建 React 应用:
npx create-react-app frontend
cd frontend
  1. 修改 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;
  1. 解决跨域问题(在 order-service 的 Spring Boot 中加配置):
@CrossOrigin(origins = "http://localhost:3000")
@RestController
public class OrderController { ... }
  1. 启动前端:
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,结果连本地多服务调试都不会。记住:先跑起来,再优化


下一步学习建议

你已经迈出了微服务的第一步!接下来可以:

  1. 加服务注册中心
    试试用 NacosEureka,让服务自动发现彼此,告别硬编码 IP。

  2. 引入 API 网关
    Spring Cloud Gateway 统一入口,前端只调 gateway:8080,由网关路由到具体服务。

  3. 加熔断机制
    Resilience4j(Java)或 Go 的 circuit breaker 库,防止级联故障。

  4. 容器化部署
    把 Java 和 Go 服务打包成 Docker 镜像,用 docker-compose.yml 一键启动所有服务。

  5. 学分布式事务
    当订单和库存要同时更新,如何保证数据一致?了解 Saga 模式TCC


最后说两句

微服务不是银弹,小项目用单体更高效。但当你遇到“改一行代码要全量回归测试”“一个 bug 导致全站瘫痪”时,就是该考虑拆分的时候了。

这篇教程故意省略了 Spring Cloud、gRPC、消息队列等复杂内容,因为对新手来说,先理解“为什么拆”比“怎么拆”更重要

如果你跟着做完了这个小项目,恭喜你——你已经超过了 80% 只看理论不动手的人!

我在 B 站更新了配套视频(搜“阿哲 微服务实战”),包含完整代码演示和调试技巧。有问题欢迎评论区留言,我会一一回复。

技术路上,一起成长!

评论 0

最热最新
暂无评论
Linux夜行者Lv.1
0
影响力
0
文章
0
粉丝