Java面试微服务案例

wen java案例 2

本文目录导读:

Java面试微服务案例

  1. 目录导读(SEO优化结构)
  2. 第一天:微服务的“拆分”哲学
  3. 第二天:服务发现的“心跳”玄机
  4. 第三天:Feign拦截器的“隐藏炸弹”
  5. 第四天:Seata AT模式——无侵入的代价
  6. 第五天:Sentinel的“热点参数”限流
  7. 第六天:Apollo灰度发布的“标签路由”
  8. 第七天:SkyWalking——跨线程的传递丢失
  9. 第八天:Service Mesh——Istio的“边车”代价
  10. 第九天:设计秒杀系统——终极案例
  11. 第十天:分库分表——ShardingSphere的“灵魂拷问”

Java面试微服务案例深度解析:从原理到实战的12个核心问题

目录导读(SEO优化结构)

  1. 微服务架构的本质:为什么面试官总爱问“拆分”?
  2. 服务注册与发现:Eureka/Nacos/Consul 的终极对比
  3. 远程调用陷阱:Feign vs Dubbo,你真的选对了吗?
  4. 分布式事务:Seata AT模式与TCC的实战抉择
  5. 熔断降级:Sentinel 与 Hystrix 的源码级差异
  6. 配置中心:Apollo 的灰度发布如何设计?
  7. 链路追踪:SkyWalking 与 Zipkin 的选型逻辑
  8. 容器化部署:K8s 中 Service Mesh 的演进方向
  9. 高频面试题:如果让你设计一个秒杀系统微服务?
  10. 避坑指南:微服务拆分后数据库的“分库分表”难题

第一天:微服务的“拆分”哲学

面试官视角:当被问到“为什么用微服务?”时,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方案

  1. 创建灰度规则:按IP地址用户标签(如测试用户tester)匹配。
  2. 配置发布时选择“灰度发布”,系统会生成app.properties临时文件覆盖主配置。
  3. 通过ConfigService读取ConfigFileFormat.Properties时,会优先读取灰度配置。

避坑提示:灰度发布的配置变更不会自动通知客户端重启,但ApolloConfigChangeListener会回调,需确保监听器实现幂等逻辑。


第七天:SkyWalking——跨线程的传递丢失

真实事故:某公司异步线程池ExecutorService提交任务后,链路追踪ID从trace-123变成了null,原因:skywalkingTraceSegment存储在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。


第九天:设计秒杀系统——终极案例

标准架构

  1. 前端:按钮置灰+限流(Nginx 1秒2次)。
  2. 接入层:Spring Cloud Gateway 基于RequestRateLimiter过滤器,使用Redis令牌桶(Key=用户ID),每秒生成1000个令牌。
  3. 业务层:订单服务接收请求,写Redis预减库存(使用Lua脚本保证原子性),再异步发送MQ(rocketmq.order.create)。
  4. 削峰:消费者线程池设置corePoolSize=10, maxPoolSize=20,队列容量5000,超过则快速失败并返回“排队中”。

难点:MQ消费失败时,需要@TransactionalEventListener(phase = AFTER_COMMIT)确保下单事务提交后再发消息。


第十天:分库分表——ShardingSphere的“灵魂拷问”

高频问题

Q:订单表3亿数据,如何查询最近30天订单? A:先按订单ID取模分库(4库),再按create_time分表(每月一张表),但这样查询条件必须带“订单ID”才能路由,若按用户ID查询,需要维护“用户-订单映射表”或用Sharding-JDBChint强制路由。

进阶方案:采用冷热分离:订单完成90天后归档至order_history表,业务表只保留活跃数据(约2000万),这样单表查询性能在毫秒级。


微服务面试不是背概念,而是检验候选人在生产环境中的取舍能力,本文的每个案例都对应一个真实踩坑点:从服务发现的自我保护误判,到Feign请求头丢失,再到Seata锁冲突,建议读者在准备面试时,亲手搭建一个包含“订单-库存-支付”的分布式项目,通过压测工具(JMeter)模拟高并发,观察Sentinel的限流曲线和Seata的undo_log表变化,面试官最看重的是你能否说清“为什么这么做”而非“做了什么”。

抱歉,评论功能暂时关闭!