当微服务变成一场噩梦
\n想象一下:你成功地将单体应用拆解成了十几个基于 Node.js 的微服务。每个服务负责自己的任务——身份验证、商品目录、购物车、支付。一切都很美好,直到真正的“乐趣”开始:服务宕机、请求丢失、超时增加、日志分散在五个不同的仪表板上。熟悉吗?
\n你从通过 axios 进行简单的 HTTP 请求开始,但现在每个服务都需要知道所有其他服务的地址,处理重试、熔断器和负载均衡。代码变成了基础设施逻辑的意大利面条。正是在这里,Service Mesh 登场了——这是一种架构模式,它将所有网络魔法从你的 JavaScript 代码中抽离出来。
在本文中,我们将探讨 Service Mesh 在 JavaScript 和 Node.js 上下文中的工作原理,编写与流行解决方案(Envoy、Istio、Linkerd)集成的真实代码,并理解这种复杂性何时是合理的。开始吧!
\n\n什么是 Service Mesh,为什么 JavaScript 开发者需要它
\nService Mesh 是一个专门用于微服务间通信的基础设施层。与其将服务发现、负载均衡、加密和监控逻辑嵌入到每个 JavaScript 服务中,不如将其提取到一个“sidecar”代理中,该代理与你的应用程序的每个实例一起运行。
\n对于 Node.js 开发者来说,这意味着:
\n- \n
- 干净的业务逻辑代码——你只需编写 Express/Koa/Fastify 处理器,所有网络魔法都被隐藏了。 \n
- 自动负载均衡——请求在实例之间分配,无需额外的库。 \n
- 开箱即用的可靠性——重试、超时、熔断器都可以声明式配置。 \n
- 可观测性——追踪、指标和日志自动收集。 \n
最酷的是:你不受语言限制。Service Mesh 在网络层工作,因此你的 JavaScript 服务可以与 Go 服务、Python 服务或遗留的 Java 单体应用通信——代理会让一切透明化。
\n\nService Mesh 架构:Sidecar 容器和控制平面
\n经典的 Service Mesh 由两部分组成:
\n数据平面
\n你的每个微服务(例如,运行在 3000 端口的 Node.js 应用程序)都与一个 sidecar 代理(通常是 Envoy 或 Linkerd-proxy)一起运行。所有入站和出站请求都通过这个代理:
\n// 通过 sidecar 进行路由的示例\n// 你的服务监听 localhost:3000\n// Sidecar 监听 localhost:8080 并转发流量\n\n// 在生产环境中,你永远不会直接访问另一个服务\n// 相反,你向 localhost:8080/service-name 发送请求\n\nconst axios = require('axios');\n\n// 没有 Service Mesh(不好)\n// const userService = 'http://user-service:4000';\n\n// 使用 Service Mesh(好)—— 向 sidecar 发起请求\nconst userService = 'http://localhost:8080'; \n\nasync function getUser(id) {\n const response = await axios.get(`${userService}/users/${id}`);\n return response.data;\n}\n代理会自动找到正确的 user-service 实例,平衡负载并应用安全策略。
\n\n控制平面
\n这是系统的“大脑”。例如,Istio 使用 Pilot、Mixer 和 Citadel 组件来管理配置、收集遥测数据和颁发证书。你用 YAML 描述路由规则,控制平面将这些规则分发到所有 sidecar 代理。
\n\n实践示例:Node.js + Envoy + Istio
\n让我们创建一个真实的配置。我们将有两个基于 Express 的微服务:order-service 和 payment-service。我们不会硬编码彼此的 URL,而是使用 Service Mesh。