微服务架构实战中的常见陷阱与优化策略

FSD-莉娟 框架 阅读 1,373
赞 24 收藏
二维码
手机扫码查看
反馈

微服务拆分后,我差点被通信问题搞崩溃

去年我们团队把一个单体应用拆成微服务,前端也跟着调整。一开始觉得不就是多调几个接口嘛,结果上线第一天就炸了——用户登录后首页白屏,控制台一堆跨域错误。折腾到凌晨三点才发现,网关没配好,子服务的 CORS 策略各自为政,有的允许所有源,有的只认 localhost。这种混乱在单体里根本不会出现。

微服务架构实战中的常见陷阱与优化策略

从那以后,我给自己定了条铁律:微服务前端集成,必须统一入口、统一错误处理、统一认证透传。下面说说我现在怎么搞的。

我的写法,亲测靠谱:封装一个微服务请求层

别再在每个组件里直接写 fetchaxios 调不同域名了。我见过有人这样写:

// 别这么干!
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 做聚合查询?我还在观望中……

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论