Java大厂面试:Spring Cloud+Redis+Kafka微服务架构实战全解析
Java大厂面试:Spring Cloud+Redis+Kafka微服务架构实战全解析
前言
在互联网大厂面试中,微服务架构相关的技术问题几乎是必考内容。本文通过一场真实的面试场景,以严肃的面试官和搞笑的水货程序员"谢飞机"的对话形式,带你深入了解Java微服务面试的核心知识点。
面试场景:电商平台技术岗
面试官:(严肃脸)你好,请坐。我是今天的技术面试官。看你简历上写了有电商项目经验,那我们今天就围绕电商平台的技术架构来聊聊。
谢飞机:(紧张地搓手)好的好的,面试官好!我...我做过电商项目,就是那个...让用户买东西的平台。
面试官:(微微点头)嗯,基础认知还是有的。那我们开始第一轮问题。
第一轮:基础架构与Web框架
面试官:问题1:你们的电商项目用的是什么Web框架?为什么选择Spring Boot而不是其他框架?
谢飞机:(眼睛一亮)这个我会!我们用的是Spring Boot!因为它...因为它很流行!而且配置简单,开箱即用,不用像以前那样写一堆XML配置了。
面试官:(露出满意的笑容)不错,回答到了关键点。那问题2:Spring Boot的自动配置原理是什么?
谢飞机:(开始冒汗)自动配置...就是...它会自动帮你配置一些东西。好像是扫描什么注解...@SpringBootApplication?里面有个@EnableAutoConfiguration...
面试官:(点头)继续说。
谢飞机:(支支吾吾)然后...然后它会读取META-INF目录下的配置文件,加载一些配置类...具体怎么加载的...我...我回去再研究研究。
面试官:(记录)好,问题3:电商项目中,你们如何处理高并发下的商品详情页访问?
谢飞机:(来精神了)这个我用过Redis缓存!把商品详情缓存到Redis里,用户访问的时候先查缓存,缓存没有再查数据库。
面试官:(赞许)很好,有实战经验。那问题4:缓存穿透、缓存击穿、缓存雪崩有什么区别?怎么解决?
谢飞机:(眼神开始飘忽)穿透...就是缓存没有,数据库也没有?击穿...是热点key过期?雪崩...是大面积过期?解决方案...可以用布隆过滤器...加锁...随机过期时间...
面试官:(继续记录)嗯,概念知道,但不够深入。问题5:你们的项目用的是什么数据库连接池?为什么?
谢飞机:(松口气)HikariCP!因为它性能好,启动快,Spring Boot默认就用它。
面试官:(微笑)这轮基础还可以,我们进入第二轮。
第二轮:微服务与消息队列
面试官:问题1:你们的电商系统是怎么拆分成微服务的?
谢飞机:(自信)我们按业务拆分!有用户服务、订单服务、商品服务、支付服务...大概十来个服务吧。
面试官:问题2:服务之间怎么调用?用的什么框架?
谢飞机:用OpenFeign!声明式的,写个接口加个@FeignClient注解就行,很方便。
面试官:(点头)问题3:如果某个服务挂了,怎么保证系统不崩溃?
谢飞机:(思考状)这个...用熔断器!Resilience4j或者Hystrix...服务挂了就直接返回默认值,不让故障扩散。
面试官:(满意)很好。问题4:订单创建成功后,需要通知库存服务扣减库存、通知积分服务加积分、通知消息服务发短信,这种场景你们怎么做的?
谢飞机:(眼睛放光)用消息队列!Kafka!订单服务发消息,其他服务订阅,解耦又异步。
面试官:(追问)问题5:那消息重复消费怎么处理?消息丢失怎么保证?
谢飞机:(开始含糊)重复消费...让业务接口幂等...用唯一标识...消息丢失...Kafka有ACK机制...配置all...具体配置参数我...我得查查文档...
面试官:(记录)概念清楚,细节需要加强。第三轮。
第三轮:分布式事务与监控
面试官:问题1:刚才说的订单场景,涉及多个服务,怎么保证数据一致性?
谢飞机:(紧张)分布式事务...我们用...Seata!有AT模式、TCC模式...
面试官:问题2:Seata的AT模式原理是什么?
谢飞机:(擦汗)原理...就是...它会记录前后镜像...然后...如果失败就回滚...具体怎么记录的...我...我实际项目中是封装好的,没太深入研究...
面试官:(理解地点头)问题3:线上服务出问题,你们怎么监控和排查?
谢飞机:(恢复自信)监控用Prometheus+Grafana!日志用ELK Stack!链路追踪用SkyWalking或者Zipkin!
面试官:问题4:JVM调优做过吗?线上OOM怎么排查?
谢飞机:(眼神游离)JVM调优...调过...就是调整堆大小...Xms、Xmx...OOM的话...用jmap dump内存...然后用MAT分析...但实际生产环境...都是运维同事处理的...
面试官:(微笑)问题5:最后一个问题,如果让你设计一个秒杀系统,你会考虑哪些技术点?
谢飞机:(兴奋)这个我准备过!Redis预减库存、Kafka削峰填谷、限流熔断、数据库分库分表、静态资源CDN...
面试官:(打断)具体怎么实现Redis预减库存?
谢飞机:(卡壳)就是...用Lua脚本...保证原子性...具体脚本怎么写...我...我回去写一下...
面试官:(合上简历)好的,今天面试就到这里。技术基础还可以,有些深度问题需要再加强。我们会在一周内通知你结果,请回家等通知。
谢飞机:(起身鞠躬)谢谢面试官!我等通知!
详细答案解析(小白学习区)
第一轮答案详解
1. Spring Boot自动配置原理
业务场景:电商项目快速启动,减少配置工作量
技术原理:
@SpringBootApplication
= @SpringBootConfiguration + @EnableAutoConfiguration + @ComponentScan
@EnableAutoConfiguration
= @AutoConfigurationPackage + @Import(AutoConfigurationImportSelector.class)
AutoConfigurationImportSelector
→ 读取 META-INF/spring.factories (Spring Boot 2.7前)
→ 读取 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports (Spring Boot 2.7+)
→ 加载所有自动配置类
→ 根据@ConditionalOnClass等条件注解按需启用
核心代码示例:
@Configuration
@ConditionalOnClass(RedisTemplate.class)
@EnableConfigurationProperties(RedisProperties.class)
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public RedisTemplate<String, Object> redisTemplate(
RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
return template;
}
}
2. 缓存三大问题及解决方案
业务场景:商品详情页高并发访问
| 问题 | 原因 | 解决方案 | |------|------|----------| | 缓存穿透 | 查询不存在的数据,缓存和数据库都没有 | 布隆过滤器、缓存空对象 | | 缓存击穿 | 热点key过期,大量请求打到数据库 | 互斥锁、逻辑过期 | | 缓存雪崩 | 大量key同时过期 | 随机过期时间、高可用集群 |
代码示例-布隆过滤器:
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
return Redisson.create(config);
}
// 使用
RBloomFilter<String> bloomFilter = redisson.getBloomFilter("productFilter");
bloomFilter.tryInit(1000000L, 0.03); // 预期100万数据,3%误判率
bloomFilter.add("product_1001");
// 查询时先判断
if (!bloomFilter.contains(productId)) {
return null; // 直接返回,不查数据库
}
代码示例-互斥锁解决击穿:
public Product getProduct(String productId) {
// 1. 查缓存
Product product = redisTemplate.opsForValue().get("product:" + productId);
if (product != null) {
return product;
}
// 2. 缓存未命中,加锁
String lockKey = "lock:product:" + productId;
boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
if (locked) {
try {
// 3. 双重检查
product = redisTemplate.opsForValue().get("product:" + productId);
if (product != null) {
return product;
}
// 4. 查数据库
product = productMapper.selectById(productId);
// 5. 写缓存
redisTemplate.opsForValue().set(
"product:" + productId,
product,
30 + new Random().nextInt(60),
TimeUnit.MINUTES
);
} finally {
// 6. 释放锁
redisTemplate.delete(lockKey);
}
} else {
// 7. 等待后重试
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
return getProduct(productId);
}
return product;
}
3. HikariCP连接池优势
业务场景:数据库连接管理,提升查询性能
核心配置:
spring:
datasource:
hikari:
maximum-pool-size: 20 # 最大连接数
minimum-idle: 5 # 最小空闲连接
connection-timeout: 30000 # 连接超时时间
idle-timeout: 600000 # 空闲超时时间
max-lifetime: 1800000 # 连接最大生命周期
pool-name: ProductHikariCP # 连接池名称
优势对比:
- 字节码级别优化,性能最优
- 连接池大小动态调整
- 快速故障检测
- Spring Boot默认集成
第二轮答案详解
1. OpenFeign服务调用
业务场景:微服务间RESTful接口调用
配置示例:
@FeignClient(name = "inventory-service", fallback = InventoryFallback.class)
public interface InventoryClient {
@PostMapping("/api/inventory/deduct")
Result<Boolean> deductStock(@RequestBody StockDeductDTO dto);
}
// 熔断降级处理
@Component
public class InventoryFallback implements InventoryClient {
@Override
public Result<Boolean> deductStock(StockDeductDTO dto) {
return Result.fail("库存服务繁忙,请稍后重试");
}
}
开启配置:
feign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 10000
compression:
request:
enabled: true
response:
enabled: true
2. Kafka消息可靠性保证
业务场景:订单创建后异步通知多服务
消息不丢失配置:
spring:
kafka:
producer:
acks: all # 所有副本确认
retries: 3 # 重试次数
properties:
min.insync.replicas: 2 # 最小同步副本数
consumer:
enable-auto-commit: false # 手动提交offset
properties:
isolation.level: read_committed
幂等性处理代码:
@KafkaListener(topics = "order-created", groupId = "inventory-group")
public void handleOrderCreated(OrderMessage message, Acknowledgment ack) {
String messageId = message.getMessageId();
// 1. 检查是否已处理(幂等表)
if (messageService.isProcessed(messageId)) {
ack.acknowledge();
return;
}
// 2. 业务处理
try {
inventoryService.deductStock(message.getOrderId(), message.getItems());
// 3. 记录已处理
messageService.recordProcessed(messageId);
// 4. 提交offset
ack.acknowledge();
} catch (Exception e) {
// 5. 异常处理,不提交offset,Kafka会重试
log.error("处理订单消息失败", e);
throw e;
}
}
3. Resilience4j熔断配置
业务场景:防止服务雪崩
resilience4j:
circuitbreaker:
instances:
inventoryService:
slidingWindowSize: 10 # 滑动窗口大小
failureRateThreshold: 50 # 失败率阈值50%
waitDurationInOpenState: 30s # 打开状态等待时间
permittedNumberOfCallsInHalfOpenState: 5 # 半开状态允许请求数
retry:
instances:
inventoryService:
maxAttempts: 3 # 最大重试次数
waitDuration: 1s # 重试间隔
ratelimiter:
instances:
inventoryService:
limitForPeriod: 100 # 周期内允许请求数
limitRefreshPeriod: 1s # 周期时间
使用代码:
@Service
public class OrderService {
@Autowired
private InventoryClient inventoryClient;
@CircuitBreaker(name = "inventoryService", fallbackMethod = "deductStockFallback")
@Retry(name = "inventoryService")
@RateLimiter(name = "inventoryService")
public Result<Boolean> deductStock(Long orderId, List<OrderItem> items) {
return inventoryClient.deductStock(new StockDeductDTO(orderId, items));
}
public Result<Boolean> deductStockFallback(Long orderId, List<OrderItem> items, Throwable t) {
log.warn("库存服务降级", t);
return Result.fail("库存服务暂时不可用");
}
}
第三轮答案详解
1. Seata分布式事务AT模式
业务场景:订单、库存、积分多服务数据一致性
工作原理:
1. 一阶段:
- 业务数据和回滚日志在同一本地事务提交
- 记录数据前后镜像到undo_log表
2. 二阶段提交:
- 异步删除undo_log记录
3. 二阶段回滚:
- 根据undo_log生成反向SQL
- 恢复数据到一阶段前的状态
配置示例:
// 全局事务注解
@GlobalTransactional
public Result<OrderVO> createOrder(OrderCreateDTO dto) {
// 1. 创建订单
Order order = orderService.create(dto);
// 2. 扣减库存(远程调用)
inventoryClient.deductStock(order.getId(), dto.getItems());
// 3. 增加积分(远程调用)
pointClient.addPoints(order.getUserId(), order.getTotalAmount());
// 4. 发送消息
kafkaTemplate.send("order-created", order);
return Result.success(order.toVO());
}
undo_log表结构:
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
);
2. Prometheus+Grafana监控
业务场景:服务性能监控与告警
Micrometer集成:
@Configuration
public class MetricsConfig {
@Bean
MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
return registry -> registry.config()
.commonTags("application", "order-service");
}
// 自定义业务指标
@Autowired
public void registerCustomMetrics(MeterRegistry registry) {
Gauge.builder("order.count", this, OrderService::getOrderCount)
.description("订单总数")
.register(registry);
Timer.builder("order.create.duration")
.description("订单创建耗时")
.register(registry);
}
}
Prometheus配置:
scrape_configs:
- job_name: 'order-service'
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['order-service:8080']
scrape_interval: 15s
Grafana告警规则:
# 订单创建失败率告警
- alert: OrderCreateFailureRate
expr: rate(order_create_total{status="failed"}[5m]) / rate(order_create_total[5m]) > 0.1
for: 5m
labels:
severity: warning
annotations:
summary: "订单创建失败率超过10%"
3. JVM OOM排查流程
业务场景:生产环境内存溢出问题定位
排查步骤:
1. 保留现场:jmap -dump:format=b,file=heap.hprof <pid>
2. 分析工具:MAT(JMemoryAnalyzer)、JVisualVM
3. 常见问题:
- 内存泄漏:大对象未释放、静态集合持续增长
- 内存不足:堆大小配置过小、并发量大
4. 解决方案:
- 优化代码:及时释放资源、使用弱引用
- 调整参数:-Xms、-Xmx、-XX:MaxMetaspaceSize
推荐JVM参数:
-Xms4g -Xmx4g # 堆内存
-XX:MetaspaceSize=256m # 元空间初始大小
-XX:MaxMetaspaceSize=512m # 元空间最大大小
-XX:+UseG1GC # 使用G1垃圾收集器
-XX:MaxGCPauseMillis=200 # 最大GC停顿时间
-XX:+HeapDumpOnOutOfMemoryError # OOM时自动dump
-XX:HeapDumpPath=/data/logs/heap # dump文件路径
-Xlog:gc*:file=/data/logs/gc.log:time,uptime:filecount=5,filesize=10M
4. 秒杀系统核心设计
业务场景:高并发商品秒杀
架构设计:
用户请求 → CDN静态资源 → Nginx限流 → 网关鉴权
→ Redis预减库存 → Kafka削峰 → 订单服务 → 数据库
Redis Lua脚本预减库存:
-- KEYS[1]: 库存key
-- KEYS[2]: 用户购买记录key
-- ARGV[1]: 商品ID
-- ARGV[2]: 用户ID
-- ARGV[3]: 购买数量
-- 1. 检查是否重复购买
if redis.call('EXISTS', KEYS[2]) == 1 then
return -1 -- 重复购买
end
-- 2. 检查库存
local stock = tonumber(redis.call('GET', KEYS[1]))
if stock == nil or stock <= 0 then
return 0 -- 库存不足
end
-- 3. 预减库存
local newStock = redis.call('DECRBY', KEYS[1], ARGV[3])
if newStock < 0 then
redis.call('INCRBY', KEYS[1], ARGV[3]) -- 回滚
return 0 -- 库存不足
end
-- 4. 记录用户购买
redis.call('SETEX', KEYS[2], 3600, ARGV[1])
return 1 -- 成功
Java调用代码:
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private DefaultRedisScript<Long> redisScript;
public Result<Boolean> seckill(Long userId, Long productId, Integer quantity) {
String stockKey = "seckill:stock:" + productId;
String userKey = "seckill:user:" + userId + ":" + productId;
Long result = redisTemplate.execute(
redisScript,
Arrays.asList(stockKey, userKey),
productId.toString(), userId.toString(), quantity.toString()
);
if (result == 1) {
// 发送消息到Kafka,异步创建订单
kafkaTemplate.send("seckill-order", new SeckillMessage(userId, productId, quantity));
return Result.success("抢购成功");
} else if (result == 0) {
return Result.fail("库存不足");
} else {
return Result.fail("请勿重复购买");
}
}
总结与学习建议
面试准备要点
- 基础扎实:Spring Boot原理、JVM基础、数据库优化
- 实战经验:有真实项目经历,能说清楚技术选型原因
- 深度理解:不仅会用,还要知道原理和源码
- 系统思维:能从整体架构角度思考问题
学习路线推荐
Java基础 → Spring Boot → 微服务 → 分布式 → 高并发
↓ ↓ ↓ ↓ ↓
集合/JVM 自动配置 Spring Cloud 缓存/消息 秒杀/限流
推荐资源
- 书籍:《深入理解Java虚拟机》《Spring Boot实战》
- 源码:Spring Boot、Spring Cloud Alibaba
- 实战:自己搭建完整的电商微服务项目
- 社区:CSDN、GitHub、Stack Overflow
最后提醒:面试是双向选择,保持自信,诚实回答。不会的问题可以表示"这个我了解不深,但我的理解是...",展现学习能力和思考过程比死记硬背更重要。
祝大家面试顺利,拿到心仪的Offer!🎉
更多推荐




所有评论(0)