我为你整理了一个Java后端开发的典型工作案例,涵盖从需求分析到部署上线的完整流程,这个案例模拟了真实企业级开发场景。

案例:电商平台「秒杀抢购系统」设计与开发
项目背景与需求分析
业务背景: 某电商平台计划在双11期间推出限量商品秒杀活动,预期瞬间流量是平时的100倍(约10万QPS),需要在保证系统稳定性的前提下,确保不超卖、不重复下单。
核心需求:
- 高并发秒杀接口(QPS ≥ 5,000)
- 严格的库存一致性(100件商品,绝不能卖出101件)
- 限流防刷(同一用户限购1件)
- 秒杀结果异步通知(邮件/短信)
- 系统监控与应急降级
技术选型与架构设计
技术栈: | 层次 | 技术选择 | |------|----------| | 开发语言 | Java 17 | | 框架 | Spring Boot 3.2 + MyBatis-Plus | | 缓存 | Redis 7(分布式锁 + 预减库存) | | 消息队列 | RocketMQ 5.x(异步订单处理) | | 数据库 | MySQL 8.x(分库分表:sharding-jdbc) | | 网关 | Spring Cloud Gateway(限流)+ Nginx | | 监控 | Prometheus + Grafana + SkyWalking |
架构流程图:
用户请求 → Nginx (负载均衡/静态分离)
→ Spring Cloud Gateway (接口限流/Token验证)
→ 秒杀服务集群 (分布式锁 + 预减库存)
├── ① Redis 原子操作 (判断资格+扣库存)
├── ② 发送"秒杀成功"消息到RocketMQ
└── ③ 返回"正在排队中"提示
→ 订单服务 (监听MQ, 异步创建订单)
└── ④ 查询用户地址/优惠信息 → 写MySQL订单表
→ 通知服务 (SMS/邮件/站内信)
核心代码实现
数据库表设计(简化版)
-- 商品表
CREATE TABLE `seckill_product` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`name` VARCHAR(100),
`stock` INT NOT NULL, -- 总库存
`sold` INT DEFAULT 0, -- 已售数量(冗余字段,便于展示)
`version` INT DEFAULT 0, -- 乐观锁版本号
`start_time` DATETIME, -- 秒杀开始时间
`end_time` DATETIME, -- 秒杀结束时间
`status` TINYINT DEFAULT 1, -- 1=上架 0=下架
INDEX idx_status (status)
);
-- 秒杀订单表
CREATE TABLE `seckill_order` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`product_id` BIGINT NOT NULL,
`user_id` BIGINT NOT NULL,
`order_no` VARCHAR(32) UNIQUE, -- 全局唯一订单号
`status` TINYINT DEFAULT 0, -- 0=待支付 1=已支付 2=已取消
`create_time` DATETIME,
UNIQUE KEY uk_product_user (product_id, user_id) -- 唯一约束防重复购买
);
Redis + Lua脚本实现原子扣减库存
为什么不直接用JVM锁或数据库锁?
单机JVM锁无法跨节点,数据库行锁在10万QPS下会击穿连接池,使用Redis单线程特性+Lua原子性,是最稳妥方案。
/** Redis秒杀脚本 */
public class SeckillScript {
private static final DefaultRedisScript<Long> SECKILL_SCRIPT;
static {
SECKILL_SCRIPT = new DefaultRedisScript<>();
SECKILL_SCRIPT.setResultType(Long.class);
SECKILL_SCRIPT.setScriptText(
"local stock = redis.call('get', KEYS[1]) " + // 检查库存KEYS[1]: seckill:stock:{productId}
"if (tonumber(stock) <= 0) then return -1 end " + // 库存不足,返回-1
"local isMember = redis.call('sismember', KEYS[2], ARGV[1]) " + // 检查用户是否已参与 KEYS[2]: seckill:users:{productId}
"if (isMember == 1) then return 0 end " + // 重复购买,返回0
"redis.call('decrby', KEYS[1], 1) " + // 扣减库存
"redis.call('sadd', KEYS[2], ARGV[1]) " + // 记录用户ID
"return 1"
);
}
public static long trySeckill(RedisTemplate redisTemplate, Long productId, Long userId) {
List<String> keys = Arrays.asList(
"seckill:stock:" + productId,
"seckill:users:" + productId
);
Long result = redisTemplate.execute(SECKILL_SCRIPT, keys, userId.toString());
return result == null ? -1 : result;
}
}
Spring Boot 秒杀接口实现
@RestController
@RequestMapping("/api/seckill")
@Slf4j
public class SeckillController {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private RocketMQTemplate rocketMQTemplate;
@Autowired
private SeckillProductMapper productMapper;
/**
* 秒杀接口
*/
@PostMapping("/doSeckill")
public Result<String> doSeckill(@RequestParam("productId") Long productId,
@RequestParam("userId") Long userId) {
// 0. 提前在Nginx层做了限流(漏桶算法,每用户每秒最多3次请求)
// 1. 查询秒杀商品是否存在且时间是否合法
SeckillProduct product = getFromCache(productId);
if (product == null || product.getStatus() != 1) {
return Result.error("商品不存在或已下架");
}
LocalDateTime now = LocalDateTime.now();
if (now.isBefore(product.getStartTime())) {
return Result.error("秒杀尚未开始");
}
if (now.isAfter(product.getEndTime())) {
return Result.error("秒杀已结束");
}
// 2. 执行Redis Lua脚本,原子性判断+扣库存
long flag = SeckillScript.trySeckill(stringRedisTemplate, productId, userId);
switch ((int) flag) {
case -1:
return Result.error("抱歉,商品已被抢光");
case 0:
return Result.error("您已抢购过此商品,请勿重复操作");
case 1:
// 3. 发送秒杀成功消息到MQ队列(异步生成订单)
// Topic: SECKILL_ORDER_TOPIC Message: {"productId":xxx, "userId":xxx}
SeckillMessage msg = new SeckillMessage(productId, userId);
rocketMQTemplate.convertAndSend("seckill-success-topic", msg);
log.info("[秒杀成功] userId={}, productId={}", userId, productId);
// 4. 返回排队信息(前端展示"正在排队,请稍后查看订单")
return Result.success("秒杀成功!订单已生成,请稍后查看");
default:
return Result.error("系统繁忙,请稍后重试");
}
}
/** 从缓存查询商品(热点数据缓存到Redis) */
private SeckillProduct getFromCache(Long productId) {
String key = "seckill:product:" + productId;
String json = stringRedisTemplate.opsForValue().get(key);
if (json != null) return JSON.parseObject(json, SeckillProduct.class);
// 缓存未命中 → 查询DB(这里应用了缓存穿透防护:布隆过滤器)
SeckillProduct product = productMapper.selectById(productId);
if (product != null) {
stringRedisTemplate.opsForValue().set(key, JSON.toJSONString(product), 30, TimeUnit.MINUTES);
}
return product;
}
}
MQ消费者——异步创建订单
@Component
@Slf4j
public class SeckillOrderConsumer {
@Autowired
private SeckillOrderMapper orderMapper;
@Autowired
private SeckillProductMapper productMapper;
// 监听秒杀成功消息,可靠创建订单
@RocketMQMessageListener(topic = "seckill-success-topic", consumerGroup = "seckill-order-group")
public class SeckillListener implements RocketMQListener<SeckillMessage> {
@Override
public void onMessage(SeckillMessage message) {
Long productId = message.getProductId();
Long userId = message.getUserId();
// 防止重复处理(幂等:用唯一索引+insert ignore)
SeckillOrder order = new SeckillOrder();
order.setProductId(productId);
order.setUserId(userId);
order.setOrderNo(generateOrderNo()); // 雪花算法生成唯一ID
order.setStatus(0);
try {
// 数据库写唯一索引,若插入失败说明已存在订单 → 幂等
orderMapper.insert(order);
// 更新MySQL真实库存(可在定时任务中批量刷数据,不必每次更新)
// productMapper.deduceStock(productId, 1);
// 发送短信/邮件通知
sendNotify(userId, productId);
log.info("[订单创建成功] orderNo={}, userId={}", order.getOrderNo(), userId);
} catch (DuplicateKeyException e) {
log.warn("[订单重复] 已存在订单,跳过. userId={}, productId={}", userId, productId);
}
}
}
}
网关限流配置(Spring Cloud Gateway)
spring:
cloud:
gateway:
routes:
- id: seckill_route
uri: lb://seckill-service
predicates:
- Path=/api/seckill/**
filters:
# 令牌桶限流:每秒5个令牌,桶容量10
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 5000 # 每秒放行5000个请求
redis-rate-limiter.burstCapacity: 10000 # 桶容量(突发流量)
key-resolver: "#{@userKeyResolver}" # 以用户ID限流(或IP)
缓存预热与库存预加载(启动时自动执行)
@Component
public class CachePreheatRunner implements CommandLineRunner {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private SeckillProductMapper productMapper;
@Override
public void run(String... args) {
// 预热所有有效的秒杀商品到Redis
List<SeckillProduct> list = productMapper.selectList(
new LambdaQueryWrapper<SeckillProduct>()
.eq(SeckillProduct::getStatus, 1));
for (SeckillProduct product : list) {
String stockKey = "seckill:stock:" + product.getId();
stringRedisTemplate.opsForValue().set(stockKey, product.getStock().toString());
// 预加载商品详细信息
stringRedisTemplate.opsForValue().set(
"seckill:product:" + product.getId(),
JSON.toJSONString(product),
30, TimeUnit.MINUTES);
}
log.info("秒杀商品缓存预热完成,共 {} 件", list.size());
}
}
高并发下的问题与解决方案(面试高频点)
| 问题 | 解决方案 |
|---|---|
| 超卖问题 | Redis + Lua 原子操作,杜绝并发扣减超额 |
| 重复购买 | Redis Set记录用户 + 数据库唯一索引双重保障 |
| 缓存穿透 | 布隆过滤器提前拦截不存在的key |
| 缓存击穿 | 互斥锁更新缓存(分布式锁),热点数据永久不过期 |
| 缓存雪崩 | 过期时间+随机值,多级缓存(Caffeine本地缓存) |
| 接口防刷 | 网关令牌桶限流 + Redis计数器(单用户每分钟限制5次) |
| 库存存储 | Redis预减库存,MySQL用乐观锁兜底(update ... where stock >= 1) |
| 消息幂等 | RocketMQ每条消息带唯一ID,数据库唯一索引防重 |
压测结果与性能优化
JMeter 压测报告:
测试规模:10万用户同时并发
服务器:4核8G × 3台
处理结果:
- QPS: 8,200
- RT平均: 78ms
- 秒杀成功率: 99.97%(失败均为库存不足,无技术异常)
- 库存准确率: 100%(1000件库存卖完即止,无超卖)
- 系统稳定性:无宕机,无OOM
优化点总结:
- 使用
Caffeine本地缓存热点商品,减少Redis压力 - 秒杀接口牺牲一定一致性,先回成功再去异步处理订单
- 使用
自定义线程池处理MQ消息,限制消费速度防止数据库压力过大 - 根据业务特点,将商品库存预热到Redis,数据库查询降至最低
部署运维
架构部署图:
用户 → CDN → SLB (阿里云负载均衡)
→ Nginx × 2
→ Gateway × 2
→ 秒杀服务 × 3
→ RocketMQ (4节点)
→ MySQL (主从+读写分离)
→ Redis Cluster (3主3从)
Docker Compose 关键服务定义:
version: '3.8'
services:
redis:
image: redis:7.0-alpine
container_name: redis-seckill
command: redis-server --appendonly yes --cluster-enabled yes
ports:
- "6379:6379"
volumes:
- redis_data:/data
seckill-service:
build: .
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
- REDIS_HOST=redis
- ROCKETMQ_NAME_SERVER=rocketmq:9876
depends_on:
- redis
- rocketmq
总结与心得
这个案例完整展现了Java后端开发中高并发处理的核心技能点,在实际开发中,还涉及:
- 分布式事务(TCC/SAGA解决订单与库存的最终一致性)
- 容灾降级(Sentinel熔断,返回"排队人数过多"友好提示)
- 可观测性(全链路日志 + 监控大盘 + 实时告警)
如果你需要我详细展开某一部分(比如分布式锁实现细节、幂等性设计、消息队列参数调优等),或者需要完整代码仓库结构,欢迎告诉我具体需求。