从单体到微服务:一个培训班出身的前端眼中的架构跃迁

栈里有风
2026-01-13 01:48
阅读 413

大家好,我是阿杰。三年前我还在某知名培训班“速成”前端开发,每天被Vue、React和Webpack轮番轰炸,连HTTP状态码都背不全。但阴差阳错进了公司后,却被安排参与一个从单体应用拆分成微服务的项目——没错,一个连Linux命令都不熟的前端,硬着头皮啃起了分布式系统。

今天写这篇教程,就是想用最直白的语言,告诉像我当年一样的零基础朋友:微服务不是高不可攀的神坛,而是一步一步走出来的路。哪怕你只会写console.log('Hello World'),也能理解它、实践它,甚至用它解决真实问题。


微服务到底是什么?它能帮我爬数据吗?

先说结论:微服务是一种把大应用拆成多个小服务的架构方式

想象你开了一家奶茶店:

  • 单体架构就像一个人干所有事:接单、泡茶、收钱、打扫……一旦他感冒请假,整个店就停摆。
  • 微服务架构则是分工明确:前台点单(API网关)、厨房做茶(订单服务)、收银台结账(支付服务)、清洁工打扫(日志服务)……一个人请假,其他岗位照常运转。

那这和爬虫GoJavaScript有什么关系?

  • 爬虫:可能是你业务的一部分(比如抓取商品价格),可以独立成一个“数据采集服务”。
  • Go:高性能、轻量级,特别适合写微服务(比如订单处理、爬虫引擎)。
  • JavaScript:虽然常用于前端,但用 Node.js 也能写微服务(比如用户认证、通知推送)。

我当初学的时候总以为微服务是“后端专属”,其实前端思维也能理解——组件化思想 + 网络通信 = 微服务雏形


环境准备:三行命令搭起你的微服务地基

别怕!我们只用最基础的工具:

工具 作用 安装方式
Go (1.20+) 写核心服务 官网下载或 brew install go
Node.js (18+) 写辅助服务 官网下载或 nvm install 18
Docker 容器化部署(可选但推荐) 官网安装 Docker Desktop

💡 避坑指南:新手常卡在环境变量。装完 Go 后,务必检查 go version 能否正常输出。如果报错,去官网看“Adding to PATH”部分。

创建项目目录:

mkdir micro-demo && cd micro-demo
mkdir user-service order-service crawler-service

核心概念:用“送外卖”理解微服务通信

1. 服务拆分:不是越小越好!

很多新手一上来就想把每个功能都拆成服务,结果维护成本爆炸。记住原则:

  • 按业务边界拆:用户管理、订单处理、支付流程、数据爬取——这些天然独立。
  • 避免高频调用:如果A服务每秒要调100次B服务,说明它们不该分开。

2. 服务通信:REST vs gRPC

  • REST(用 JavaScript/Node.js 写):简单、通用,适合浏览器交互。
  • gRPC(用 Go 写):高性能、二进制传输,适合服务间内部通信。

我当初用 REST 写所有服务,结果延迟高到老板皱眉。后来改用 gRPC,QPS 直接翻倍!

3. 服务发现与注册

当服务越来越多,你怎么知道“订单服务”现在跑在哪台机器、哪个端口?

答案:注册中心(如 Consul、ETCD)。但我们先用手动配置过渡。


实战:用 Go 和 JavaScript 搭建三个微服务

第一步:写一个用户服务(Go)

这个服务只做一件事:根据ID返回用户信息。

// user-service/main.go
package main

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

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

var users = []User{
	{1, "小明"},
	{2, "小红"},
}

func getUser(w http.ResponseWriter, r *http.Request) {
	id := r.URL.Query().Get("id")
	for _, u := range users {
		if fmt.Sprintf("%d", u.ID) == id {
			w.Header().Set("Content-Type", "application/json")
			json.NewEncoder(w).Encode(u)
			return
		}
	}
	http.Error(w, "User not found", 404)
}

func main() {
	http.HandleFunc("/user", getUser)
	log.Println("User service running on :8081")
	log.Fatal(http.ListenAndServe(":8081", nil))
}

运行:

cd user-service
go run main.go

测试:浏览器访问 http://localhost:8081/user?id=1,应返回 {"id":1,"name":"小明"}


第二步:写一个订单服务(Go + 调用用户服务)

订单服务需要知道“谁下的单”,所以要调用用户服务。

// order-service/main.go
package main

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

type Order struct {
	ID     int `json:"id"`
	UserID int `json:"user_id"`
	Item   string `json:"item"`
}

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

var orders = []Order{
	{101, 1, "珍珠奶茶"},
	{102, 2, "椰果奶茶"},
}

func getOrder(w http.ResponseWriter, r *http.Request) {
	idStr := r.URL.Query().Get("id")
	id, _ := strconv.Atoi(idStr)

	for _, o := range orders {
		if o.ID == id {
			// 调用用户服务获取用户名
			resp, err := http.Get("http://localhost:8081/user?id=" + strconv.Itoa(o.UserID))
			if err != nil {
				http.Error(w, "User service error", 500)
				return
			}
			defer resp.Body.Close()
			body, _ := io.ReadAll(resp.Body)

			var user User
			json.Unmarshal(body, &user)

			// 组合返回
			result := map[string]interface{}{
				"order_id": o.ID,
				"user_name": user.Name,
				"item": o.Item,
			}
			w.Header().Set("Content-Type", "application/json")
			json.NewEncoder(w).Encode(result)
			return
		}
	}
	http.Error(w, "Order not found", 404)
}

func main() {
	http.HandleFunc("/order", getOrder)
	log.Println("Order service running on :8082")
	log.Fatal(http.ListenAndServe(":8082", nil))
}

运行:

cd order-service
go run main.go

测试:访问 http://localhost:8082/order?id=101,应返回包含用户名的订单信息。

⚠️ 注意:这里硬编码了 localhost:8081,实际项目要用服务发现或配置中心!


第三步:写一个爬虫服务(JavaScript + Node.js)

假设我们要抓取某个公开API的商品价格,作为订单的参考数据。

// crawler-service/index.js
const express = require('express');
const axios = require('axios');

const app = express();
const PORT = 8083;

// 模拟一个公开商品API(实际可用真实接口)
const mockProductAPI = async () => {
  // 这里本该是真实爬虫逻辑,比如用 puppeteer 或 cheerio
  // 为简化,我们直接返回模拟数据
  return { price: Math.floor(Math.random() * 100) + 10 };
};

app.get('/price', async (req, res) => {
  try {
    const data = await mockProductAPI();
    res.json({ current_price: data.price });
  } catch (err) {
    res.status(500).json({ error: 'Crawler failed' });
  }
});

app.listen(PORT, () => {
  console.log(`Crawler service running on :${PORT}`);
});

先初始化项目并安装依赖:

cd crawler-service
npm init -y
npm install express axios
node index.js

测试:访问 http://localhost:8083/price,应返回随机价格。

🕷️ 关于爬虫:真实场景中,你可能需要用 cheerio 解析 HTML,或 puppeteer 处理动态页面。但微服务架构下,爬虫只是其中一个独立服务,失败不会拖垮整个系统。


服务组合:让它们协同工作

现在三个服务各自运行,但还没形成闭环。我们可以加一个API 网关(用 Node.js 快速实现):

// gateway/index.js
const express = require('express');
const axios = require('axios');

const app = express();

app.get('/api/order/:id', async (req, res) => {
  try {
    // 先获取订单详情(含用户信息)
    const orderRes = await axios.get(`http://localhost:8082/order?id=${req.params.id}`);
    const orderData = orderRes.data;

    // 再获取当前价格
    const priceRes = await axios.get('http://localhost:8083/price');
    const priceData = priceRes.data;

    // 合并返回
    res.json({
      ...orderData,
      reference_price: priceData.current_price
    });
  } catch (err) {
    res.status(500).json({ error: 'Gateway error' });
  }
});

app.listen(3000, () => {
  console.log('API Gateway running on :3000');
});

启动网关:

cd gateway
npm init -y
npm install express axios
node index.js

最终测试:访问 http://localhost:3000/api/order/101,你会看到:

{
  "order_id": 101,
  "user_name": "小明",
  "item": "珍珠奶茶",
  "reference_price": 42
}

恭喜!你完成了第一个微服务系统


新手常见问题解答

Q1:为什么不用数据库?这样不真实!

A:为了聚焦架构设计。真实项目当然要用数据库,但微服务的核心在于“拆分”和“通信”,而非存储细节。你可以后续给每个服务加自己的数据库(数据库隔离是微服务重要原则)。

Q2:服务挂了怎么办?

A:这就是微服务的挑战!你需要:

  • 健康检查:定期 ping 服务是否存活
  • 熔断机制:如 Hystrix(Go 有类似库)
  • 重试策略:自动重试失败请求

我当初没做这些,结果爬虫服务崩了,整个订单页打不开……血泪教训!

Q3:Go 和 JavaScript 选哪个写微服务?

场景 推荐语言 原因
高并发、低延迟(如支付) Go Goroutine 轻量高效
快速原型、I/O 密集(如通知) JavaScript (Node.js) 开发快,生态丰富
爬虫、数据处理 Go 或 Python Go 性能好,Python 库多

下一步学习建议

  1. 容器化:用 Docker 把每个服务打包,避免“在我机器上能跑”。
  2. 服务发现:学习 Consul 或 ETCD,告别硬编码 IP。
  3. 消息队列:用 RabbitMQ/Kafka 解耦服务(比如订单成功后发邮件)。
  4. 监控告警:集成 Prometheus + Grafana,看清系统瓶颈。

最后送大家一句话:架构不是设计出来的,是演进而来的。不要一上来就想搞“完美微服务”,先从单体做起,等业务复杂了再拆——这正是我从培训班菜鸟成长为架构参与者的亲身经历。

愿你在分布式的世界里,不再迷路。

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝