- 项目深挖题:5个重点项目STAR法则回答,针对每个项目准备深挖问题 - 场景设计题:秒杀系统、优惠券系统、数据一致性、限流降级等设计题 - 个人发展题:职业规划、学习能力、团队协作、抗压能力、价值观 - 离职原因与动机:离职原因、择公司、职业目标、反问技巧 - 薪资谈判:谈判策略、Web3特有问题(代币激励、远程工作)、DO & DON'T 针对简历特点: - 结合字节跳动、阿里巴巴、ThoughtWorks的项目经验 - 提供STAR法则回答模板 - 强调Web2经验向Web3的转化 - 包含大量代码示例和架构图 - 提供薪资谈判实战策略 适用场景: - 面试前准备:项目深挖、场景设计 - 面试中:个人发展、离职原因 - 面试后:薪资谈判、offer评估
26 KiB
26 KiB
场景设计题
说明
场景设计题是考察系统设计能力和问题解决能力的常见题型。本文档针对你的简历背景,设计相关的场景题。
1. 电商促销系统设计
题目
面试官问: "假设你要设计一个电商秒杀系统,支持百万用户同时抢购,你会如何设计?"
参考回答
【需求分析】
核心需求:
1. 高并发:百万用户同时抢购
2. 防超卖:库存不能卖超
3. 高可用:服务不能挂
4. 公平性:先到先得
5. 用户体验:快速响应
非功能需求:
- QPS: 100万+
- 延迟: <100ms
- 可用性: 99.99%
- 数据一致性: 强一致
【架构设计】
┌─────────────────────────────────────────────┐
│ 客户端层 │
│ 移动App、Web、H5 │
└──────────────┬──────────────────────────────┘
│
↓
┌─────────────────────────────────────────────┐
│ CDN层 │
│ - 静态资源(图片、CSS、JS) │
│ - 动态接口(边缘计算) │
└──────────────┬──────────────────────────────┘
│
↓
┌─────────────────────────────────────────────┐
│ API网关层 │
│ - 限流:100万QPS │
│ - 熔断:保护后端 │
│ - 负载均衡 │
└──────────────┬──────────────────────────────┘
│
┌────────┴────────┐
↓ ↓
┌──────────┐ ┌──────────┐
│ 缓存层 │ │ 服务层 │
└──────────┘ └──────────┘
│ │
↓ ↓
┌─────────────────────────────────┐
│ 数据库层 │
│ MySQL主从 + 分库分表 │
└─────────────────────────────────┘
【详细设计】
1. 缓存层设计(关键)
/**
* 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. 限流设计
/**
* 限流策略(多级)
*/
@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. 防刷设计
/**
* 防刷策略
*/
@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. 降级策略
/**
* 降级策略
*/
@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. 库存回滚(重要)
/**
* 库存回滚机制
*/
@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万
- 峰值QPS:100万/秒
- 考虑10倍冗余:1000万QPS
2. 缓存容量
- Redis实例:10个
- 每个实例:10万QPS
- 总计:100万QPS
3. 消息队列
- Kafka分区:100个
- 消费者:200个
- 吞吐量:100万/秒
4. 数据库
- 主库:1个(写入)
- 从库:10个(读取)
- 分库分表:100个库,1000个表
【压测方案】
/**
* 压测方案
*/
@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
- 准确:不能算错
- 灵活:方便配置新规则
【数据模型设计】
-- 优惠券模板
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
);
【核心代码设计】
/**
* 优惠计算引擎
*/
@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:延迟双删(推荐)
/**
* 延迟双删
*/
@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(最终一致)
/**
* 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:分布式锁(强一致)
/**
* 分布式锁保证一致性
*/
@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. 限流降级设计
题目
面试官问: "在大促活动中,如何设计限流和降级策略?"
参考回答
【限流策略】
/**
* 多级限流
*/
@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;
}
}
【降级策略】
/**
* 降级策略
*/
@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. 预热:提前加载缓存