微服务常见高频问题与选型
❓ 面试官:微服务之间是怎么进行通信的?你们用的哪种?
频率:🔥🔥🔥🔥
💡 一句话总结(先抛结论): 微服务通信主要分为同步调用和异步调用。我们项目里,强依赖且需要立刻拿到结果的查询走同步的 HTTP(OpenFeign);不需要立刻拿到结果、或者为了解耦削峰的操作走异步的 MQ(如 RocketMQ/RabbitMQ)。
📝 详细通信方式盘点:
- HTTP/RESTful(目前最主流的同步方式):
- 代表组件:OpenFeign、RestTemplate。
- 优点:协议通用、跨语言、基于 JSON 调试极其方便(Postman 直接调)。
- 缺点:HTTP 协议头部比较臃肿,性能一般。
- RPC 协议(高性能的同步方式):
- 代表组件:Dubbo、gRPC。
- 优点:底层基于 TCP 长连接,使用自研的二进制序列化(如 Hessian、Protobuf),报文极小,性能比 HTTP 快很多。
- 缺点:调试相对麻烦(不能直接用浏览器发请求),多语言支持不如 HTTP(虽然现在 gRPC 很好)。
- 消息队列(MQ,异步解耦最佳实践):
- 如果 A 服务调用 B 服务,A 不需要等 B 返回结果,或者为了防止 B 挂了拖死 A,就应该把调用改成往 MQ 里扔一条消息,让 B 自己去排队消费。
❓ 面试官:在微服务架构下,用户登录的 Session 状态怎么共享?网关是怎么做鉴权的?【中高级】
频率:🔥🔥🔥🔥🔥
💡 一句话总结(先抛结论): 在微服务下我们彻底抛弃了传统的 JVM Session,因为服务是集群部署的,Session 无法在多台机器间共享。目前行业标配是采用 “网关统一鉴权 + JWT / Redis Token” 的方案。
📝 详细鉴权流转过程(面试高分模板):
方案:Gateway + JWT 无状态方案
- 登录阶段:用户携带账号密码请求登录。网关将请求路由到
AuthService(认证服务)。认证通过后,AuthService使用用户的 ID、角色等信息生成一个 JWT(JSON Web Token),并用服务器的私钥对其签名,最后返回给前端。 - 请求阶段:前端把 JWT 放到 HTTP 请求头的
Authorization: Bearer <token>里。 - 网关拦截阶段(核心防线):
- 请求到达 Gateway 网关。
- 网关配置了一个
GlobalFilter(全局过滤器)。它会拦截请求,拿出请求头里的 JWT。 - 网关使用公钥验证这个 JWT 的签名是否合法、是否过期。
- 如果非法,网关直接返回 401,请求根本不会打到后面的微服务,保护了内部系统。
- 如果合法,网关会把 JWT 里解析出来的
userId塞进请求的 Header 里,然后放行。
- 微服务接收阶段:下游具体的业务微服务(如订单服务),只需要通过
request.getHeader("userId")就能知道当前是谁在操作,完全不需要自己再去连数据库或校验 Token。
🌟 面试加分项(JWT 的坑点): "面试官您好,虽然纯 JWT 方案很轻量,但它有一个致命缺陷:无法主动踢人下线(Token 签发后在过期前无法作废)。 所以在我们公司实际落地的架构中,我们采用了 JWT + Redis 的混合模式: 用户登录后,生成的 JWT 依然返回给前端,同时我们会把这个 Token 作为 Key 存入 Redis 并设置过期时间。网关不仅要校验 JWT 的签名合法性,还要去 Redis 里查一下这个 Token 还在不在。如果我们想封禁某个用户,只需把 Redis 里的 Token 删掉,网关校验就会失败,完美实现了踢人下线功能。"
❓ 面试官:如果不用 Seata,你们还有其他方法解决分布式事务吗?(可靠消息最终一致性)【高级】
频率:🔥🔥🔥🔥
💡 一句话总结(先抛结论): Seata 的 AT 模式本质上还是偏向强一致性,性能还是会有所损耗。在高并发的互联网 C 端场景(如支付、下单)中,我们通常采用“基于 MQ 的可靠消息最终一致性”方案。它的核心是:允许短暂的数据不一致,但保证最终所有节点的数据一定是对的。
📝 详细落地实战(以 RocketMQ 事务消息为例): 假设业务场景是:A 服务(订单服务)负责创建订单,B 服务(积分服务)负责加积分。
- A 服务发送“半消息”:A 服务在执行本地创建订单的 SQL 之前,先向 RocketMQ 发送一条“加积分”的 Half 消息。此时这个消息对 B 服务是不可见的。
- A 服务执行本地事务:A 收到 MQ 的确认后,开始执行本地的数据库操作(
insert into order)。 - A 服务提交/回滚消息:
- 如果 A 本地 SQL 执行成功,向 MQ 发送
Commit。此时该消息变成对 B 可见,B 就能消费它去加积分了。 - 如果 A 本地 SQL 执行报错,向 MQ 发送
Rollback。MQ 会把这条半消息直接丢弃,B 不会加积分。(订单没建,积分也没加,数据一致)。
- 如果 A 本地 SQL 执行成功,向 MQ 发送
- 极端情况兜底(MQ 回查机制,核心亮点):
- 如果在第 3 步,A 执行完本地 SQL 后突然宕机了,没来得及发 Commit 或 Rollback 怎么办?
- RocketMQ 等了一会儿,发现这条半消息还没确切状态,它就会主动发起 回查(Check),调用 A 服务暴露的一个回查接口:“兄弟,你刚才那个订单建成功了吗?”
- A 服务的代码里,去数据库查一下那个订单号。如果查到了,说明成功了,补发 Commit;查不到,说明之前事务回滚了,补发 Rollback。
- 消费端兜底:B 服务消费这条消息去加积分。为了防止消费失败,B 会利用 MQ 的重试机制一直试。并且 B 服务的加积分接口必须做好幂等性(防重)设计,防止重复加分。