本文目录导读:

- 目录导读(SEO优化结构)
- 第一天:微服务的“拆分”哲学
- 第二天:服务发现的“心跳”玄机
- 第三天:Feign拦截器的“隐藏炸弹”
- 第四天:Seata AT模式——无侵入的代价
- 第五天:Sentinel的“热点参数”限流
- 第六天:Apollo灰度发布的“标签路由”
- 第七天:SkyWalking——跨线程的传递丢失
- 第八天:Service Mesh——Istio的“边车”代价
- 第九天:设计秒杀系统——终极案例
- 第十天:分库分表——ShardingSphere的“灵魂拷问”
Java面试微服务案例深度解析:从原理到实战的12个核心问题
目录导读(SEO优化结构)
- 微服务架构的本质:为什么面试官总爱问“拆分”?
- 服务注册与发现:Eureka/Nacos/Consul 的终极对比
- 远程调用陷阱:Feign vs Dubbo,你真的选对了吗?
- 分布式事务:Seata AT模式与TCC的实战抉择
- 熔断降级:Sentinel 与 Hystrix 的源码级差异
- 配置中心:Apollo 的灰度发布如何设计?
- 链路追踪:SkyWalking 与 Zipkin 的选型逻辑
- 容器化部署:K8s 中 Service Mesh 的演进方向
- 高频面试题:如果让你设计一个秒杀系统微服务?
- 避坑指南:微服务拆分后数据库的“分库分表”难题
第一天:微服务的“拆分”哲学
面试官视角:当被问到“为什么用微服务?”时,80%的候选人会背诵“高可用、高性能”等词汇,但真正的考察点在于业务边界划分。
经典案例:
某电商平台将订单服务拆分为:订单确认、订单查询、订单状态机 三个独立服务,表面看增加了复杂度,但实际将高并发读写分离——确认服务使用Redis缓存+异步MQ,查询服务走ElasticSearch。
灵魂拷问:
Q:你拆分的依据是什么? A:依据DDD(领域驱动设计)中的限界上下文,订单”在
下单时属于交易域,在售后时属于履约域,盲目按AKF轴拆分(X轴水平复制、Y轴业务功能、Z轴数据分区)会导致过度设计。
第二天:服务发现的“心跳”玄机
必考原理: Eureka采用自我保护机制(默认关闭):当15分钟内85%的服务心跳异常,它不会剔除任何实例,而是进入“只读”状态,Nacos则引入临时/持久实例概念:临时实例用临时节点存储(删除节点),持久实例用持久节点(注册后即使宕机也存在)。
实战案例: 某金融项目使用Nacos,因网络抖动导致大量临时实例被误删(因为Nacos的客户端默认每5秒发送一次心跳,超过15秒未收到即判死),最终解决方案:
spring.cloud.nacos.discovery.heart-beat-timeout: 3000 # 调短 spring.cloud.nacos.discovery.ip-delete-timeout: 5000 # 加速淘汰
Q:Eureka和Nacos的“优雅下线”有什么区别? A:Eureka需要显式调用
ServiceRegistry.deregister();Nacos则通过HTTP API(DELETE /nacos/v1/ns/instance)主动移除,但两者都面临最终一致性问题——注册中心容灾时可能短暂出现服务缺失。
第三天:Feign拦截器的“隐藏炸弹”
高频错误案例:所有服务通过Feign传递用户信息,直接在方法参数里加UserDTO,但服务间调用链超过3层时,请求头会因拦截器缺失而丢失。
标准化方案:
public class FeignRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
// 从ThreadLocal或TraceId传递用户ID
String userId = UserContext.getUserId();
template.header("X-User-Id", userId);
}
}
同时开启@EnableFeignClients(defaultConfiguration = FeignConfig.class)避免配置类被Spring Boot的全局扫描污染。
Q:Feign的
Logger.Level为何默认是NONE? A:因为Feign默认使用LoggerFactory,如果开启了BASIC级别,会打印所有请求头导致敏感信息泄露,生产环境建议用FULL级别配合日志脱敏框架。
第四天:Seata AT模式——无侵入的代价
AT模式原理:通过undo_log表记录数据快照,执行SQL前生成“前镜像”,提交时对比“后镜像”并生成“回滚SQL”,但性能瓶颈在于:全局锁(for update)在写操作时锁住整个分布式事务资源,吞吐量下降30%。
实际生产案例:
某支付服务采用TCC(Try-Confirm-Cancel)替代AT,通过@LocalTCC注解:
@LocalTCC
public interface AccountService {
@TwoPhaseBusinessAction(name="deduct", commitMethod="confirm", rollbackMethod="cancel")
void tryDeduct(BusinessActionContext ctx, @BusinessActionContextParameter("amount") Double amount);
}
TCC的空回滚和悬挂处理是难点(需要幂等控制)。
第五天:Sentinel的“热点参数”限流
面试官常问:Sentinel的@SentinelResource和Hystrix的@HystrixCommand差别在哪?
核心答案:
- Hystrix基于线程池隔离,每个依赖一个线程池(默认10线程),资源开销大。
- Sentinel基于信号量隔离(默认
maxConcurrentRequests=20),更轻量。 - Sentinel支持热点参数限流:如
@SentinelResource(value="getProduct", blockHandler="handleBlock")+@HotParam可对特定参数值(如商品ID=1)设置专属限流阈值。
实战用例:秒杀场景中,对库存ID参数限流10次/秒,而其他参数限流100次/秒。
第六天:Apollo灰度发布的“标签路由”
经典问题:如何做到只对10%用户发布新配置?
Apollo方案:
- 创建灰度规则:按
IP地址、用户标签(如测试用户tester)匹配。 - 配置发布时选择“灰度发布”,系统会生成
app.properties临时文件覆盖主配置。 - 通过
ConfigService读取ConfigFileFormat.Properties时,会优先读取灰度配置。
避坑提示:灰度发布的配置变更不会自动通知客户端重启,但ApolloConfigChangeListener会回调,需确保监听器实现幂等逻辑。
第七天:SkyWalking——跨线程的传递丢失
真实事故:某公司异步线程池ExecutorService提交任务后,链路追踪ID从trace-123变成了null,原因:skywalking的TraceSegment存储在ThreadLocal中,子线程无法继承。
解法:
// 使用包装器传递上下文
ExecutorService pool = new ThreadPoolExecutor(2,4,0L,TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(10), new SkywalkingContextPropagator());
// 实现:在execute()时捕获当前traceId,在run()前重新注入
更稳妥的方式是使用MQ传递traceId(Kafka消息头中携带)。
第八天:Service Mesh——Istio的“边车”代价
面试题:何时应放弃Service Mesh?
答案:当微服务数量 < 20个,且团队没有K8s运维经验时,Service Mesh的注水流量(每个Pod额外消耗20%CPU)和配置复杂度(VirtualService/DestinationRule)会严重拖慢开发。
替代方案:用Spring Cloud Gateway + Nacos实现跨语言调用,而非装Envoy。
第九天:设计秒杀系统——终极案例
标准架构:
- 前端:按钮置灰+限流(Nginx 1秒2次)。
- 接入层:Spring Cloud Gateway 基于
RequestRateLimiter过滤器,使用Redis令牌桶(Key=用户ID),每秒生成1000个令牌。 - 业务层:订单服务接收请求,写Redis预减库存(使用Lua脚本保证原子性),再异步发送MQ(
rocketmq.order.create)。 - 削峰:消费者线程池设置
corePoolSize=10, maxPoolSize=20,队列容量5000,超过则快速失败并返回“排队中”。
难点:MQ消费失败时,需要@TransactionalEventListener(phase = AFTER_COMMIT)确保下单事务提交后再发消息。
第十天:分库分表——ShardingSphere的“灵魂拷问”
高频问题:
Q:订单表3亿数据,如何查询最近30天订单? A:先按
订单ID取模分库(4库),再按create_time分表(每月一张表),但这样查询条件必须带“订单ID”才能路由,若按用户ID查询,需要维护“用户-订单映射表”或用Sharding-JDBC的hint强制路由。
进阶方案:采用冷热分离:订单完成90天后归档至order_history表,业务表只保留活跃数据(约2000万),这样单表查询性能在毫秒级。
微服务面试不是背概念,而是检验候选人在生产环境中的取舍能力,本文的每个案例都对应一个真实踩坑点:从服务发现的自我保护误判,到Feign请求头丢失,再到Seata锁冲突,建议读者在准备面试时,亲手搭建一个包含“订单-库存-支付”的分布式项目,通过压测工具(JMeter)模拟高并发,观察Sentinel的限流曲线和Seata的undo_log表变化,面试官最看重的是你能否说清“为什么这么做”而非“做了什么”。