Java 大厂面试:Spring Cloud+Redis+Kafka 互联网医疗系统架构实战解析
Java 大厂面试:Spring Cloud+Redis+Kafka 互联网医疗系统架构实战解析
前言
在互联网医疗行业高速发展的今天,Java 工程师面临着更高的技术要求。本文通过一场真实的面试场景,带你深入了解互联网医疗系统的技术架构设计。
面试场景
面试官:(严肃脸)你好,请先做个自我介绍。
谢飞机:(紧张)面试官好,我叫谢飞机,毕业于...那个...家里蹲大学计算机系,有 3 年 Java 开发经验...
面试官:(点头)好,那我们直接进入技术环节。今天我们要聊的是互联网医疗场景下的系统架构设计。
第一轮:基础架构与服务治理
面试官:第一个问题,互联网医疗系统中,患者挂号、问诊、处方流转涉及多个服务,你们用什么做服务注册发现?
谢飞机:(自信)这个简单!我们用 Nacos 做服务注册中心,比 Eureka 新,支持配置中心,一举两得!
面试官:(微笑)不错,那服务之间怎么调用?
谢飞机:OpenFeign 啊,声明式调用,加个@FeignClient 注解就完事了!
面试官:(点头)好,那如果问诊服务调用处方服务超时了怎么办?
谢飞机:(挠头)呃...加个超时时间配置?实在不行...重试几次?
面试官:(严肃)只配置超时和重试够吗?如果处方服务挂了,问诊服务会不会被拖垮?
谢飞机:(冒汗)这个...那个...应该有个熔断机制吧?具体怎么配我回去查查文档...
面试官:(记录)好,下一个问题。医疗系统对数据一致性要求很高,你们数据库用的什么连接池?
谢飞机:(放松)HikariCP!性能最好,Spring Boot 默认就是这个!
面试官:(满意)不错,那数据库版本管理怎么做?
谢飞机:Flyway!每次发布自动执行 SQL 脚本,不会漏掉表结构变更!
第二轮:缓存与消息队列
面试官:(翻简历)好,基础不错。现在说缓存。患者查询医生排班信息,QPS 很高,你们怎么优化?
谢飞机:(来劲了)Redis 缓存啊!医生排班信息不常变,缓存起来,查询速度嗖嗖的!
面试官:缓存过期时间设多少?
谢飞机:(思考)排班一般一周不变,设 7 天?不对,医生可能临时调班...那设 1 小时?
面试官:(点头)可以,那缓存穿透、击穿、雪崩怎么预防?
谢飞机:(流利)穿透用布隆过滤器,击穿用互斥锁,雪崩给过期时间加随机值!
面试官:(满意)不错,看来缓存这块有研究。那患者问诊完成后,需要发送短信通知、生成电子病历、推送给保险公司,这些怎么解耦?
谢飞机:(自信)消息队列!Kafka 吞吐量高,适合这种场景!
面试官:为什么不用 RabbitMQ?
谢飞机:(犹豫)Kafka...Kafka 更...更流行?而且我们数据量大...
面试官:(追问)那消息可靠性怎么保证?患者没收到短信投诉怎么办?
谢飞机:(含糊)开启 ack 机制...重试...死信队列...具体配置我得问问运维...
面试官:(记录)好,那电子病历需要长期存储,你们用的什么?
谢飞机:MySQL 主表,Elasticsearch 做搜索,OSS 存 PDF 文件!
第三轮:安全、监控与分布式事务
面试官:(严肃)医疗系统涉及患者隐私,安全怎么做?
谢飞机:(认真)Spring Security+JWT!接口都加权限控制,敏感数据加密存储!
面试官:加密用什么算法?
谢飞机:AES...还是 RSA?反正 Spring Security 都支持,配置一下就行...
面试官:(皱眉)患者处方药涉及医保结算,如果扣款成功但处方状态没更新怎么办?
谢飞机:(紧张)分布式事务...Seata!AT 模式,无侵入...
面试官:AT 模式适合高并发场景吗?
谢飞机:(冒汗)这个...高并发可能用 TCC 更好?但 TCC 要写三个方法...我们项目用 AT 凑合着...
面试官:(继续)系统上线后怎么监控?
谢飞机:Prometheus+Grafana!CPU、内存、JVM 指标都监控,告警发钉钉!
面试官:链路追踪呢?一个请求经过多个服务,出问题怎么定位?
谢飞机:(模糊)SkyWalking...还是 Zipkin?我们好像装了一个,具体名字我忘了...
面试官:(合上简历)最后一个问题,JVM 调优做过吗?
谢飞机:(松口气)做过!-Xms -Xmx 设成一样,避免动态扩容,GC 日志打开分析...
面试官:Full GC 频繁怎么排查?
谢飞机:(卡壳)这个...用 jmap dump 内存...然后 MAT 分析...但我没实际遇到过...
面试结束
面试官:(站起来)好了,今天先到这里。技术面表现...(停顿)还行,有些问题需要再深入。HR 会联系你,回家等通知吧。
谢飞机:(起身)谢谢面试官!那个...大概多久有通知啊?
面试官:(微笑)一周内吧。对了,回去把 Resilience4j 熔断配置和 Seata TCC 模式好好看看。
谢飞机:(内心 OS)完了,刚才熔断和事务那块露馅了...
详细答案解析
第一轮答案:基础架构与服务治理
1. 服务注册发现 - Nacos
业务场景:互联网医疗系统包含患者服务、医生服务、问诊服务、处方服务、支付服务等 10+ 微服务,需要统一的服务注册与发现机制。
技术方案:
# application.yml
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
namespace: medical-prod
group: MEDICAL_GROUP
核心优势:
- 支持 AP/CP 模式切换
- 集成配置中心,减少组件
- 支持权重路由,实现灰度发布
- 健康检查更及时
2. 服务调用 - OpenFeign
业务场景:问诊服务需要调用患者服务获取病史、调用处方服务开具处方。
技术方案:
@FeignClient(name = "prescription-service", fallback = PrescriptionFallback.class)
public interface PrescriptionClient {
@PostMapping("/api/prescription/create")
Result<PrescriptionVO> createPrescription(@RequestBody PrescriptionDTO dto);
}
// 熔断降级实现
@Component
public class PrescriptionFallback implements PrescriptionClient {
@Override
public Result<PrescriptionVO> createPrescription(PrescriptionDTO dto) {
return Result.fail("处方服务繁忙,请稍后重试");
}
}
3. 容错保护 - Resilience4j
业务场景:处方服务响应慢或宕机时,防止问诊服务被拖垮。
技术方案:
resilience4j:
circuitbreaker:
instances:
prescriptionService:
slidingWindowSize: 10
failureRateThreshold: 50
waitDurationInOpenState: 30000
permittedNumberOfCallsInHalfOpenState: 5
timelimiter:
instances:
prescriptionService:
timeoutDuration: 3s
retry:
instances:
prescriptionService:
maxAttempts: 3
waitDuration: 1s
熔断状态说明:
- CLOSED:正常状态,请求正常处理
- OPEN:熔断状态,直接返回降级结果
- HALF_OPEN:半开状态,允许少量请求探测服务是否恢复
4. 数据库连接池 - HikariCP
业务场景:医疗系统读写频繁,需要高性能连接池。
技术方案:
spring:
datasource:
hikari:
maximum-pool-size: 20
minimum-idle: 10
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
5. 数据库版本管理 - Flyway
业务场景:多环境部署,确保数据库表结构一致。
技术方案:
src/main/resources/db/migration/
├── V1.0.0__create_patient_table.sql
├── V1.0.1__create_doctor_table.sql
├── V1.0.2__create_prescription_table.sql
└── V1.0.3__add_insurance_column.sql
第二轮答案:缓存与消息队列
1. Redis 缓存架构
业务场景:医生排班信息查询 QPS 达 5000+,数据库无法承受。
技术方案:
@Service
public class ScheduleService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 三级缓存:Caffeine + Redis + 数据库
@Cacheable(value = "schedule", key = "#doctorId + '_' + #date",
unless = "#result == null")
public ScheduleVO getSchedule(Long doctorId, LocalDate date) {
// 缓存穿透保护
String cacheKey = "schedule:" + doctorId + "_" + date;
ScheduleVO schedule = (ScheduleVO) redisTemplate.opsForValue().get(cacheKey);
if (schedule == null) {
// 布隆过滤器检查
if (!bloomFilter.mightContain(cacheKey)) {
return null; // 肯定不存在
}
// 互斥锁防止缓存击穿
RLock lock = redissonClient.getLock("lock:" + cacheKey);
if (lock.tryLock()) {
try {
schedule = scheduleMapper.selectByDoctorAndDate(doctorId, date);
if (schedule == null) {
// 空值也缓存,防止穿透
redisTemplate.opsForValue().set(cacheKey, null, 5, TimeUnit.MINUTES);
} else {
// 随机过期时间防止雪崩
int expireTime = 3600 + new Random().nextInt(600);
redisTemplate.opsForValue().set(cacheKey, schedule, expireTime, TimeUnit.SECONDS);
}
} finally {
lock.unlock();
}
} else {
// 等待后重试
Thread.sleep(100);
return getSchedule(doctorId, date);
}
}
return schedule;
}
}
缓存问题解决方案: | 问题 | 解决方案 | 代码实现 | |------|----------|----------| | 缓存穿透 | 布隆过滤器 + 空值缓存 | bloomFilter.mightContain() | | 缓存击穿 | 互斥锁 | redissonClient.getLock() | | 缓存雪崩 | 随机过期时间 | 3600 + random(600) |
2. 消息队列 - Kafka
业务场景:问诊完成后需要异步处理短信通知、病历生成、保险推送等任务。
技术方案:
# Kafka 配置
spring:
kafka:
bootstrap-servers: 127.0.0.1:9092
producer:
acks: all # 所有副本确认
retries: 3
properties:
enable.idempotence: true # 幂等性
consumer:
auto-offset-reset: earliest
enable-auto-commit: false # 手动提交
// 生产者
@Service
public class ConsultationEventProducer {
@Autowired
private KafkaTemplate<String, Object> kafkaTemplate;
public void sendConsultationComplete(ConsultationEvent event) {
kafkaTemplate.send("consultation-complete", event.getOrderId(), event)
.addCallback(
success -> log.info("消息发送成功:{}", event.getOrderId()),
failure -> log.error("消息发送失败:{}", event.getOrderId(), failure)
);
}
}
// 消费者
@Component
public class ConsultationEventConsumer {
@KafkaListener(topics = "consultation-complete", groupId = "medical-group")
public void consume(ConsultationEvent event, Acknowledgment ack) {
try {
// 发送短信
smsService.send(event.getPatientPhone());
// 生成病历
medicalRecordService.generate(event);
// 推送保险
insuranceService.push(event);
ack.acknowledge(); // 手动提交
} catch (Exception e) {
log.error("处理消息失败", e);
// 发送死信队列
kafkaTemplate.send("consultation-complete-dlx", event);
}
}
}
消息可靠性保证:
- 生产者 acks=all,确保所有副本写入
- 开启幂等性,防止重复发送
- 消费者手动提交 offset
- 死信队列处理失败消息
- 消息落盘,持久化存储
3. 电子病历存储架构
业务场景:电子病历需要支持全文搜索、长期存储、合规审计。
技术方案:
┌─────────────────────────────────────────────┐
│ 电子病历存储架构 │
├─────────────┬───────────────┬───────────────┤
│ MySQL │ Elasticsearch │ OSS │
│ 结构化数据 │ 全文检索 │ PDF/影像文件 │
│ 病历元数据 │ 症状搜索 │ 长期存储 │
└─────────────┴───────────────┴───────────────┘
第三轮答案:安全、监控与分布式事务
1. 安全认证 - Spring Security + JWT
业务场景:患者、医生、管理员不同角色权限控制,敏感数据加密。
技术方案:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/api/patient/**").hasRole("PATIENT")
.antMatchers("/api/doctor/**").hasRole("DOCTOR")
.antMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); // 密码加密
}
}
// JWT 工具类
@Component
public class JwtUtil {
private static final String SECRET = "medical-secret-key";
private static final long EXPIRE = 7200000; // 2 小时
public String generateToken(UserDetails userDetails) {
return Jwts.builder()
.setSubject(userDetails.getUsername())
.claim("role", userDetails.getAuthorities())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
.signWith(SignatureAlgorithm.HS256, SECRET)
.compact();
}
}
敏感数据加密:
@Component
public class DataEncryptionUtil {
// 患者身份证号加密
public String encryptIdCard(String idCard) throws Exception {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, getSecretKey());
byte[] encrypted = cipher.doFinal(idCard.getBytes());
return Base64.getEncoder().encodeToString(encrypted);
}
}
2. 分布式事务 - Seata
业务场景:医保扣款与处方状态更新需要保证原子性。
AT 模式方案:
@GlobalTransactional
public void processInsurancePayment(PaymentDTO dto) {
// 1. 扣减医保账户
insuranceAccountService.deduct(dto.getPatientId(), dto.getAmount());
// 2. 更新处方状态
prescriptionService.updateStatus(dto.getPrescriptionId(), "PAID");
// 3. 记录交易流水
transactionLogService.save(dto);
}
TCC 模式方案(高并发场景):
@TwoPhaseBusinessAction(name = "insurancePaymentTCC", commitMethod = "commit", rollbackMethod = "rollback")
public boolean prepare(BusinessActionContext context, @Param("paymentDTO") PaymentDTO dto) {
// 一阶段:预留资源
insuranceAccountService.freeze(dto.getPatientId(), dto.getAmount());
return true;
}
public boolean commit(BusinessActionContext context) {
// 二阶段提交:正式扣款
PaymentDTO dto = (PaymentDTO) context.getActionContext("paymentDTO");
insuranceAccountService.deduct(dto.getPatientId(), dto.getAmount());
return true;
}
public boolean rollback(BusinessActionContext context) {
// 二阶段回滚:解冻资金
PaymentDTO dto = (PaymentDTO) context.getActionContext("paymentDTO");
insuranceAccountService.unfreeze(dto.getPatientId(), dto.getAmount());
return true;
}
事务模式对比: | 模式 | 适用场景 | 性能 | 代码侵入 | |------|----------|------|----------| | AT | 一般业务 | 中 | 低 | | TCC | 高并发核心业务 | 高 | 高 | | Saga | 长流程业务 | 中 | 中 |
3. 监控体系 - Prometheus + Grafana
业务场景:实时监控服务健康状态,快速定位问题。
技术方案:
@Configuration
public class MetricsConfig {
@Bean
public MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
return registry -> registry.config()
.commonTags("application", "medical-system", "env", "prod");
}
}
// 自定义业务指标
@Component
public class BusinessMetrics {
private final MeterRegistry meterRegistry;
private final Counter consultationCounter;
private final Timer consultationTimer;
public BusinessMetrics(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
this.consultationCounter = Counter.builder("consultation.total")
.description("问诊总数")
.register(meterRegistry);
this.consultationTimer = Timer.builder("consultation.duration")
.description("问诊耗时")
.register(meterRegistry);
}
public void recordConsultation(long duration) {
consultationCounter.increment();
consultationTimer.record(duration, TimeUnit.MILLISECONDS);
}
}
Grafana 监控面板:
- JVM 内存使用率
- GC 次数与耗时
- 接口 QPS 与响应时间
- 错误率统计
- 数据库连接池状态
4. 链路追踪 - SkyWalking
业务场景:一个问诊请求经过网关→问诊服务→患者服务→处方服务→支付服务,需要追踪全链路。
技术方案:
# agent.config
agent.service_name=medical-${HOSTNAME}
collector.backend_service=127.0.0.1:11800
# 应用启动参数
-javaagent:/path/to/skywalking-agent.jar
-Dskywalking.agent.service_name=consultation-service
-Dskywalking.collector.backend_service=127.0.0.1:11800
追踪效果:
[Trace ID: 1a2b3c4d5e6f]
├─ Gateway (5ms)
├─ Consultation Service (120ms)
│ ├─ Patient Service (30ms)
│ └─ Prescription Service (80ms)
│ └─ Payment Service (50ms)
└─ Notification Service (15ms)
5. JVM 调优
业务场景:医疗系统高峰期 GC 频繁,响应变慢。
技术方案:
# JVM 启动参数
-Xms4g -Xmx4g # 堆内存固定,避免动态扩容
-XX:MetaspaceSize=256m # 元空间大小
-XX:MaxMetaspaceSize=256m # 元空间上限
-XX:+UseG1GC # G1 垃圾收集器
-XX:MaxGCPauseMillis=200 # 最大 GC 停顿时间
-XX:+HeapDumpOnOutOfMemoryError # OOM 时 dump 内存
-XX:HeapDumpPath=/logs/heapdump # dump 文件路径
-Xloggc:/logs/gc.log # GC 日志
-XX:+PrintGCDetails # 详细 GC 信息
Full GC 排查步骤:
- jstat -gcutil 查看 GC 统计
- jmap -histo 查看对象分布
- jmap -dump 导出堆内存
- MAT 分析内存泄漏
- 定位大对象和长生命周期对象
学习路线建议
🌱 初级工程师(0-2 年)
- Spring Boot 基础开发
- RESTful API 设计
- MySQL 基本操作
- Redis 基础使用
- Git 版本控制
🌿 中级工程师(2-5 年)
- Spring Cloud 微服务架构
- 消息队列原理与使用
- 分布式缓存设计
- 分布式事务理解
- JVM 基础调优
🌳 高级工程师(5 年+)
- 高可用架构设计
- 性能优化与调优
- 容灾降级方案
- 全链路监控体系
- 技术选型与决策
总结
通过这场面试,我们涵盖了互联网医疗系统的核心技术点:
✅ 服务治理:Nacos + OpenFeign + Resilience4j ✅ 缓存设计:Redis 三级缓存 + 问题预防 ✅ 消息队列:Kafka 可靠性保证 ✅ 安全认证:Spring Security + JWT + 数据加密 ✅ 分布式事务:Seata AT/TCC 模式 ✅ 监控运维:Prometheus + Grafana + SkyWalking ✅ JVM 调优:参数配置 + 问题排查
希望这篇文章能帮助正在准备面试的同学,祝大家都能拿到心仪的 Offer!🎉
参考资料:
- Spring Cloud 官方文档
- Redis 设计与实现
- Kafka 权威指南
- 深入理解 JVM 虚拟机
- 分布式事务实战手册
更多推荐




所有评论(0)