微服务架构实战中的常见陷阱与优化策略
微服务拆分后,我差点被通信问题搞崩溃
去年我们团队把一个单体应用拆成微服务,前端也跟着调整。一开始觉得不就是多调几个接口嘛,结果上线第一天就炸了——用户登录后首页白屏,控制台一堆跨域错误。折腾到凌晨三点才发现,网关没配好,子服务的 CORS 策略各自为政,有的允许所有源,有的只认 localhost。这种混乱在单体里根本不会出现。
从那以后,我给自己定了条铁律:微服务前端集成,必须统一入口、统一错误处理、统一认证透传。下面说说我现在怎么搞的。
我的写法,亲测靠谱:封装一个微服务请求层
别再在每个组件里直接写 fetch 或 axios 调不同域名了。我见过有人这样写:
// 别这么干!
const userRes = await fetch('https://user-service.example.com/profile');
const orderRes = await fetch('https://order-service.example.com/list');
这玩意儿一换环境就死,本地、测试、生产域名全不一样,改配置改到吐。更别说跨域、鉴权、重试这些逻辑到处重复。
我现在会建一个 api/ 目录,里面按服务模块组织,但所有请求都走同一个代理地址(比如 /api),由后端网关或 Nginx 转发到具体服务。前端代码里只认一个基础 URL:
// config.js
export const API_BASE = '/api'; // 本地开发时 proxy 到 http://localhost:8080
// services/user.js
import { API_BASE } from '../config';
export async function getUserProfile() {
const res = await fetch(${API_BASE}/user/profile, {
credentials: 'include' // 关键!带 cookie
});
if (!res.ok) throw new Error(HTTP ${res.status});
return res.json();
}
这样无论后端服务怎么拆、IP 怎么变,前端完全无感。本地开发用 webpack devServer proxy,生产环境靠 Nginx 路由,一套代码走天下。
这几种错误写法,别再踩坑了
1. 在前端硬编码多个服务地址
有人为了“灵活”,把每个微服务的地址写进 .env 文件:
VUE_APP_USER_API=https://user.jztheme.com
VUE_APP_ORDER_API=https://order.jztheme.com
然后在代码里分别调用。表面看没问题,但一旦某个服务要加路径前缀、换协议(HTTP/HTTPS)、或者临时下线做灰度,你就得改一堆地方。更糟的是,如果这些服务不在同一个主域下,cookie 无法共享,登录态直接丢。
2. 错误地处理 401 重定向
微服务架构下,任何一个服务返回 401,都应该跳转到登录页。但我见过有人只在用户服务里处理 401,其他服务报错就静默失败。结果用户明明没登录,订单页面却显示“加载中…”卡住,体验极差。
3. 忽略请求时序和依赖
比如先拿用户信息,再根据用户 ID 拿他的订单。如果两个请求并行发出去,订单服务可能因为用户 ID 为空而报错。虽然可以用 async/await 串行,但更好的做法是用 React Query 或 SWR 这类库管理依赖:
// 用 React Query 的 enabled 选项
const { data: user } = useQuery('user', getUserProfile);
const { data: orders } = useQuery(
['orders', user?.id],
() => getOrders(user.id),
{ enabled: !!user } // 只有 user 存在才发请求
);
实际项目中的坑:认证和状态同步
微服务最头疼的不是技术,是状态。比如用户在 A 服务修改了头像,B 服务里的头像还是旧的。前端能做的有限,但至少可以:
- 在关键操作后主动刷新相关数据(比如上传头像成功后,重新拉取用户信息)
- 对非实时性要求高的数据,加个缓存过期时间(比如 5 分钟)
另外,一定要统一 token 机制。我们之前用 cookie + session,后来改成 JWT,但不同服务签发的 token 有效期不一致,导致用户在某个服务突然登出。现在强制所有服务使用同一个认证中心签发的 token,前端只管存和带,不管解析。
还有个小细节:本地开发时,如果微服务分散在不同端口(比如用户服务 3001,订单服务 3002),记得在 devServer 里配多个 proxy:
// vite.config.js 示例
export default {
server: {
proxy: {
'/api/user': 'http://localhost:3001',
'/api/order': 'http://localhost:3002',
// 所有 /api/* 都被代理,前端代码无感知
}
}
}
核心代码就这几行:全局错误拦截
微服务错误码五花八门,但前端处理逻辑可以统一。我一般在 axios 或 fetch 封装层加个拦截器:
// api/client.js
function handleResponse(response) {
if (response.status === 401) {
// 清掉本地 token,跳转登录
localStorage.removeItem('token');
window.location.href = '/login';
return Promise.reject('Unauthorized');
}
if (response.status >= 500) {
// 上报错误监控
reportError(Service error: ${response.url});
throw new Error('服务暂时不可用,请稍后再试');
}
return response;
}
// 使用时
export async function fetchData() {
const res = await fetch('/api/data').then(handleResponse);
return res.json();
}
这样,不管哪个微服务崩了,用户看到的都是统一的友好提示,而不是一堆 technical details。
结尾:没有银弹,但有避坑指南
微服务前端集成,说白了就是“让复杂后端对前端透明”。我现在的方案也不是完美的——比如服务间数据不一致的问题,前端只能缓解不能根治。但至少保证了:换环境不用改代码、错误处理不遗漏、登录态不丢失。
以上是我踩坑后的总结,希望对你有帮助。有更好的方案欢迎评论区交流,比如你们怎么处理微服务下的 loading 状态合并?或者有没有用 GraphQL 做聚合查询?我还在观望中……

暂无评论