从单体到微服务:一个培训班出身的前端眼中的架构跃迁
大家好,我是阿杰。三年前我还在某知名培训班“速成”前端开发,每天被Vue、React和Webpack轮番轰炸,连HTTP状态码都背不全。但阴差阳错进了公司后,却被安排参与一个从单体应用拆分成微服务的项目——没错,一个连Linux命令都不熟的前端,硬着头皮啃起了分布式系统。
今天写这篇教程,就是想用最直白的语言,告诉像我当年一样的零基础朋友:微服务不是高不可攀的神坛,而是一步一步走出来的路。哪怕你只会写console.log('Hello World'),也能理解它、实践它,甚至用它解决真实问题。
微服务到底是什么?它能帮我爬数据吗?
先说结论:微服务是一种把大应用拆成多个小服务的架构方式。
想象你开了一家奶茶店:
- 单体架构就像一个人干所有事:接单、泡茶、收钱、打扫……一旦他感冒请假,整个店就停摆。
- 微服务架构则是分工明确:前台点单(API网关)、厨房做茶(订单服务)、收银台结账(支付服务)、清洁工打扫(日志服务)……一个人请假,其他岗位照常运转。
那这和爬虫、Go、JavaScript有什么关系?
- 爬虫:可能是你业务的一部分(比如抓取商品价格),可以独立成一个“数据采集服务”。
- 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 库多 |
下一步学习建议
- 容器化:用 Docker 把每个服务打包,避免“在我机器上能跑”。
- 服务发现:学习 Consul 或 ETCD,告别硬编码 IP。
- 消息队列:用 RabbitMQ/Kafka 解耦服务(比如订单成功后发邮件)。
- 监控告警:集成 Prometheus + Grafana,看清系统瓶颈。
最后送大家一句话:架构不是设计出来的,是演进而来的。不要一上来就想搞“完美微服务”,先从单体做起,等业务复杂了再拆——这正是我从培训班菜鸟成长为架构参与者的亲身经历。
愿你在分布式的世界里,不再迷路。

评论 0