概述
基于企业级电商平台(Spring Cloud Alibaba 微服务架构)的实战经验。涵盖从基础架构设计到高并发场景、从分布式事务到性能优化的全方位技术考察点。
一、微服务架构基础
1.1 为什么选择微服务架构?
问题:单体应用 vs 微服务,什么情况下应该选择微服务?
核心要点:
- 团队规模:微服务适合 50+ 人的研发团队,按业务域拆分团队
- 业务复杂度:电商涉及商品、订单、支付、库存等多个独立业务域
- 技术异构性:不同服务可选择最适合的技术栈
- 独立部署:订单服务升级不影响商品服务
电商场景示例:
单体痛点:
- 代码库 100万+ 行,构建时间 30分钟+
- 小改动需要全量发布
- 库存高峰拖垮整个系统
微服务收益:
- 订单服务独立扩容应对大促
- 商品服务静态化 + CDN 加速
- 库存服务 Redis 抗高并发
1.2 微服务拆分原则
问题:如何合理拆分微服务?拆得太细或太粗各有什么问题?
拆分原则:
| 原则 | 说明 | 示例 |
|---|---|---|
| 业务边界 | 按 DDD 限界上下文拆分 | 订单、商品、用户、支付 |
| 数据独立性 | 每个服务独立数据库 | 订单表不在商品库 |
| 团队规模 | 2个披萨团队原则 | 6-10人维护一个服务 |
| 变更频率 | 高频变更独立拆分 | 促销服务单独部署 |
拆分粒度问题:
- 过细:分布式事务复杂度上升,网络开销增加
- 过粗:失去微服务独立部署、独立扩容的优势
电商服务拆分示例:
✅ 合理拆分:
- user-service(用户)
- product-service(商品)
- order-service(订单)
- payment-service(支付)
- inventory-service(库存)
- promotion-service(促销)
❌ 过度拆分:
- user-profile-service
- user-address-service
- user-coupon-service
(应该合并为 user-service)
1.3 服务间通信方式
问题:微服务间同步调用和异步消息如何选择?
决策矩阵:
| 场景 | 推荐方式 | 说明 |
|---|---|---|
| 用户注册发送短信 | 异步消息 | 非核心流程,允许延迟 |
| 下单扣减库存 | 同步调用 | 强一致性要求 |
| 订单支付成功通知 | 异步消息 | 最终一致性即可 |
| 获取商品详情 | 同步调用 | 实时性要求高 |
电商最佳实践:
// 同步调用 - Feign 客户端
@FeignClient(name = "inventory-service")
public interface InventoryClient {
@PostMapping("/inventory/deduct")
Result<Boolean> deduct(@RequestBody DeductRequest request);
}
// 异步消息 - RocketMQ
@RocketMQMessageListener(topic = "order-paid", consumerGroup = "inventory-consumer")
public class InventoryConsumer implements RocketMQListener<OrderPaidEvent> {
@Override
public void onMessage(OrderPaidEvent event) {
// 异步扣减库存
}
}
二、分布式事务
2.1 分布式事务解决方案对比
问题:电商下单场景(扣库存、创订单、扣余额)如何保证数据一致性?
方案对比:
| 方案 | 一致性 | 性能 | 复杂度 | 适用场景 |
|---|---|---|---|---|
| 2PC | 强一致 | 低 | 中 | 传统数据库XA |
| TCC | 最终一致 | 高 | 高 | 电商核心交易 |
| Saga | 最终一致 | 高 | 中 | 长事务流程 |
| 本地消息表 | 最终一致 | 中 | 中 | 异步场景 |
| Seata AT | 最终一致 | 中 | 低 | 简单CRUD场景 |
电商推荐方案:
核心链路(强一致性):TCC
- Try:预留库存、预扣余额
- Confirm:确认扣减
- Cancel:释放预留
非核心链路(最终一致):RocketMQ事务消息
- 订单创建成功发送消息
- 积分服务消费增加积分
2.2 TCC 实现细节
问题:TCC 的 Try、Confirm、Cancel 分别做什么?空回滚和幂等如何解决?
TCC 三阶段:
@Component
public class InventoryTccAction {
// Try:预留资源
@Transactional
public boolean tryDeduct(String xid, Long productId, Integer count) {
// 1. 检查库存
// 2. 扣减可售库存
// 3. 增加冻结库存
// 4. 记录事务日志
}
// Confirm:确认执行
@Transactional
public boolean confirm(String xid) {
// 1. 删除事务日志
// 2. 扣减冻结库存(实际扣减)
}
// Cancel:取消回滚
@Transactional
public boolean cancel(String xid) {
// 1. 查询事务日志
// 2. 恢复可售库存
// 3. 扣减冻结库存
// 4. 删除事务日志
}
}
异常处理:
- 空回滚:Try 未执行时 Cancel 被调用,需记录事务状态
- 幂等控制:Confirm/Cancel 可能被重复调用,需保证幂等
- 悬挂:Cancel 先于 Try 执行,需检查事务状态
三、高并发与性能优化
3.1 秒杀系统设计
问题:如何设计一个支持 10万 QPS 的秒杀系统?
分层优化策略:
┌─────────────────────────────────────────┐
│ 前端层 │
│ - 静态页面 CDN │
│ - 秒杀按钮置灰/倒计时 │
│ - 验证码/滑块防刷 │
└─────────────────────────────────────────┘
│
┌─────────────────────────────────────────┐
│ 网关层 │
│ - Nginx 限流(漏桶算法) │
│ - Sentinel 热点参数限流 │
│ - 黑名单过滤 │
└─────────────────────────────────────────┘
│
┌─────────────────────────────────────────┐
│ 应用层 │
│ - 令牌桶限流(Redis List) │
│ - 库存预热(Redis) │
│ - Lua 原子扣减 │
│ - 异步下单(RocketMQ) │
└─────────────────────────────────────────┘
│
┌─────────────────────────────────────────┐
│ 数据层 │
│ - 库存分片(多 Redis 节点) │
│ - 写保护(限流 + 队列) │
└─────────────────────────────────────────┘
核心代码:
-- Redis Lua 原子扣减库存
local stock = redis.call('get', KEYS[1])
if tonumber(stock) <= 0 then
return -1 -- 库存不足
end
redis.call('decr', KEYS[1])
return redis.call('get', KEYS[1]) -- 返回剩余库存
3.2 缓存设计
问题:多级缓存如何设计?缓存穿透、击穿、雪崩如何解决?
多级缓存架构:
请求 → Caffeine(L1,本地)→ Redis(L2,分布式)→ DB
↓ ↓
1ms响应 5ms响应
单机10万QPS 集群10万QPS
缓存问题解决方案:
| 问题 | 现象 | 解决方案 |
|---|---|---|
| 穿透 | 查询不存在数据,直达DB | 布隆过滤器 / 空值缓存 |
| 击穿 | 热点Key过期,瞬间大量请求到DB | 互斥锁 / 逻辑过期 |
| 雪崩 | 大量Key同时过期 | 随机过期时间 / 多级缓存 |
电商缓存策略:
@Component
public class ProductCache {
@Cacheable(value = "product", key = "#id",
unless = "#result == null")
public Product getProduct(Long id) {
return productMapper.selectById(id);
}
// 布隆过滤器防止穿透
public Product getProductWithBloomFilter(Long id) {
if (!bloomFilter.mightContain(id)) {
return null; // 一定不存在
}
return getProduct(id);
}
}
3.3 数据库优化
问题:订单表数据量过亿,如何优化查询性能?
分库分表策略:
| 维度 | 策略 | 说明 |
|---|---|---|
| 分库 | 用户ID取模 | user_id % 2 = 0/1 |
| 分表 | 时间范围 + 取模 | 按月分表,每月16张 |
| 路由 | ShardingSphere-JDBC | 透明化分片 |
订单表分片示例:
# ShardingSphere 配置
sharding:
tables:
t_order:
actualDataNodes: ds${0..1}.t_order_${2024..2025}_${1..12}
tableStrategy:
standard:
shardingColumn: create_time
preciseAlgorithm: TimeShardingAlgorithm
databaseStrategy:
standard:
shardingColumn: user_id
preciseAlgorithm: UserIdShardingAlgorithm
慢SQL优化 checklist:
- 索引覆盖查询(Using index)
- 避免 SELECT *
- 大分页使用游标/延迟关联
- 深分页优化(limit 1000000, 10 → 延迟关联)
四、高可用与容灾
4.1 服务熔断降级
问题:下游服务故障时,如何保证系统可用性?
熔断器状态机:
┌─────────┐ 失败率>阈值 ┌─────────┐
│ CLOSED │ ─────────────────→ │ OPEN │
│ (正常) │ │ (熔断) │
└────┬────┘ └────┬────┘
│ ←─────────────────── │
│ 超时后半开 │
│ │
└─────────────────────────────┘
半开状态(HALF_OPEN)
Sentinel 配置示例:
// 熔断降级规则
DegradeRule rule = new DegradeRule();
rule.setResource("getProduct");
rule.setGrade(CircuitBreakerStrategy.ERROR_RATIO); // 异常比例
rule.setCount(0.5); // 50% 异常率触发熔断
rule.setTimeWindow(30); // 熔断30秒
rule.setMinRequestAmount(10); // 最小请求数
4.2 限流策略
问题:大促期间如何保护系统不被流量打垮?
限流算法对比:
| 算法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 计数器 | 简单 | 突刺问题 | 简单限流 |
| 滑动窗口 | 平滑 | 内存占用 | 精准限流 |
| 令牌桶 | 允许突发 | 实现复杂 | 秒杀场景 |
| 漏桶 | 绝对平滑 | 无突发 | 流量整形 |
电商限流实践:
// 网关层限流 - 基于 IP
GatewayFilterSpec filterSpec = builder.routes()
.route("order_route", r -> r.path("/order/**")
.filters(f -> f.requestRateLimiter(config -> {
config.setRateLimiter(redisRateLimiter());
config.setKeyResolver(exchange ->
Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()));
}))
.uri("lb://order-service"));
4.3 多活架构
问题:如何实现异地多活?数据一致性如何处理?
多活架构层级:
| 层级 | 同城双活 | 异地多活 |
|---|---|---|
| 接入层 | DNS 轮询 | 智能 DNS(按地域) |
| 应用层 | 双机房部署 | 单元化部署 |
| 数据层 | 主从同步 | 双向同步 / 分片 |
数据一致性策略:
读多写少场景(商品信息):
- 最终一致性,多机房同步延迟 < 1s
强一致场景(库存、余额):
- 单机房写,跨机房读
- 或 Paxos/Raft 分布式共识
五、可观测性
5.1 链路追踪
问题:微服务调用链复杂,如何快速定位问题?
SkyWalking 链路追踪:
用户请求 → Gateway → Order → Payment → Inventory
│ │ │ │ │
└──────────┴────────┴─────────┴──────────┘
TraceID: abc123
Span 层级:
- Gateway: 10ms
- Order: 50ms
- Payment: 30ms
- Inventory: 15ms
关键指标:
- P99 延迟:99% 请求响应时间 < 500ms
- 错误率:< 0.1%
- 吞吐量:> 10000 QPS
5.2 监控告警
问题:如何构建有效的监控告警体系?
监控分层:
| 层级 | 工具 | 监控内容 |
|---|---|---|
| 基础设施 | Prometheus | CPU、内存、磁盘、网络 |
| 应用指标 | Micrometer | QPS、延迟、错误率 |
| 业务指标 | 自定义 | 订单量、支付成功率 |
| 日志 | ELK | 错误日志、慢查询 |
告警规则示例:
# Prometheus 告警规则
groups:
- name: ecommerce-alerts
rules:
- alert: HighErrorRate
expr: rate(http_requests_total{status=~"5.."}[5m]) > 0.01
for: 2m
labels:
severity: critical
annotations:
summary: "错误率超过 1%"
六、安全架构
6.1 认证授权
问题:微服务架构下如何实现统一认证授权?
JWT + Gateway 方案:
┌─────────┐ 登录 ┌─────────┐ JWT Token ┌─────────┐
│ 客户端 │ ─────────→ │ 用户服务 │ ─────────────→ │ 客户端 │
└─────────┘ └─────────┘ └─────────┘
│
│ 携带 JWT
▼
┌─────────┐ 验签 ┌─────────┐ 转发 ┌─────────┐
│ Gateway │ ───────→ │ 业务服务 │ ←──────→ │ 数据库 │
│ (验Token)│ │ (鉴权限) │ │ │
└─────────┘ └─────────┘ └─────────┘
权限设计:
@PreAuthorize("hasRole('ADMIN') or hasPermission(#orderId, 'ORDER:VIEW')")
public Order getOrder(Long orderId) {
return orderService.getOrder(orderId);
}
6.2 防刷防攻击
问题:如何防止接口被恶意刷量?
防护策略:
| 层级 | 措施 | 实现 |
|---|---|---|
| 网关层 | IP 限流 | Nginx limit_req |
| 应用层 | 用户限流 | Redis + Lua |
| 业务层 | 验证码 | 图形/短信/滑块 |
| 数据层 | 请求签名 | HMAC + 时间戳 |
防重放攻击:
public class AntiReplayFilter {
public boolean validateRequest(String sign, String timestamp, String nonce) {
// 1. 时间戳校验(5分钟内有效)
long now = System.currentTimeMillis();
if (now - Long.parseLong(timestamp) > 300000) {
return false;
}
// 2. nonce 去重(Redis SetNX)
if (!redis.setnx("nonce:" + nonce, "1", 300, TimeUnit.SECONDS)) {
return false;
}
// 3. 签名验证
String expectedSign = HmacUtils.hmacSha256Hex(key, timestamp + nonce);
return expectedSign.equals(sign);
}
}
七、面试技巧总结
7.1 回答框架(STAR法则)
Situation(场景):什么场景下遇到的问题?
Task(任务):你的任务/目标是什么?
Action(行动):你采取了哪些行动?
Result(结果):最终效果如何?
示例:
"在 2024 年双 11 大促期间(S),我们的订单服务峰值达到了 5万 QPS,数据库连接池频繁耗尽(T)。我引入了 Redis 缓存 + 异步下单方案(A),将数据库压力降低了 80%,成功支撑了峰值流量(R)。"
7.2 加分项
| 能力 | 体现方式 |
|---|---|
| 全栈视野 | 从前端到数据库的完整链路理解 |
| 数据驱动 | 用具体数字说明优化效果 |
| 故障经验 | 真实的线上故障排查经历 |
| 持续学习 | 关注云原生、ServiceMesh 等新技术 |
7.3 避坑指南
❌ 避免回答:
- "我们用了微服务,因为流行"
- "分布式事务用 2PC,因为简单"
- "缓存用 Redis,因为快"
✅ 正确回答:
- "微服务拆分基于 DDD,按业务边界划分..."
- "对比了 Seata AT/TCC/Saga,最终选择 TCC 因为..."
- "Redis 用于热点数据缓存,配合本地缓存 Caffeine..."
八、实战场景题
场景 1:订单超时自动取消
问题:下单后 30 分钟未支付,如何自动取消订单并恢复库存?
方案对比:
| 方案 | 实现 | 优缺点 |
|---|---|---|
| 定时任务 | XXL-Job 每分钟扫描 | 简单,但存在延迟 |
| 延迟队列 | RocketMQ 延迟消息 | 精确触发,推荐 |
| Redis 过期 | Key 过期监听 | 不可靠,不推荐 |
推荐实现:
// 发送延迟消息
rocketMQTemplate.asyncSend("order-delay-topic",
MessageBuilder.withPayload(order).build(),
new SendCallback() {...},
300000, // 延迟5分钟
3 // 延迟级别
);
// 消费超时消息
@RocketMQMessageListener(topic = "order-delay-topic",
consumerGroup = "order-timeout-consumer")
public class OrderTimeoutConsumer implements RocketMQListener<Order> {
@Override
public void onMessage(Order order) {
// 1. 查询订单状态
// 2. 未支付则取消订单
// 3. 恢复库存
}
}
场景 2:库存扣减与回滚
问题:下单时扣了库存,用户取消订单后如何回滚?
状态机设计:
┌─────────┐ 下单 ┌─────────┐ 支付 ┌─────────┐
│ 可用 │ ─────────→ │ 冻结 │ ─────────→ │ 已扣减 │
│ 100 │ 扣10 │ 90/10 │ 确认 │ 90 │
└────┬────┘ └────┬────┘ └─────────┘
│ │
│ 取消订单 │
└──────────────────────┘
回滚
总结
本文从微服务基础、分布式事务、高并发优化、高可用容灾、可观测性、安全架构等维度,整理了电商架构师面试的核心问题。掌握这些知识点,结合实际项目经验,能够在架构师面试中展现出扎实的技术功底和系统设计能力。
核心要点回顾:
- 微服务拆分:按业务边界,避免过度拆分
- 分布式事务:TCC 用于核心链路,消息用于最终一致
- 高并发:多级缓存 + 异步化 + 限流熔断
- 高可用:熔断降级 + 多活架构 + 容灾演练
- 可观测性:链路追踪 + 指标监控 + 日志聚合