自学内容网 自学内容网

基于Springboot+Redis秒杀系统 demo

基于SpringBoot+Redis的商品秒杀系统的Demo。这个例子将展示如何防止商品超卖。

  1. 首先创建项目依赖(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>
  1. 创建商品实体类(Product.java):
@Data
public class Product {
    private Long id;
    private String name;
    private Integer stock;
    private BigDecimal price;
}
  1. 创建订单实体类(Order.java):
@Data
public class Order {
    private Long id;
    private Long productId;
    private Long userId;
    private Integer quantity;
    private Date createTime;
}
  1. 创建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);
}
  1. 创建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);
        }
    }
}
  1. 创建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 ? "秒杀成功" : "秒杀失败";
    }
}
  1. 配置文件(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
  1. 数据库表结构:
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
);

测试方法:

  1. 单元测试:
@SpringBootTest
class ProductServiceTest {
    
    @Autowired
    private ProductService productService;
    
    @Test
    void testSeckill() {
        boolean result = productService.seckill(1L, 1L);
        assertTrue(result);
    }
}

核心代码解释

  1. 分布式锁解决并发问题:
Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
if (!locked) {
    return false;
}
  • 使用Redis的setNX命令实现分布式锁
  • 防止多个服务器节点同时操作同一商品
  • 设置10秒超时,防止死锁
  1. 双重库存校验:
// 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库存,确保数据一致性
  1. 库存回滚机制:
Long remain = redisTemplate.opsForValue().decrement(stockKey);
if (remain < 0) {
    // 库存不足,回滚
    redisTemplate.opsForValue().increment(stockKey);
    return false;
}
  • 当库存不足时自动回滚
  • 保证数据一致性
  1. 缓存预热:
@PostConstruct
public void init() {
    Product product = productMapper.getById(1L);
    redisTemplate.opsForValue().set(PRODUCT_STOCK_KEY + product.getId(), 
        String.valueOf(product.getStock()));
}
  • 系统启动时初始化Redis库存
  • 避免首次访问数据库压力
  1. 完整的事务流程:
try {
    // 1. 获取锁
    // 2. 检查库存
    // 3. 扣减Redis库存
    // 4. 扣减MySQL库存
    // 5. 创建订单
} finally {
    // 释放锁
    redisTemplate.delete(lockKey);
}
  • 确保锁一定会被释放
  • 保证操作的原子性

这个实现解决了秒杀系统的主要问题:

  1. 并发安全(分布式锁)
  2. 超卖问题(双重库存校验)
  3. 性能问题(Redis缓存)
  4. 数据一致性(回滚机制)
  5. 死锁问题(锁超时)

原文地址:https://blog.csdn.net/web2u/article/details/145223958

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!