1) 【一句话结论】采用微服务+分布式架构,通过Nginx负载均衡分发请求,限流熔断保障稳定性,多级缓存(本地+Redis+数据库索引)降低数据库压力,分库分表(用户分库+订单分表)结合读写分离提升数据库性能,多机房容灾(异步复制+故障自动切换)实现高可用,整体支撑百万级并发下的低延迟与高可用。
2) 【原理/概念讲解】
老师会以“老师讲解”口吻阐述:
- 系统架构:拆分为票务服务(核心)、订单服务、支付服务、核验服务等微服务,通过API网关(如Nginx)统一入口,实现服务解耦。比如“把系统拆成多个独立的小服务,每个服务只负责购票、支付等单一功能,通过网关统一管理入口,这样某个服务出问题,不影响其他服务”。
- 负载均衡:入口层用Nginx实现请求分发,按IP、权重或会话保持策略,确保请求均匀分布,避免单点过载。
- 限流熔断:限流用令牌桶算法(入口层),每秒生成固定令牌,请求需获取令牌才能通过,平滑控制突发流量;熔断用Sentinel(或Hystrix),服务间调用时,若响应超时或错误率超过阈值(如5秒内50%失败),触发熔断,直接返回错误,避免级联故障。比如“限流像给水管装阀门,控制流量大小;熔断像安全阀,流量过大时自动关停,防止系统崩溃”。
- 限流动态调整:通过Prometheus监控请求量、响应时间等指标,结合业务峰值(如节假日),动态调整令牌桶的令牌生成速率(如平时每秒1000令牌,峰值时降至500),以及熔断阈值(如平时错误率10%,峰值时提升至30%)。
- 缓存策略:三级缓存。本地缓存(ConcurrentHashMap)存储热点数据(如热门车次、座位号),读取速度极快;分布式缓存(Redis集群)存储订单、用户信息,支持高并发读写;数据库缓存(索引)优化查询效率。缓存淘汰:Redis采用LRU(最近最少使用)或LFU(最不经常使用)策略,定期清理过期数据。缓存雪崩:设置缓存过期时间(如5分钟),并采用布隆过滤器+互斥锁(或热点数据预热),避免大量请求同时访问数据库。比如“本地缓存是手机内存,存常用数据;Redis是共享内存,大家都能用;数据库索引是书架标签,快速找到书”。
- 数据库分库分表:分库按用户ID哈希分库(如用户ID % 10,分10个库,每个库处理部分用户,分散写压力);分表按订单ID时间分表(如按月分表,或按ID范围分表,避免单表过大)。结合读写分离(主库写,从库读,通过MyCAT代理路由),提升读写性能。跨库事务:使用Seata分布式事务(TCC模式),业务预检查(Check)、确认(Confirm)、回滚(Cancel),确保跨库操作的原子性。比如“分库像把用户分成10个班级,每个班级有自己的教室;分表像每个班级的课桌按座位号排列;读写分离像老师讲课(主库写)时,学生(从库读)可以同时听”。
- 容灾设计:多机房部署(主机房+备机房),数据通过异步复制(如MySQL的Binlog同步)同步,主机房故障时,备机房通过心跳检测(如ZooKeeper)判断故障,检查数据同步状态(如Binlog位置一致),自动切换为主节点,RTO(故障恢复时间)控制在30秒内,RPO(数据丢失量)控制在分钟级(通过异步复制保证一致性)。比如“两个城市都有医院,主城市医院出故障时,备城市医院能立刻接手,病人不用等”。
3) 【对比与适用场景】
| 对比项 | 令牌桶算法(限流) | 漏桶算法(限流) | 适用场景 | 注意点 |
|---|
| 原理 | 每秒生成固定令牌,请求需获取令牌 | 每秒滴入固定水量,超过则溢出 | 突发流量控制(如双十一) | 令牌桶更平滑,允许短暂超限;漏桶更严格,不允许超限 |
| 特性 | 流量平滑,允许短暂超限 | 严格限制速率,不允许超限 | 需要平滑流量的场景 | 超限时需等待令牌生成 |
| 分库分表策略 | 垂直分库(按业务) | 水平分库(按数据量) | 业务复杂度低(如按用户分库) | 垂直分库需跨库事务,水平分库需分表查询 |
| 缓存方案 | Redis(持久化+集群) | Memcached(纯内存+无持久化) | 高并发读写(如订单数据) | Redis支持数据持久化,适合重要数据;Memcached适合临时缓存 |
4) 【示例】
- 请求示例(HTTP):
POST /api/v1/tickets/purchase
Content-Type: application/json
{
"userId": "user_001",
"trainId": "train_20240501_10:00",
"seatType": "二等座"
}
- 伪代码(票务服务核心逻辑):
// 限流检查(入口层)
if (!rateLimiter.tryAcquire()) {
return new Response("系统繁忙,请稍后再试");
}
// 本地缓存检查
TicketInfo ticket = localCache.get(trainId);
if (ticket != null) {
return new Response(ticket);
}
// 分布式缓存检查(Redis)
ticket = redisCache.get(trainId);
if (ticket != null) {
localCache.put(trainId, ticket); // 写入本地缓存
return new Response(ticket);
}
// 数据库查询(分库分表)
ticket = db.queryTicket(trainId);
if (ticket != null) {
redisCache.put(trainId, ticket); // 写入分布式缓存
localCache.put(trainId, ticket); // 写入本地缓存
return new Response(ticket);
}
// 无票务信息
return new Response("票务信息不存在");
5) 【面试口播版答案】
“面试官您好,针对百万级用户购票的AFC系统设计,核心是采用微服务+分布式架构,通过Nginx负载均衡分发请求,限流熔断保障稳定性,多级缓存(本地+Redis+数据库索引)降低数据库压力,分库分表(用户分库+订单分表)结合读写分离提升数据库性能,多机房容灾(异步复制+故障自动切换)实现高可用。具体来说,系统拆分为票务、订单、支付等微服务,通过API网关统一入口。限流用令牌桶算法(入口层),动态调整令牌速率应对流量变化;服务间调用用Sentinel熔断,避免级联故障。缓存采用三级设计:本地缓存存热点数据,Redis存订单信息,数据库索引优化查询。数据库按用户ID分库(分散写压力),按订单ID分表(避免单表过大),结合读写分离提升性能,跨库事务用Seata TCC模式保证一致性。容灾方面,多机房部署,数据异步复制,故障时自动切换,RTO控制在30秒内。这样能支撑百万级并发,低延迟,高可用。”
6) 【追问清单】
- 问题:限流参数如何动态调整?
回答要点:通过Prometheus监控请求量、响应时间,结合业务峰值(如节假日),动态调整令牌桶的令牌生成速率(如平时1000/秒,峰值降至500/秒),以及熔断阈值(如平时错误率10%,峰值提升至30%)。
- 问题:缓存雪崩如何处理?
回答要点:设置缓存过期时间(如5分钟),并采用布隆过滤器+互斥锁(或热点数据预热),避免大量请求同时访问数据库。比如,热门车次数据提前预热到缓存,减少雪崩影响。
- 问题:分库分表后事务如何保证?
回答要点:使用Seata分布式事务(TCC模式),业务预检查(Check)、确认(Confirm)、回滚(Cancel),确保跨库操作的原子性。比如,订单创建时检查库存,确认后扣减库存,回滚时恢复库存。
- 问题:容灾切换的RTO和RPO是多少?
回答要点:RTO(故障恢复时间)控制在30秒内,RPO(数据丢失量)控制在分钟级(通过异步复制保证数据一致性,如Binlog同步)。
- 问题:微服务间如何保证数据一致性?
回答要点:通过消息队列(如Kafka)异步通信,结合最终一致性模型,确保数据在多个服务间同步。比如,订单创建后发送消息到支付服务,支付服务消费消息后处理支付。
7) 【常见坑/雷区】
- 限流位置只放在入口层,服务间无熔断导致级联故障;
- 缓存穿透未处理(如空值查询)导致数据库压力激增;
- 跨库事务未使用分布式事务(如Seata),导致数据不一致;
- 容灾方案只考虑数据同步,未考虑业务切换流程(如故障检测、切换逻辑);
- 微服务间通信采用同步调用,导致高并发时服务阻塞(应使用异步消息队列)。