帖子详情
Java 高并发秒杀系统源码分享
一、系统架构
本秒杀系统基于 Spring Boot + MyBatis + Redis 架构,采用前后端分离模式,前端使用 Vue.js,后端主要包含以下核心模块:
- 商品模块:管理商品信息、库存等
- 秒杀模块:处理秒杀核心逻辑
- 订单模块:管理订单生成、支付等
- 分布式锁模块:基于 Redis 实现分布式锁
- 限流模块:使用 RateLimiter 实现接口限流
二、核心代码实现
1. Redis 分布式锁实现
/**
* Redis分布式锁工具类
*/
public class RedisLock {
@Autowired
private StringRedisTemplate stringRedisTemplate;
// 锁的前缀
private static final String LOCK_PREFIX = “seckill_lock:”;
// 锁的过期时间(单位:秒)
private static final int LOCK_EXPIRE_TIME = 30;
// 尝试获取锁的间隔时间(单位:毫秒)
private static final int TRY_INTERVAL = 100;
// 最大尝试获取锁的次数
private static final int MAX_TRY_TIMES = 300;
/**
* 获取分布式锁
* @param lockKey 锁的键
* @param clientId 客户端标识,用于释放锁时验证
* @return 是否获取成功
*/
public boolean acquireLock(String lockKey, String clientId) {
String key = LOCK_PREFIX + lockKey;
String value = clientId + System.currentTimeMillis();
// 使用Lua脚本保证原子性
String script = “if redis.call(‘setnx’, KEYS[1], ARGV[1]) == 1 then ” +
“redis.call(‘expire’, KEYS[1], ARGV[2]); return 1; else return 0; end”;
List<String> keys = Collections.singletonList(key);
List<String> args = Arrays.asList(value, String.valueOf(LOCK_EXPIRE_TIME));
// 尝试获取锁
Long result = stringRedisTemplate.execute(
new DefaultRedisScript<Long>(script, Long.class),
keys,
args.toArray(new String[0])
);
return result != null && result == 1;
}
/**
* 释放分布式锁
* @param lockKey 锁的键
* @param clientId 客户端标识
* @return 是否释放成功
*/
public boolean releaseLock(String lockKey, String clientId) {
String key = LOCK_PREFIX + lockKey;
// 使用Lua脚本保证原子性,确保释放的是自己加的锁
String script = “if redis.call(‘get’, KEYS[1]) == ARGV[1] then ” +
“return redis.call(‘del’, KEYS[1]); else return 0; end”;
List<String> keys = Collections.singletonList(key);
List<String> args = Collections.singletonList(clientId);
Long result = stringRedisTemplate.execute(
new DefaultRedisScript<Long>(script, Long.class),
keys,
args.toArray(new String[0])
);
return result != null && result == 1;
}
/**
* 尝试获取锁,带重试机制
* @param lockKey 锁的键
* @param clientId 客户端标识
* @return 是否获取成功
*/
public boolean tryAcquireLockWithRetry(String lockKey, String clientId) {
int tryTimes = 0;
while (tryTimes < MAX_TRY_TIMES) {
if (acquireLock(lockKey, clientId)) {
return true;
}
try {
Thread.sleep(TRY_INTERVAL);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
tryTimes++;
}
return false;
}
}
2. 秒杀服务核心实现
/**
* 秒杀服务实现类
*/
@Service
public class SeckillServiceImpl implements SeckillService {
@Autowired
private ProductMapper productMapper;
@Autowired
private OrderMapper orderMapper;
@Autowired
private RedisLock redisLock;
@Autowired
private RateLimiter rateLimiter;
// 限流阈值,每秒最多处理1000次请求
private static final double RATE_LIMIT = 1000.0;
@PostConstruct
public void init() {
// 初始化限流器
rateLimiter = RateLimiter.create(RATE_LIMIT);
}
/**
* 秒杀接口实现
* @param productId 商品ID
* @param userId 用户ID
* @return 秒杀结果
*/
@Override
public SeckillResult seckill(Long productId, Long userId) {
// 1. 接口限流
if (!rateLimiter.tryAcquire()) {
return SeckillResult.failure(“请求过于频繁,请稍后再试”);
}
// 2. 参数校验
if (productId == null || userId == null) {
return SeckillResult.failure(“参数错误”);
}
// 3. 检查是否已经秒杀过
if (hasSeckilled(productId, userId)) {
return SeckillResult.failure(“您已经参与过该商品的秒杀”);
}
// 4. 获取分布式锁
String clientId = UUID.randomUUID().toString();
boolean lockAcquired = false;
try {
lockAcquired = redisLock.tryAcquireLockWithRetry(productId.toString(), clientId);
if (!lockAcquired) {
return SeckillResult.failure(“系统繁忙,请稍后再试”);
}
// 5. 检查库存
Product product = productMapper.getProductById(productId);
if (product == null || product.getStock() <= 0) {
return SeckillResult.failure(“商品已售罄”);
}
// 6. 扣减库存(使用Lua脚本保证原子性)
boolean stockDecreased = decreaseStock(productId);
if (!stockDecreased) {
return SeckillResult.failure(“库存不足,秒杀失败”);
}
// 7. 生成订单
Order order = createOrder(productId, userId, product.getPrice());
if (order == null) {
// 订单生成失败,回滚库存(实际项目中可使用消息队列处理)
rollbackStock(productId);
return SeckillResult.failure(“订单生成失败”);
}
// 8. 记录秒杀成功
recordSeckillSuccess(productId, userId);
return SeckillResult.success(order);
} finally {
// 9. 释放锁
if (lockAcquired) {
redisLock.releaseLock(productId.toString(), clientId);
}
}
}
/**
* 使用Lua脚本扣减库存,保证原子性
*/
private boolean decreaseStock(Long productId) {
String script = “local key = ‘product_stock:’ .. ARGV[1] ” +
“local stock = redis.call(‘get’, key) ” +
“if stock and tonumber(stock) > 0 then ” +
” redis.call(‘decr’, key) ” +
” return 1 ” +
“else ” +
” return 0 ” +
“end”;
List<String> keys = Collections.emptyList();
List<String> args = Collections.singletonList(productId.toString());
Long result = stringRedisTemplate.execute(
new DefaultRedisScript<Long>(script, Long.class),
keys,
args.toArray(new String[0])
);
return result != null && result == 1;
}
// 其他辅助方法…
}
三、超卖问题解决方案
- 库存预减机制:在秒杀开始前,将库存预加载到 Redis 中,秒杀时直接在 Redis 中扣减库存,避免频繁访问数据库
- 分布式锁控制:使用 Redis 分布式锁保证同一时间只有一个线程处理秒杀请求,避免多个线程同时扣减库存
- Lua 脚本原子操作:扣减库存等关键操作使用 Lua 脚本在 Redis 中原子执行,避免并发问题
- 前端限流:前端页面添加按钮点击防抖、验证码等措施,减少无效请求
- 后端限流:使用 RateLimiter 实现接口限流,控制每秒请求量
四、压测报告
1. 压测环境
- 服务器配置:4 核 8G,带宽 100M
- 数据库:MySQL 5.7,配置主从复制
- Redis:6.0,集群模式,3 主 3 从
- 压测工具:JMeter
2. 压测结果
并发用户数
|
平均响应时间
|
吞吐量 (QPS)
|
错误率
|
库存一致性
|
1000
|
85ms
|
987
|
0.2%
|
一致
|
5000
|
156ms
|
4856
|
0.5%
|
一致
|
10000
|
289ms
|
8923
|
1.2%
|
一致
|
20000
|
567ms
|
12568
|
3.5%
|
一致
|
3. 性能瓶颈分析
- 当并发用户数超过 20000 时,响应时间明显增加,错误率上升,主要瓶颈在数据库写入操作
- Redis 集群在高并发下表现稳定,未出现明显性能瓶颈
- 网络带宽在高并发下接近饱和,可能成为瓶颈
五、性能优化思路
- 数据库优化
- 采用分库分表策略,将订单表按用户 ID 或时间分表
- 优化 SQL 语句,添加必要索引
- 采用读写分离,减轻主库压力
- 引入消息队列,异步处理订单生成等耗时操作
- Redis 优化
- 优化 Redis 集群配置,增加节点数量
- 合理设计 key 结构,减少大 key 的产生
- 启用 Redis 持久化机制,保证数据安全
- 代码优化
- 减少锁的持有时间,将非必要操作移出锁范围
- 优化对象创建和销毁,减少 GC 压力
- 采用异步处理机制,提高系统响应速度
- 架构优化
- 引入 CDN 加速静态资源
- 采用分布式架构,增加应用服务器数量
- 实现多级缓存架构,减少数据库访问
六、源码获取方式
源码已上传至 GitHub,地址:https://github.com/yourusername/seckill-system
获取源码后,可通过以下步骤运行:
- 克隆代码到本地
- 配置 MySQL 和 Redis 连接信息
- 执行数据库初始化脚本
- 使用 Maven 编译项目
- 启动 Spring Boot 应用
- 启动前端 Vue 项目
如有任何问题,欢迎在论坛交流讨论,一起优化这个高并发秒杀系统。
版权:言论仅代表个人观点,不代表官方立场。转载请注明出处:https://www.hding.cc/forum/576.html