🎓博主介绍:Java、Python、js全栈开发 “多面手”,精通多种编程语言和技术,痴迷于人工智能领域。秉持着对技术的热爱与执着,持续探索创新,愿在此分享交流和学习,与大家共进步。
📖DeepSeek-行业融合之万象视界(附实战案例详解100+)
📖全栈开发环境搭建运行攻略:多语言一站式指南(环境搭建+运行+调试+发布+保姆级详解)
👉感兴趣的可以先收藏起来,希望帮助更多的人
SpringBoot电商项目实战:高并发场景下的库存扣减与消息队列整合
一、背景介绍
在电商系统中,库存管理是一个核心功能,尤其是在高并发场景下,如限时抢购、节日大促等活动期间,大量用户同时对商品进行下单操作,会对库存系统造成巨大的压力。如果库存扣减处理不当,可能会导致超卖现象,即卖出的商品数量超过了实际库存数量,这会给商家带来严重的经济损失和信誉问题。为了解决高并发场景下的库存扣减问题,我们可以引入消息队列来实现异步处理,提高系统的吞吐量和稳定性。
二、项目环境搭建
2.1 技术选型
- Spring Boot:简化Spring应用的开发过程,提供快速搭建项目的能力。
- Spring Data JPA:用于与数据库进行交互,简化数据库操作。
- RabbitMQ:作为消息队列,实现异步消息处理。
- MySQL:作为项目的数据库,存储商品信息和库存信息。
2.2 项目创建
使用Spring Initializr(https://start.spring.io/)创建一个新的Spring Boot项目,添加以下依赖:
<dependencies>
<!-- Spring Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Data JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- MySQL Driver -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- RabbitMQ -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
</dependencies>
2.3 配置文件
在application.properties中配置数据库和RabbitMQ的连接信息:
# Database configuration
spring.datasource.url=jdbc:mysql://localhost:3306/ecommerce?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# JPA configuration
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
# RabbitMQ configuration
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
三、数据库设计
3.1 商品表(product)
| 字段名 | 类型 | 描述 |
|---|---|---|
| id | bigint | 商品ID,主键 |
| name | varchar(255) | 商品名称 |
| price | decimal(10, 2) | 商品价格 |
| stock | int | 商品库存 |
3.2 订单表(order)
| 字段名 | 类型 | 描述 |
|---|---|---|
| id | bigint | 订单ID,主键 |
| product_id | bigint | 商品ID,外键关联product表的id |
| quantity | int | 订单数量 |
| status | varchar(20) | 订单状态,如待支付、已支付、已取消等 |
3.3 创建实体类
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
private int stock;
// Getters and Setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public int getStock() {
return stock;
}
public void setStock(int stock) {
this.stock = stock;
}
}
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long productId;
private int quantity;
private String status;
// Getters and Setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getProductId() {
return productId;
}
public void setProductId(Long productId) {
this.productId = productId;
}
public int getQuantity() {
return quantity;
}
public void setQuantity(int quantity) {
this.quantity = quantity;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
}
四、库存扣减的同步处理
4.1 编写Service层
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
@Service
public class ProductService {
@Autowired
private ProductRepository productRepository;
@Transactional
public boolean reduceStock(Long productId, int quantity) {
Optional<Product> optionalProduct = productRepository.findById(productId);
if (optionalProduct.isPresent()) {
Product product = optionalProduct.get();
if (product.getStock() >= quantity) {
product.setStock(product.getStock() - quantity);
productRepository.save(product);
return true;
}
}
return false;
}
}
4.2 编写Controller层
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ProductController {
@Autowired
private ProductService productService;
@PostMapping("/products/{productId}/reduce-stock")
public String reduceStock(@PathVariable Long productId, @RequestParam int quantity) {
boolean result = productService.reduceStock(productId, quantity);
if (result) {
return "库存扣减成功";
} else {
return "库存不足,扣减失败";
}
}
}
4.3 同步处理的问题
在高并发场景下,同步处理库存扣减会导致系统吞吐量下降,因为每个请求都需要等待数据库操作完成才能返回结果。而且,由于数据库的锁机制,多个请求同时对同一商品的库存进行扣减时,会出现锁竞争问题,进一步降低系统性能。
五、引入消息队列进行异步处理
5.1 配置RabbitMQ
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitMQConfig {
public static final String STOCK_QUEUE = "stock_queue";
@Bean
public Queue stockQueue() {
return new Queue(STOCK_QUEUE, true);
}
}
5.2 发送消息到队列
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class MessageSenderService {
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendStockMessage(Long productId, int quantity) {
String message = productId + "," + quantity;
rabbitTemplate.convertAndSend(RabbitMQConfig.STOCK_QUEUE, message);
}
}
5.3 修改Controller层
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ProductController {
@Autowired
private MessageSenderService messageSenderService;
@PostMapping("/products/{productId}/reduce-stock")
public String reduceStock(@PathVariable Long productId, @RequestParam int quantity) {
messageSenderService.sendStockMessage(productId, quantity);
return "库存扣减请求已发送,请稍后查看结果";
}
}
5.4 消费消息并扣减库存
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class MessageReceiverService {
@Autowired
private ProductService productService;
@RabbitListener(queues = RabbitMQConfig.STOCK_QUEUE)
public void receiveStockMessage(String message) {
String[] parts = message.split(",");
Long productId = Long.parseLong(parts[0]);
int quantity = Integer.parseInt(parts[1]);
productService.reduceStock(productId, quantity);
}
}
六、解决超卖问题
6.1 数据库乐观锁
在商品表中添加一个version字段,用于实现乐观锁。
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Version;
@Entity
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
private int stock;
@Version
private int version;
// Getters and Setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public int getStock() {
return stock;
}
public void setStock(int stock) {
this.stock = stock;
}
public int getVersion() {
return version;
}
public void setVersion(int version) {
this.version = version;
}
}
修改ProductService的reduceStock方法:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
@Service
public class ProductService {
@Autowired
private ProductRepository productRepository;
@Transactional
public boolean reduceStock(Long productId, int quantity) {
Optional<Product> optionalProduct = productRepository.findById(productId);
if (optionalProduct.isPresent()) {
Product product = optionalProduct.get();
if (product.getStock() >= quantity) {
int rows = productRepository.reduceStock(productId, quantity, product.getVersion());
return rows > 0;
}
}
return false;
}
}
在ProductRepository中添加自定义方法:
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.transaction.annotation.Transactional;
public interface ProductRepository extends JpaRepository<Product, Long> {
@Modifying
@Transactional
@Query("UPDATE Product p SET p.stock = p.stock - ?2, p.version = p.version + 1 WHERE p.id = ?1 AND p.version = ?3 AND p.stock >= ?2")
int reduceStock(Long productId, int quantity, int version);
}
七、总结
通过引入消息队列和数据库乐观锁,我们可以有效地解决高并发场景下的库存扣减问题,提高系统的吞吐量和稳定性,避免超卖现象的发生。在实际项目中,还可以根据业务需求对系统进行进一步的优化和扩展,如使用分布式缓存、限流、熔断等技术。

4929

被折叠的 条评论
为什么被折叠?



