基于Springboot+Redis秒杀系统 demo
基于SpringBoot+Redis的商品秒杀系统的Demo。这个例子将展示如何防止商品超卖。
- 首先创建项目依赖(pom.xml):
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
- 创建商品实体类(Product.java):
@Data
public class Product {
private Long id;
private String name;
private Integer stock;
private BigDecimal price;
}
- 创建订单实体类(Order.java):
@Data
public class Order {
private Long id;
private Long productId;
private Long userId;
private Integer quantity;
private Date createTime;
}
- 创建Mapper接口(ProductMapper.java):
@Mapper
public interface ProductMapper {
@Select("SELECT * FROM product WHERE id = #{id}")
Product getById(Long id);
@Update("UPDATE product SET stock = stock - #{quantity} WHERE id = #{id} AND stock >= #{quantity}")
int decreaseStock(@Param("id") Long id, @Param("quantity") Integer quantity);
}
- 创建Service层(ProductService.java):
@Service
@Slf4j
public class ProductService {
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private ProductMapper productMapper;
private static final String PRODUCT_STOCK_KEY = "product:stock:";
private static final String LOCK_KEY = "product:lock:";
@PostConstruct
public void init() {
// 初始化商品库存到Redis
Product product = productMapper.getById(1L);
redisTemplate.opsForValue().set(PRODUCT_STOCK_KEY + product.getId(),
String.valueOf(product.getStock()));
}
public boolean seckill(Long productId, Long userId) {
String lockKey = LOCK_KEY + productId;
String stockKey = PRODUCT_STOCK_KEY + productId;
try {
// 尝试获取分布式锁
Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
if (!locked) {
return false;
}
// 检查库存
String stock = redisTemplate.opsForValue().get(stockKey);
if (Integer.parseInt(stock) <= 0) {
return false;
}
// 扣减Redis库存
Long remain = redisTemplate.opsForValue().decrement(stockKey);
if (remain < 0) {
// 库存不足,回滚
redisTemplate.opsForValue().increment(stockKey);
return false;
}
// 扣减MySQL库存
int result = productMapper.decreaseStock(productId, 1);
if (result <= 0) {
// 数据库扣减失败,回滚Redis
redisTemplate.opsForValue().increment(stockKey);
return false;
}
// 创建订单
Order order = new Order();
order.setProductId(productId);
order.setUserId(userId);
order.setQuantity(1);
order.setCreateTime(new Date());
// orderMapper.insert(order);
return true;
} finally {
// 释放锁
redisTemplate.delete(lockKey);
}
}
}
- 创建Controller层(ProductController.java):
@RestController
@RequestMapping("/api/product")
public class ProductController {
@Autowired
private ProductService productService;
@PostMapping("/seckill/{productId}")
public String seckill(@PathVariable Long productId, @RequestParam Long userId) {
boolean success = productService.seckill(productId, userId);
return success ? "秒杀成功" : "秒杀失败";
}
}
- 配置文件(application.yml):
spring:
redis:
host: localhost
port: 6379
datasource:
url: jdbc:mysql://localhost:3306/seckill
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
- 数据库表结构:
CREATE TABLE product (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
stock INT NOT NULL,
price DECIMAL(10,2) NOT NULL
);
CREATE TABLE `order` (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
product_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
quantity INT NOT NULL,
create_time DATETIME NOT NULL
);
测试方法:
- 单元测试:
@SpringBootTest
class ProductServiceTest {
@Autowired
private ProductService productService;
@Test
void testSeckill() {
boolean result = productService.seckill(1L, 1L);
assertTrue(result);
}
}
核心代码解释
- 分布式锁解决并发问题:
Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
if (!locked) {
return false;
}
- 使用Redis的setNX命令实现分布式锁
- 防止多个服务器节点同时操作同一商品
- 设置10秒超时,防止死锁
- 双重库存校验:
// Redis库存校验
String stock = redisTemplate.opsForValue().get(stockKey);
if (Integer.parseInt(stock) <= 0) {
return false;
}
// MySQL库存校验
int result = productMapper.decreaseStock(productId, 1);
if (result <= 0) {
redisTemplate.opsForValue().increment(stockKey);
return false;
}
- 先检查Redis库存,快速失败
- 再检查MySQL库存,确保数据一致性
- 库存回滚机制:
Long remain = redisTemplate.opsForValue().decrement(stockKey);
if (remain < 0) {
// 库存不足,回滚
redisTemplate.opsForValue().increment(stockKey);
return false;
}
- 当库存不足时自动回滚
- 保证数据一致性
- 缓存预热:
@PostConstruct
public void init() {
Product product = productMapper.getById(1L);
redisTemplate.opsForValue().set(PRODUCT_STOCK_KEY + product.getId(),
String.valueOf(product.getStock()));
}
- 系统启动时初始化Redis库存
- 避免首次访问数据库压力
- 完整的事务流程:
try {
// 1. 获取锁
// 2. 检查库存
// 3. 扣减Redis库存
// 4. 扣减MySQL库存
// 5. 创建订单
} finally {
// 释放锁
redisTemplate.delete(lockKey);
}
- 确保锁一定会被释放
- 保证操作的原子性
这个实现解决了秒杀系统的主要问题:
- 并发安全(分布式锁)
- 超卖问题(双重库存校验)
- 性能问题(Redis缓存)
- 数据一致性(回滚机制)
- 死锁问题(锁超时)
原文地址:https://blog.csdn.net/web2u/article/details/145223958
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!