场景是在进行秒杀活动时处理库存扣减的逻辑。下面我会提供一个简化的处理流程,并解释每一步的操作。
-
批量扣减库存:
- 当用户发起秒杀请求时,系统首先尝试批量扣减库存。
- 这通常涉及到从数据库(如MySQL)中读取当前库存数量,然后减去请求的数量。
- 如果批量扣减成功,则继续处理订单等其他逻辑。
-
批量扣减失败:
- 如果由于某种原因(如库存不足)批量扣减失败,则改为单个扣减库存。
- 这意味着系统尝试逐一扣减每个商品的库存,直到库存耗尽或满足用户请求的数量。
-
单个扣减库存:
- 在单个扣减的过程中,系统仍然需要确保并发安全性,避免超卖。
- 这通常通过数据库的事务、锁或其他并发控制机制来实现。
-
扣减完库存后写入Redis:
- 一旦库存扣减成功(无论是批量还是单个),系统需要将库存数量更新到Redis中。
- Redis作为一个高速缓存,可以提供更快的库存查询速度,减轻数据库压力。
- 在Redis中设置库存为0表示该商品已售罄。
-
写入秒杀失败表到MongoDB:
- 对于那些因为库存不足而未能成功秒杀的用户,系统需要将他们的请求信息写入到MongoDB的秒杀失败表中。
- 这有助于后续的分析和补偿措施,例如通知用户下次秒杀活动的时间或提供其他优惠。
- MongoDB作为一个面向文档的数据库,适合存储这种结构化的日志或事件数据。
注意事项:
- 并发控制: 在高并发的秒杀场景下,确保库存扣减的原子性和准确性是关键。需要使用数据库的事务、锁或其他机制来确保这一点。
- 性能优化: Redis和MongoDB的引入可以提高系统的响应速度和可扩展性。但也需要确保这些组件的性能和稳定性,避免成为新的瓶颈。
- 异常处理: 在整个流程中,需要妥善处理各种异常情况,如网络中断、数据库故障等,确保系统的健壮性。
- 日志记录: 对于关键操作,如库存扣减、写入Redis和MongoDB等,需要记录详细的日志,以便后续的问题追踪和分析。
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import org.bson.Document;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource;
import java.util.concurrent.TimeUnit; @Service
public class SeckillService { @Autowired private ProductRepository productRepository; // 假设的库存数据访问层 @Resource private RedisTemplate<String, Long> redisTemplate; // Redis操作模板 @Autowired private MongoDatabase mongoDatabase; // MongoDB数据库实例 // 批量扣减库存 @Transactional public boolean tryBatchDecreaseStock(String productId, int quantity) { // 读取数据库中的库存数量 long stock = productRepository.getStock(productId); if (stock >= quantity) { // 批量扣减库存 long newStock = stock - quantity; if (productRepository.updateStock(productId, newStock)) { // 批量扣减成功,更新Redis中的库存数量 updateRedisStock(productId, newStock); return true; } } return false; } // 单个扣减库存 @Transactional public boolean trySingleDecreaseStock(String productId, int quantity) { for (int i = 0; i < quantity; i++) { // 读取数据库中的库存数量 long stock = productRepository.getStock(productId); if (stock > 0) { // 单个扣减库存 long newStock = stock - 1; if (productRepository.updateStock(productId, newStock)) { // 单个扣减成功,更新Redis中的库存数量 updateRedisStock(productId, newStock); } else { // 更新失败,可能由于并发导致的数据不一致,返回失败 return false; } } else { // 库存不足,返回失败 break; } } return true; } // 更新Redis中的库存数量 private void updateRedisStock(String productId, long stock) { ValueOperations<String, Long> ops = redisTemplate.opsForValue(); ops.set(productId, stock, 1, TimeUnit.MINUTES); // 设置库存数量,并设置过期时间 } // 写入秒杀失败记录到MongoDB public void recordSeckillFailure(String userId, String productId) { MongoCollection<Document> collection = mongoDatabase.getCollection("seckill_failures"); Document doc = new Document("user_id", userId) .append("product_id", productId) .append("failure_time", new java.util.Date()); collection.insertOne(doc); } // 实际业务逻辑中调用这些方法 public boolean processSeckillOrder(String userId, String productId, int quantity) { if (tryBatchDecreaseStock(productId, quantity)) { // 批量扣减成功,处理订单等其他逻辑... return true; } else { // 批量扣减失败,尝试单个扣减 if (trySingleDecreaseStock(productId, quantity)) { // 单个扣减成功,处理订单等其他逻辑... return true; } else { // 扣减库存失败,记录秒杀失败 recordSeckillFailure(userId, productId); return false; } } }
}