Files
interview/questions/15-简历面试/场景设计题.md
yasinshaw 9c9610fc60 feat: 添加简历面试题
- 项目深挖题:5个重点项目STAR法则回答,针对每个项目准备深挖问题
- 场景设计题:秒杀系统、优惠券系统、数据一致性、限流降级等设计题
- 个人发展题:职业规划、学习能力、团队协作、抗压能力、价值观
- 离职原因与动机:离职原因、择公司、职业目标、反问技巧
- 薪资谈判:谈判策略、Web3特有问题(代币激励、远程工作)、DO & DON'T

针对简历特点:
- 结合字节跳动、阿里巴巴、ThoughtWorks的项目经验
- 提供STAR法则回答模板
- 强调Web2经验向Web3的转化
- 包含大量代码示例和架构图
- 提供薪资谈判实战策略

适用场景:
- 面试前准备:项目深挖、场景设计
- 面试中:个人发展、离职原因
- 面试后:薪资谈判、offer评估
2026-03-03 00:20:12 +08:00

1109 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 场景设计题
## 说明
场景设计题是考察系统设计能力和问题解决能力的常见题型。本文档针对你的简历背景,设计相关的场景题。
---
## 1. 电商促销系统设计
### 题目
**面试官问**
"假设你要设计一个电商秒杀系统,支持百万用户同时抢购,你会如何设计?"
---
### 参考回答
**【需求分析】**
```
核心需求:
1. 高并发:百万用户同时抢购
2. 防超卖:库存不能卖超
3. 高可用:服务不能挂
4. 公平性:先到先得
5. 用户体验:快速响应
非功能需求:
- QPS: 100万+
- 延迟: <100ms
- 可用性: 99.99%
- 数据一致性: 强一致
```
---
**【架构设计】**
```
┌─────────────────────────────────────────────┐
│ 客户端层 │
│ 移动App、Web、H5 │
└──────────────┬──────────────────────────────┘
┌─────────────────────────────────────────────┐
│ CDN层 │
│ - 静态资源图片、CSS、JS
│ - 动态接口(边缘计算) │
└──────────────┬──────────────────────────────┘
┌─────────────────────────────────────────────┐
│ API网关层 │
│ - 限流100万QPS │
│ - 熔断:保护后端 │
│ - 负载均衡 │
└──────────────┬──────────────────────────────┘
┌────────┴────────┐
↓ ↓
┌──────────┐ ┌──────────┐
│ 缓存层 │ │ 服务层 │
└──────────┘ └──────────┘
│ │
↓ ↓
┌─────────────────────────────────┐
│ 数据库层 │
│ MySQL主从 + 分库分表 │
└─────────────────────────────────┘
```
---
**【详细设计】**
### 1. 缓存层设计(关键)
```java
/**
* Redis预减库存核心
*/
@Service
public class SeckillService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 预减库存(原子操作)
*/
public boolean decrementStock(Long productId, Long userId) {
String key = "stock:seckill:" + productId;
// 原子减库存
Long stock = redisTemplate.opsForValue().decrement(key);
if (stock < 0) {
// 库存不足,回滚
redisTemplate.opsForValue().increment(key);
return false;
}
// 记录用户抢购记录
String userKey = "user:seckill:" + productId + ":" + userId;
redisTemplate.opsForValue().set(userKey, "1", 24, TimeUnit.HOURS);
return true;
}
/**
* 异步下单(消息队列)
*/
public void asyncOrder(Order order) {
// 发送到消息队列
rabbitTemplate.convertAndSend("order.queue", order);
}
}
/**
* 消息队列消费者
*/
@Component
public class OrderConsumer {
@RabbitListener(queues = "order.queue")
public void createOrder(Order order) {
// 1. 校验库存(数据库)
int stock = productMapper.getStock(order.getProductId());
if (stock <= 0) {
throw new InsufficientStockException();
}
// 2. 扣减库存(数据库)
productMapper.decrementStock(order.getProductId());
// 3. 创建订单
orderMapper.insert(order);
// 4. 返回成功
log.info("订单创建成功: {}", order.getId());
}
}
```
---
### 2. 限流设计
```java
/**
* 限流策略(多级)
*/
@Configuration
public class RateLimiterConfig {
/**
* 接口级限流(令牌桶)
*/
@Bean
public RateLimiter apiRateLimiter() {
// 100万QPS
return RateLimiter.create(1000000.0);
}
/**
* 用户级限流(防刷)
*/
public boolean checkUserRateLimit(Long userId) {
String key = "rate_limit:user:" + userId;
Long count = redisTemplate.opsForValue().increment(key);
if (count == 1) {
redisTemplate.expire(key, 1, TimeUnit.MINUTES);
}
// 每分钟最多10次
return count <= 10;
}
}
/**
* 限流拦截器
*/
@Component
public class RateLimitInterceptor implements HandlerInterceptor {
@Autowired
private RateLimiter apiRateLimiter;
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
// 接口级限流
if (!apiRateLimiter.tryAcquire()) {
response.setStatus(429); // Too Many Requests
return false;
}
// 用户级限流
Long userId = getUserId(request);
if (!checkUserRateLimit(userId)) {
response.setStatus(429);
return false;
}
return true;
}
}
```
---
### 3. 防刷设计
```java
/**
* 防刷策略
*/
@Service
public class AntiSpamService {
/**
* 验证码(图形验证码 + 滑动验证)
*/
public boolean verifyCaptcha(String token, String answer) {
// 调用验证码服务
return captchaService.verify(token, answer);
}
/**
* 风控规则
*/
public RiskLevel checkRisk(Long userId, SeckillRequest request) {
RiskScore score = new RiskScore();
// 1. IP风险检测
String ip = getIp(request);
if (ipRiskService.isHighRisk(ip)) {
score.add(50);
}
// 2. 设备指纹检测
String deviceId = getDeviceId(request);
if (deviceRiskService.isEmulator(deviceId)) {
score.add(80); // 模拟器
}
// 3. 行为检测
if (userBehaviorService.isAbnormal(userId)) {
score.add(30);
}
// 4. 频率检测
int requestCount = getRequestCount(userId, 1, TimeUnit.MINUTES);
if (requestCount > 100) {
score.add(40);
}
// 判定风险等级
if (score.getScore() >= 80) {
return RiskLevel.HIGH; // 拒绝
} else if (score.getScore() >= 50) {
return RiskLevel.MEDIUM; // 需要验证
} else {
return RiskLevel.LOW; // 通过
}
}
}
```
---
### 4. 降级策略
```java
/**
* 降级策略
*/
@Component
public class DegradationService {
/**
* 降级开关(配置中心)
*/
@Value("${seckill.degradation.enabled:false}")
private boolean degradationEnabled;
/**
* 降级方案
*/
public SeckillResult handleSeckill(SeckillRequest request) {
if (degradationEnabled) {
// 降级:返回静态页面
return SeckillResult.degraded();
}
// 正常流程
return seckillService.seckill(request);
}
/**
* 自动降级(基于错误率)
*/
@Scheduled(fixedRate = 1000) // 每秒检查
public void autoDegradation() {
double errorRate = getErrorRate();
if (errorRate > 0.5) { // 错误率超过50%
// 开启降级
enableDegradation();
// 发送告警
alertService.send("错误率过高,已自动降级");
}
}
}
```
---
### 5. 库存回滚(重要)
```java
/**
* 库存回滚机制
*/
@Service
public class StockRollbackService {
/**
* 定时任务:回滚未支付的订单库存
*/
@Scheduled(cron = "0 */5 * * * ?") // 每5分钟
public void rollbackUnpaidStock() {
// 查询15分钟前未支付的订单
List<Order> unpaidOrders = orderMapper.getUnpaidOrders(15);
unpaidOrders.forEach(order -> {
// 回滚库存Redis
String key = "stock:seckill:" + order.getProductId();
redisTemplate.opsForValue().increment(key);
// 回滚库存(数据库)
productMapper.incrementStock(order.getProductId());
// 取消订单
orderMapper.updateStatus(order.getId(), OrderStatus.CANCELLED);
log.info("库存已回滚: orderId={}", order.getId());
});
}
}
```
---
**【容量预估】**
```
1. QPS预估
- 用户数100万
- 峰值QPS100万/秒
- 考虑10倍冗余1000万QPS
2. 缓存容量
- Redis实例10个
- 每个实例10万QPS
- 总计100万QPS
3. 消息队列
- Kafka分区100个
- 消费者200个
- 吞吐量100万/秒
4. 数据库
- 主库1个写入
- 从库10个读取
- 分库分表100个库1000个表
```
---
**【压测方案】**
```java
/**
* 压测方案
*/
@Test
public class LoadTest {
/**
* 压测脚本
*/
@Test
public void testSeckill() {
// 1. 预热
warmUp();
// 2. 施加压力
int threadCount = 10000; // 1万个线程
int requestsPerThread = 100; // 每个线程100次请求
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
CountDownLatch latch = new CountDownLatch(threadCount);
long startTime = System.currentTimeMillis();
for (int i = 0; i < threadCount; i++) {
executor.submit(() -> {
try {
for (int j = 0; j < requestsPerThread; j++) {
// 发起请求
seckillService.seckill(request);
}
} finally {
latch.countDown();
}
});
}
latch.await();
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
int totalRequests = threadCount * requestsPerThread;
double qps = (double) totalRequests / (duration / 1000.0);
System.out.println("QPS: " + qps);
System.out.println("平均耗时: " + (duration / totalRequests) + "ms");
}
}
```
---
**【监控告警】**
```
监控指标:
1. QPS
2. 响应时间P50、P95、P99
3. 错误率
4. 库存消耗速度
5. 订单创建速度
告警策略:
- QPS < 100万警告
- P99 > 500ms警告
- 错误率 > 1%:严重告警
- 库存耗尽:通知
```
---
## 2. 优惠券系统设计
### 题目
**面试官问**
"设计一个优惠券系统,支持满减、折扣、立减等多种类型,支持叠加使用,如何设计?"
---
### 参考回答
**【需求分析】**
```
功能需求:
1. 优惠券类型
- 满减券满100减20
- 折扣券打8折
- 立减券立减10元
- 兑换券(兑换商品)
2. 使用规则
- 叠加使用(可以同时使用多张)
- 互斥使用(不能同时使用)
- 优先级(先使用优先级高的)
3. 限制条件
- 品类限制(仅限某品类)
- 商品限制(仅限某商品)
- 用户限制(新用户专享)
- 时间限制(有效期)
非功能需求:
- 性能:计算优惠 < 10ms
- 准确:不能算错
- 灵活:方便配置新规则
```
---
**【数据模型设计】**
```sql
-- 优惠券模板
CREATE TABLE coupon_template (
id BIGINT PRIMARY KEY,
name VARCHAR(128), -- 名称
type TINYINT, -- 类型1满减 2折扣 3立减
discount_value DECIMAL(10,2), -- 优惠值
min_amount DECIMAL(10,2), -- 最低消费金额
max_discount DECIMAL(10,2), -- 最大优惠金额
category_id BIGINT, -- 适用品类
product_id BIGINT, -- 适用商品
user_limit INT, -- 每人限领数量
total_count INT, -- 总发行量
issued_count INT DEFAULT 0, -- 已发行量
used_count INT DEFAULT 0, -- 已使用量
valid_days INT, -- 有效天数
status TINYINT, -- 状态1草稿 2进行中 3已结束
priority INT DEFAULT 0, -- 优先级
stackable TINYINT DEFAULT 0, -- 是否可叠加
create_time DATETIME,
update_time DATETIME
);
-- 用户优惠券
CREATE TABLE user_coupon (
id BIGINT PRIMARY KEY,
user_id BIGINT,
template_id BIGINT,
status TINYINT, -- 状态1未使用 2已使用 3已过期
order_id BIGINT, -- 关联订单
receive_time DATETIME,
use_time DATETIME,
expire_time DATETIME,
INDEX idx_user_id_status (user_id, status)
);
-- 优惠券使用记录
CREATE TABLE coupon_usage_log (
id BIGINT PRIMARY KEY,
user_id BIGINT,
coupon_id BIGINT,
order_id BIGINT,
discount_amount DECIMAL(10,2),
create_time DATETIME
);
```
---
**【核心代码设计】**
```java
/**
* 优惠计算引擎
*/
@Service
public class DiscountCalculator {
/**
* 计算最优优惠
*/
public DiscountResult calculateBestDiscount(
Long userId,
List<CartItem> items,
List<Long> availableCouponIds
) {
// 1. 获取可用优惠券
List<UserCoupon> availableCoupons = getAvailableCoupons(userId, availableCouponIds);
if (availableCoupons.isEmpty()) {
return DiscountResult.empty();
}
// 2. 计算订单金额
BigDecimal originalAmount = calculateOriginalAmount(items);
// 3. 尝试所有优惠券组合
DiscountResult bestResult = null;
BigDecimal maxDiscount = BigDecimal.ZERO;
// 3.1 单张优惠券
for (UserCoupon coupon : availableCoupons) {
DiscountResult result = calculateDiscount(coupon, items);
if (result.getDiscountAmount().compareTo(maxDiscount) > 0) {
bestResult = result;
maxDiscount = result.getDiscountAmount();
}
}
// 3.2 多张优惠券组合(如果可叠加)
List<UserCoupon> stackableCoupons = availableCoupons.stream()
.filter(c -> c.getTemplate().isStackable())
.collect(Collectors.toList());
if (stackableCoupons.size() > 1) {
// 递归计算所有组合
List<DiscountResult> combinations = calculateCombinations(stackableCoupons, items);
for (DiscountResult result : combinations) {
if (result.getDiscountAmount().compareTo(maxDiscount) > 0) {
bestResult = result;
maxDiscount = result.getDiscountAmount();
}
}
}
return bestResult;
}
/**
* 计算单张优惠券的优惠
*/
private DiscountResult calculateDiscount(UserCoupon coupon, List<CartItem> items) {
CouponTemplate template = coupon.getTemplate();
BigDecimal originalAmount = calculateOriginalAmount(items);
BigDecimal discountAmount = BigDecimal.ZERO;
switch (template.getType()) {
case 1: // 满减券
if (originalAmount.compareTo(template.getMinAmount()) >= 0) {
discountAmount = template.getDiscountValue();
// 不超过最大优惠
if (discountAmount.compareTo(template.getMaxDiscount()) > 0) {
discountAmount = template.getMaxDiscount();
}
}
break;
case 2: // 折扣券
discountAmount = originalAmount.multiply(
BigDecimal.ONE.subtract(template.getDiscountValue().divide(BigDecimal.valueOf(100)))
);
break;
case 3: // 立减券
discountAmount = template.getDiscountValue();
break;
}
return DiscountResult.builder()
.couponId(coupon.getId())
.discountAmount(discountAmount)
.finalAmount(originalAmount.subtract(discountAmount))
.build();
}
/**
* 获取可用优惠券
*/
private List<UserCoupon> getAvailableCoupons(Long userId, List<Long> couponIds) {
// 查询未使用且未过期的优惠券
return userCouponMapper.selectByUserIdAndStatusAndIds(
userId,
UserCouponStatus.UNUSED.getValue(),
couponIds
);
}
/**
* 计算原始金额
*/
private BigDecimal calculateOriginalAmount(List<CartItem> items) {
return items.stream()
.map(item -> item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity())))
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
}
/**
* 优惠券使用服务
*/
@Service
public class CouponUsageService {
@Autowired
private DiscountCalculator discountCalculator;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 使用优惠券(分布式锁保证幂等)
*/
@Transactional
public void useCoupon(Long userId, Long couponId, Long orderId) {
String lockKey = "coupon:lock:" + couponId;
// 分布式锁
Boolean locked = redisTemplate.opsForValue().setIfAbsent(
lockKey,
"1",
10,
TimeUnit.SECONDS
);
if (!locked) {
throw new BusinessException("系统繁忙,请稍后重试");
}
try {
// 1. 查询优惠券
UserCoupon coupon = userCouponMapper.selectById(couponId);
// 2. 校验状态
if (coupon.getStatus() != UserCouponStatus.UNUSED.getValue()) {
throw new BusinessException("优惠券已使用或已过期");
}
// 3. 校验有效期
if (coupon.getExpireTime().before(new Date())) {
throw new BusinessException("优惠券已过期");
}
// 4. 更新状态
userCouponMapper.updateStatus(couponId, UserCouponStatus.USED.getValue());
// 5. 记录使用日志
couponUsageLogMapper.insert(CouponUsageLog.builder()
.userId(userId)
.couponId(couponId)
.orderId(orderId)
.discountAmount(coupon.getTemplate().getDiscountValue())
.build());
} finally {
// 释放锁
redisTemplate.delete(lockKey);
}
}
}
```
---
## 3. 数据一致性设计
### 题目
**面试官问**
"在大促活动中如何保证Redis缓存和数据库的数据一致性"
---
### 参考回答
**【问题分析】**
```
不一致的场景:
1. 先更新数据库,后删除缓存
- A更新数据库
- B读取缓存旧数据
- A删除缓存
- B写入缓存旧数据
→ 不一致
2. 先删除缓存,后更新数据库
- A删除缓存
- B读取数据库旧数据
- B写入缓存旧数据
- A更新数据库
→ 不一致
3. 缓存过期
- 缓存过期
- A读取数据库旧数据
- B更新数据库
- A写入缓存旧数据
→ 不一致
```
---
**【解决方案】**
### 方案1延迟双删推荐
```java
/**
* 延迟双删
*/
@Service
public class DataUpdateService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private ProductMapper productMapper;
/**
* 更新商品(延迟双删)
*/
@Transactional
public void updateProduct(Product product) {
// 1. 删除缓存
String cacheKey = "product:" + product.getId();
redisTemplate.delete(cacheKey);
// 2. 更新数据库
productMapper.updateById(product);
// 3. 延迟删除缓存(异步)
asyncDeleteCache(cacheKey);
}
/**
* 异步延迟删除缓存
*/
@Async
public void asyncDeleteCache(String cacheKey) {
try {
// 延迟1秒保证数据库主从同步完成
Thread.sleep(1000);
// 再次删除缓存
redisTemplate.delete(cacheKey);
log.info("延迟删除缓存成功: {}", cacheKey);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
```
---
### 方案2订阅Binlog最终一致
```java
/**
* Binlog监听器
*/
@Component
public class BinlogListener {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 监听数据库更新
*/
@CanalEventListener(
destination = "product",
filter = ".*\\..*"
)
public void onUpdate(CanalEntry.Entry entry) {
// 解析Binlog
CanalEntry.RowChange rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) {
// 获取主键ID
Long id = rowData.getAfterColumnsList().stream()
.filter(c -> c.getIsKey())
.findFirst()
.map(c -> Long.valueOf(c.getValue()))
.orElse(null);
if (id != null) {
// 删除缓存
String cacheKey = "product:" + id;
redisTemplate.delete(cacheKey);
log.info("Binlog监听删除缓存: {}", cacheKey);
}
}
}
}
```
---
### 方案3分布式锁强一致
```java
/**
* 分布式锁保证一致性
*/
@Service
public class ConsistentService {
@Autowired
private RedissonClient redissonClient;
/**
* 更新数据(强一致)
*/
public void updateData(String key, Object value) {
String lockKey = "lock:data:" + key;
RLock lock = redissonClient.getLock(lockKey);
try {
// 加锁
lock.lock(10, TimeUnit.SECONDS);
// 1. 删除缓存
redisTemplate.delete(key);
// 2. 更新数据库
databaseMapper.update(key, value);
// 3. 查询最新值
Object newValue = databaseMapper.select(key);
// 4. 写入缓存
redisTemplate.opsForValue().set(key, newValue, 1, TimeUnit.HOURS);
} finally {
// 释放锁
lock.unlock();
}
}
}
```
---
## 4. 限流降级设计
### 题目
**面试官问**
"在大促活动中,如何设计限流和降级策略?"
---
### 参考回答
**【限流策略】**
```java
/**
* 多级限流
*/
@Component
public class MultiLevelRateLimiter {
/**
* Level 1: 接口级限流(保护服务)
*/
@Bean
public RateLimiter interfaceRateLimiter() {
// 令牌桶100万QPS
return RateLimiter.create(1000000.0);
}
/**
* Level 2: 用户级限流(防刷)
*/
public boolean checkUserLimit(Long userId) {
String key = "limit:user:" + userId;
Long count = redisTemplate.opsForValue().increment(key);
if (count == 1) {
redisTemplate.expire(key, 1, TimeUnit.MINUTES);
}
// 每分钟10次
return count <= 10;
}
/**
* Level 3: IP级限流防DDoS
*/
public boolean checkIPLimit(String ip) {
String key = "limit:ip:" + ip;
Long count = redisTemplate.opsForValue().increment(key);
if (count == 1) {
redisTemplate.expire(key, 1, TimeUnit.MINUTES);
}
// 每分钟100次
return count <= 100;
}
}
```
---
**【降级策略】**
```java
/**
* 降级策略
*/
@Service
public class DegradationService {
/**
* 自动降级(基于错误率)
*/
@Scheduled(fixedRate = 5000) // 每5秒检查
public void autoDegradation() {
// 1. 统计错误率
double errorRate = getErrorRate();
// 2. 判断是否需要降级
if (errorRate > 0.5) { // 错误率超过50%
enableDegradation();
sendAlert("错误率过高,已自动降级");
}
// 3. 统计RT
double rt = getAverageResponseTime();
if (rt > 1000) { // RT超过1秒
enableDegradation();
sendAlert("响应时间过长,已自动降级");
}
}
/**
* 降级执行
*/
public Result executeWithDegradation(String serviceName) {
if (isDegradationEnabled()) {
// 返回默认值或静态数据
return Result.degraded();
}
// 正常调用
return serviceClient.call(serviceName);
}
/**
* 手动降级(配置中心)
*/
@EventListener(condition = "#event.type == 'MANUAL_DEGRADATION'")
public void onManualDegradation(ConfigChangeEvent event) {
boolean enabled = Boolean.parseBoolean(event.getValue());
setDegradationEnabled(enabled);
}
}
```
---
## 💡 设计题答题技巧
### 1. 答题框架
```
【需求分析】
- 功能需求XX
- 非功能需求XX
- 约束条件XX
【架构设计】
- 整体架构图
- 技术选型
- 数据模型
【详细设计】
- 核心代码
- 关键流程
- 边界处理
【容量预估】
- QPS预估
- 存储预估
- 带宽预估
【压测方案】
- 压测工具
- 压测场景
- 优化方案
【监控告警】
- 监控指标
- 告警策略
```
---
### 2. 常见追问
**追问1如果QPS扩大10倍怎么办**
```
答:
1. 垂直扩展:升级服务器配置
2. 水平扩展:增加服务器数量
3. 缓存优化:增加缓存命中率
4. 读写分离:减轻主库压力
5. 分库分表:分散压力
6. CDN加速减轻源站压力
7. 异步化:消息队列削峰
```
**追问2如何保证数据一致性**
```
答:
1. 强一致:分布式锁
2. 最终一致延迟双删、订阅Binlog
3. 补偿机制:定时任务对账
```
**追问3如何应对突发流量**
```
答:
1. 弹性扩容Serverless、K8s HPA
2. 限流:保护系统
3. 降级:保证核心功能
4. 预热:提前加载缓存
```