秒杀活动库存扣减逻辑详解:从批量到单个,再到缓存与日志记录

时间:2024-04-12 21:01:35

场景是在进行秒杀活动时处理库存扣减的逻辑。下面我会提供一个简化的处理流程,并解释每一步的操作。

  1. 批量扣减库存:

    • 当用户发起秒杀请求时,系统首先尝试批量扣减库存。
    • 这通常涉及到从数据库(如MySQL)中读取当前库存数量,然后减去请求的数量。
    • 如果批量扣减成功,则继续处理订单等其他逻辑。
  2. 批量扣减失败:

    • 如果由于某种原因(如库存不足)批量扣减失败,则改为单个扣减库存。
    • 这意味着系统尝试逐一扣减每个商品的库存,直到库存耗尽或满足用户请求的数量。
  3. 单个扣减库存:

    • 在单个扣减的过程中,系统仍然需要确保并发安全性,避免超卖。
    • 这通常通过数据库的事务、锁或其他并发控制机制来实现。
  4. 扣减完库存后写入Redis:

    • 一旦库存扣减成功(无论是批量还是单个),系统需要将库存数量更新到Redis中。
    • Redis作为一个高速缓存,可以提供更快的库存查询速度,减轻数据库压力。
    • 在Redis中设置库存为0表示该商品已售罄。
  5. 写入秒杀失败表到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;  
            }  
        }  
    }  
}