Spring Boot实现商城高并发秒杀
简介
本博客介绍了如何使用Spring Boot实现商城高并发秒杀案例,用到了Redis和MySQL作为数据存储,以及MyBatis作为数据访问框架。该案例采用了一些优化手段,如缓存预热、数据库连接池、分布式锁等,以保证在高并发情况下能够保证系统的稳定性和性能。
准备工作
在开始之前,需要进行一些准备工作。首先,需要安装并配置好Redis和MySQL数据库。其次,需要在项目中引入相关依赖,如下所示:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.6.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.4</version>
</dependency>
</dependencies>
其中,spring-boot-starter-web用于创建Web应用,mybatis-spring-boot-starter用于集成MyBatis框架,jedis用于操作Redis数据库,druid用于创建数据库连接池,lombok用于简化Java代码,commons-lang3用于提供一些常用的工具类,jackson-databind用于处理JSON数据。
数据库设计
接下来,需要设计数据库表结构。本案例中需要创建两张表,分别为商品表和秒杀商品表,如下所示:
商品表
字段名 | 类型 | 描述 |
---|---|---|
id | bigint | 商品ID |
name | varchar | 商品名称 |
price | decimal | 商品价格 |
秒杀商品表
字段名 | 类型 | 描述 |
---|---|---|
id | bigint | 秒杀商品ID |
goods_id | bigint | 商品ID |
stock | int | 库存数量 |
start_time | datetime | 秒杀开始时间 |
end_time | datetime | 秒杀结束时间 |
其中,商品表用于存储商品的基本信息,秒杀商品表用于存储秒杀商品的信息,包括商品ID、库存数量、秒杀开始时间和秒杀结束时间等。需要注意的是,商品表和秒杀商品表之间存在一对多的关系,即一个商品可以对应多个秒杀商品。
业务实现
接下来,需要实现业务逻辑。首先,需要创建商品和秒杀商品的实体类,如下所示:
@Data
public class Goods {
private Long id;
private String name;
private BigDecimal price;
}
@Data
public class SeckillGoods {
private Long id;
private Long goodsId;
private Integer stock;
private Date startTime;
private Date endTime;
}
然后,需要创建商品和秒杀商品的数据访问层接口和实现类,以及相关的SQL语句,如下所示:
public interface GoodsMapper {
@Select("SELECT * FROM goods WHERE id = #{id}")
Goods getGoodsById(@Param("id") Long id);
}
public interface SeckillGoodsMapper {
@Select("SELECT * FROM seckill_goods WHERE id = #{id}")
SeckillGoods getSeckillGoodsById(@Param("id") Long id);
@Update("UPDATE seckill_goods SET stock = stock - 1 WHERE id = #{id} AND stock > 0")
int reduceStock(@Param("id") Long id);
}
@Mapper
public class GoodsMapperImpl implements GoodsMapper {
private final SqlSession sqlSession;
public GoodsMapperImpl(SqlSession sqlSession) {
this.sqlSession = sqlSession;
}
@Override
public Goods getGoodsById(Long id) {
return sqlSession.selectOne("getGoodsById", id);
}
}
@Mapper
public class SeckillGoodsMapperImpl implements SeckillGoodsMapper {
private final SqlSession sqlSession;
public SeckillGoodsMapperImpl(SqlSession sqlSession) {
this.sqlSession = sqlSession;
}
@Override
public SeckillGoods getSeckillGoodsById(Long id) {
return sqlSession.selectOne("getSeckillGoodsById", id);
}
@Override
public int reduceStock(Long id) {
return sqlSession.update("reduceStock", id);
}
}
其中,GoodsMapper和SeckillGoodsMapper分别对应商品和秒杀商品的数据访问层接口,GoodsMapperImpl和SeckillGoodsMapperImpl分别对应它们的实现类。需要注意的是,在实现类中使用了MyBatis的SQL语句注解。
接下来,需要实现秒杀商品的业务逻辑。为了避免高并发情况下的超卖问题,需要使用分布式锁来保证同一时间只有一个线程能够执行减库存操作。在本案例中,使用Redis作为分布式锁的存储媒介
@Service
public class SeckillService {
private final RedisTemplate<String, Object> redisTemplate;
private final SeckillGoodsMapper seckillGoodsMapper;
public SeckillService(RedisTemplate<String, Object> redisTemplate, SeckillGoodsMapper seckillGoodsMapper) {
this.redisTemplate = redisTemplate;
this.seckillGoodsMapper = seckillGoodsMapper;
}
/**
* 秒杀商品
* @param seckillId 秒杀商品ID
* @param userId 用户ID
* @return 秒杀结果
*/
public String seckill(Long seckillId, Long userId) {
String lockKey = "seckill:lock:" + seckillId;
String requestId = UUID.randomUUID().toString();
try {
// 加锁
Boolean lock = redisTemplate.opsForValue().setIfAbsent(lockKey, requestId, 5, TimeUnit.SECONDS);
if (lock == null || !lock) {
return "秒杀失败:获取锁失败";
}
// 查询秒杀商品信息
SeckillGoods seckillGoods = seckillGoodsMapper.getSeckillGoodsById(seckillId);
if (seckillGoods == null) {
return "秒杀失败:秒杀商品不存在";
}
// 检查秒杀时间
Date now = new Date();
if (now.before(seckillGoods.getStartTime()) || now.after(seckillGoods.getEndTime())) {
return "秒杀失败:秒杀未开始或已结束";
}
// 减库存
int rows = seckillGoodsMapper.reduceStock(seckillId);
if (rows == 0) {
return "秒杀失败:库存不足";
}
// 生成订单
// ...
return "秒杀成功";
} finally {
// 释放锁
if (requestId.equals(redisTemplate.opsForValue().get(lockKey))) {
redisTemplate.delete(lockKey);
}
}
}
}
在上述代码中,seckill()方法对应秒杀商品的业务逻辑。首先,使用Redis的setIfAbsent()方法尝试获取锁,如果获取失败则说明有其他线程正在执行秒杀操作,返回秒杀失败。接着,查询秒杀商品信息并检查秒杀时间,如果不在秒杀时间范围内则返回秒杀失败。然后,使用reduceStock()方法减少秒杀商品库存,如果库存不足则返回秒杀失败。最后,生成订单并返回秒杀成功。
需要注意的是,在获取锁和释放锁时需要使用唯一的requestId来标识当前线程,以避免误释放其他线程持有的锁。
部署和测试
最后,需要将应用部署到服务器上进行测试。为了模拟高并发情况,可以使用Apache JMeter等工具生成大量并发请求。测试过程中需要注意监控系统的资源使用情况,如CPU、内存、磁盘IO等。如果发现系统负载过高或出现响应延迟等问题,可以考虑对系统进行优化或升级硬件等操作。
另外,为了保证数据的一致性和可靠性,在进行秒杀操作时需要注意以下几点:
- 减库存操作需要使用数据库事务保证原子性,避免出现超卖等问题。
- 生成订单前需要使用Redis的分布式锁确保只有一个线程能够生成订单,避免出现重复订单等问题。
- 在进行高并发操作时,需要对MySQL、Redis等系统进行优化,如增加缓存、优化查询语句等,以提高系统的性能和稳定性
结语
本文主要介绍了使用Spring Boot实现商城高并发秒杀的案例,其中使用了MySQL、Redis和MyBatis等技术。在实现过程中,需要注意对系统进行优化,以提高系统的性能和稳定性。通过本文的学习,相信读者可以对如何实现高并发秒杀有一个初步的了解。
- 点赞
- 收藏
- 关注作者
评论(0)