1) 【一句话结论】
采用微服务+分布式架构,通过分库分表(冷热分离+动态分片)、Redis缓存(热点数据+分布式锁防击穿)、Kafka消息队列(死信队列+重试机制),结合最终一致性,力争百万级并发下响应时间小于2秒。
2) 【原理/概念讲解】
老师口吻解释系统分层设计:
系统分为前端(用户交互界面)、API网关(请求路由、负载均衡、限流)、服务层(拆分为用户服务、科室服务、医生服务、预约服务等微服务,各负责独立业务逻辑)、数据层(分库分表MySQL+缓存Redis+消息队列Kafka)。
- API网关:作为入口,分发请求到对应微服务,并做负载均衡(Nginx+Keepalived)和限流(令牌桶算法,如每秒1000个令牌,速率1000/s)。
- 服务层微服务:如预约服务(核心业务,处理预约流程,包含医生排班检查、预约生成、通知发送)。
- 数据层:
- 分库分表:用户表按用户ID哈希分库(如用户ID % 8取模,分8个库),科室表按科室ID分库,医生表按科室ID分库,预约表按预约时间分表(如按年月日分表,如2024-05-20的预约记录存入appointment_20240520表)。工具选ShardingSphere,支持自动分库分表,减少开发复杂度。冷热数据分离:用户基本信息(如用户ID、姓名、手机号)热数据,历史预约记录(如过去3个月的预约)冷数据,减少热点数据压力。
- 读写分离:MySQL主从复制,主库写,从库读,通过HikariCP连接池配置(maxTotal=200,maxIdle=100,minIdle=20,连接超时30秒),高效复用数据库连接。
- 缓存:Redis存储热点数据(如热门科室、医生排班),热数据设置随机过期时间(300-600秒),避免雪崩;缓存穿透用空值缓存(过期1秒);缓存击穿用Redis分布式锁(互斥锁),防热点数据争抢,粒度按医生ID加锁,避免整表加锁影响性能。
- 消息队列:Kafka处理异步通知(如短信/邮件),配置死信队列(DLQ),消息积压时自动转发,后续重试(10次后标记失败,人工处理)。
- 数据一致性:采用最终一致性(CAP的AP),预约服务先写数据库(事务提交),再发Kafka消息(异步),通知失败则重试(10次后标记失败),确保数据最终一致。
3) 【对比与适用场景】
以**分库分表工具(ShardingSphere vs 自定义分库分表)**为例:
| 对比项 | ShardingSphere(工具) | 自定义分库分表 |
|---|
| 定义 | 分布式数据库中间件,支持分库分表、分布式事务 | 手动编写分库分表逻辑(SQL分片规则) |
| 特性 | 自动分库分表,支持分布式事务(如两阶段提交),冷热数据分离配置 | 需自定义分片规则,事务处理复杂,需手动处理数据迁移 |
| 使用场景 | 大规模数据分库分表,需事务支持,且数据量增长快 | 小规模数据,或对性能要求不高,数据量稳定 |
| 注意点 | 配置分片键(如用户ID),避免数据倾斜;需监控分片键分布,调整策略 | 处理数据迁移、分片键选择(如用户ID均匀分布),避免冷热数据分离效果差 |
4) 【示例】
用户预约流程(请求示例):
- 用户访问预约页面,API网关限流检查通过后,路由到预约服务。
- 预约服务查询Redis缓存(热门科室列表),未命中则从MySQL科室表查询并缓存(随机过期400秒)。
- 检查医生排班:先查Redis缓存(医生排班表),未命中则从MySQL医生表查询并缓存(随机过期300秒)。
- 检查医生可用名额:查询MySQL预约表(分表按时间),若医生当日预约数<10,则生成预约记录(事务提交),写入appointment_20240520表,并调用Kafka发送消息(主题:appointment_notification)。
- 消息消费者(通知服务)消费消息,发送短信通知。若通知失败,Kafka重试(10次后标记失败)。
伪代码(请求示例):
POST /api/v1/appointments
{
"userId": "user123",
"doctorId": "doc456",
"appointmentTime": "2024-05-20 09:00"
}
5) 【面试口播版答案】
面试官您好,针对百万级用户访问的在线预约挂号系统,我设计的核心是微服务+分布式架构,通过分库分表(冷热分离+动态分片)、Redis缓存(热点数据+分布式锁防击穿)、Kafka消息队列(死信队列+重试机制),结合最终一致性,力争百万级并发下响应时间小于2秒。具体来说,系统分为前端、API网关(限流+负载均衡)、服务层(用户/科室/医生/预约微服务)、数据层。数据层用MySQL分库分表:用户表按用户ID哈希分库(如ID%8取模),科室/医生表按科室ID分库,预约表按时间分表(如2024-05-20的预约记录存入appointment_20240520表),工具ShardingSphere。缓存Redis存储热门科室、医生排班等热点数据,设置随机过期时间(300-600秒),缓存穿透用空值缓存(过期1秒),缓存击穿时按医生ID加分布式锁(互斥锁),避免热点数据争抢。消息队列Kafka处理异步通知,配置死信队列(DLQ),消息积压时自动转发,后续重试(10次后标记失败)。数据一致性采用最终一致性,预约成功后先写数据库(事务提交),再发Kafka消息,通知失败则重试。高峰时通过缓存减少数据库压力,负载均衡分发请求,限流控制速率,确保性能。比如用户预约时,先查缓存,缓存未命中再查数据库并缓存,避免热点数据争抢;医生排班检查用缓存+互斥锁防击穿;预约成功后异步通知,避免阻塞主流程,保证响应时间。
6) 【追问清单】
- 问:如何处理缓存击穿时分布式锁的粒度问题?
回答要点:按医生ID加锁,而非整表,避免影响其他医生排班查询性能,锁粒度细粒度,减少锁竞争。
- 问:消息队列的死信队列如何配置?
回答要点:Kafka配置DLQ(死信队列),消息积压时自动转发,后续重试(10次后标记失败,人工处理),避免消息丢失。
- 问:分库分表的具体策略?
回答要点:用户表按用户ID哈希分库(如ID%8取模),科室/医生表按科室ID分库,预约表按时间分表,冷热数据分离(用户基本信息热,历史记录冷),工具ShardingSphere。
- 问:数据库连接池如何配置?
回答要点:HikariCP连接池,maxTotal=200,maxIdle=100,minIdle=20,连接超时30秒,高效复用连接,减少创建开销。
- 问:数据一致性如何保证?
回答要点:最终一致性,预约成功后先写数据库(事务提交),再发消息队列通知,通知失败重试(10次后标记失败),确保数据最终一致。
7) 【常见坑/雷区】
- 坑1:未分库分表导致数据库瓶颈,高并发响应超时,数据倾斜严重。
- 坑2:缓存未设随机过期时间,引发雪崩,数据库压力激增,响应时间超标。
- 坑3:消息队列无死信队列,积压消息无法处理,导致通知延迟或丢失。
- 坑4:未限流,系统被大量请求淹没,响应时间超标,用户体验差。
- 坑5:强一致性设计,高并发下预约流程阻塞,导致用户无法预约,系统可用性低。