最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

基于SpringBoot+Redis+Lua 實(shí)現(xiàn)高并發(fā)秒殺系統(tǒng)

 更新時(shí)間:2026年01月25日 10:38:14   作者:三水不滴  
這篇文章主要介紹了基于SpringBoot+Redis+Lua 實(shí)現(xiàn)高并發(fā)秒殺系統(tǒng),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

基于 SpringBoot+Redis+Lua 構(gòu)建高并發(fā)秒殺系統(tǒng)的設(shè)計(jì)思路,本文將聚焦核心代碼實(shí)現(xiàn),從環(huán)境配置、庫存預(yù)熱、Lua 腳本、秒殺接口到異步下單、限流防護(hù),完整呈現(xiàn)可直接落地的代碼方案,幫你快速搭建穩(wěn)定高效的秒殺系統(tǒng)。

一、環(huán)境準(zhǔn)備與依賴配置

1. 項(xiàng)目依賴(pom.xml)

首先在 SpringBoot 項(xiàng)目中引入核心依賴,包括 Redis、消息隊(duì)列(以 RabbitMQ 為例)、SpringBoot 核心組件等:

<!-- SpringBoot核心依賴 -->
<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>
<!-- 消息隊(duì)列(RabbitMQ) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<!-- Lua腳本支持 -->
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <version>2.7.0</version>
</dependency>
<!-- 工具類 -->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.11</version>
</dependency>
<!--  lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

2. 核心配置(application.yml)

配置 Redis、RabbitMQ 連接信息,以及秒殺系統(tǒng)核心參數(shù):

spring:
  # Redis配置
  redis:
    host: 127.0.0.1
    port: 6379
    password: 123456
    database: 0
    lettuce:
      pool:
        max-active: 16  # 最大連接數(shù)
        max-idle: 8     # 最大空閑連接
        min-idle: 4     # 最小空閑連接
  # RabbitMQ配置
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest
    virtual-host: /
    listener:
      simple:
        acknowledge-mode: manual  # 手動(dòng)ACK確認(rèn)
        concurrency: 5            # 消費(fèi)者并發(fā)數(shù)

# 秒殺系統(tǒng)配置
seckill:
  redis:
    stock-key-prefix: "seckill:goods:"       # 商品庫存Key前綴
    user-key-prefix: "seckill:goods:users:"  # 已秒殺用戶Key前綴
  rabbitmq:
    queue-name: "seckill_order_queue"        # 訂單消息隊(duì)列名
    exchange-name: "seckill_order_exchange"  # 訂單交換機(jī)名
    routing-key: "seckill.order"             # 路由鍵
  rate-limit:
    qps: 1000  # 全局限流QPS

二、核心工具類與常量定義

1. 秒殺常量類(SeckillConstant)

統(tǒng)一管理 Redis 鍵前綴、消息隊(duì)列參數(shù)等常量,避免硬編碼:

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * 秒殺系統(tǒng)常量類
 */
@Getter
@AllArgsConstructor
public class SeckillConstant {
    // Redis鍵前綴
    public static final String STOCK_KEY_PREFIX = "seckill:goods:";
    public static final String USER_KEY_PREFIX = "seckill:goods:users:";
    public static final String REQUEST_ID_KEY_PREFIX = "seckill:request:";
    
    // 秒殺結(jié)果狀態(tài)碼
    public static final int SECKILL_SUCCESS = 1;  // 秒殺成功
    public static final int SECKILL_FAIL_STOCK = 0; // 庫存不足
    public static final int SECKILL_FAIL_REPEAT = 2; // 重復(fù)秒殺
    
    // 消息隊(duì)列參數(shù)
    public static final String ORDER_QUEUE_NAME = "seckill_order_queue";
    public static final String ORDER_EXCHANGE_NAME = "seckill_order_exchange";
    public static final String ORDER_ROUTING_KEY = "seckill.order";
}

2. Redis 工具類(RedisTemplateConfig)

配置 RedisTemplate,支持 String、Hash 等數(shù)據(jù)結(jié)構(gòu)操作,以及 Lua 腳本執(zhí)行:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisTemplateConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        
        // String序列化器
        StringRedisSerializer stringSerializer = new StringRedisSerializer();
        // JSON序列化器
        GenericJackson2JsonRedisSerializer jsonSerializer = new GenericJackson2JsonRedisSerializer();
        
        // Key使用String序列化
        template.setKeySerializer(stringSerializer);
        template.setHashKeySerializer(stringSerializer);
        
        // Value使用JSON序列化
        template.setValueSerializer(jsonSerializer);
        template.setHashValueSerializer(jsonSerializer);
        
        template.afterPropertiesSet();
        return template;
    }
}

三、庫存預(yù)熱實(shí)現(xiàn)(活動(dòng)前數(shù)據(jù)加載)

秒殺活動(dòng)開始前,將商品庫存、活動(dòng)狀態(tài)從數(shù)據(jù)庫加載到 Redis,避免活動(dòng)中頻繁訪問數(shù)據(jù)庫:

1. 商品實(shí)體類(GoodsDTO)

import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;

/**
 * 秒殺商品DTO
 */
@Data
public class GoodsDTO {
    private Long goodsId;        // 商品ID
    private String goodsName;    // 商品名稱
    private BigDecimal seckillPrice; // 秒殺價(jià)格
    private Integer stock;       // 秒殺庫存
    private Date startTime;      // 活動(dòng)開始時(shí)間
    private Date endTime;        // 活動(dòng)結(jié)束時(shí)間
    private Integer status;      // 活動(dòng)狀態(tài):0-未開始,1-進(jìn)行中,2-已結(jié)束
}

2. 庫存預(yù)熱服務(wù)(StockWarmUpService)

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;

/**
 * 庫存預(yù)熱服務(wù)
 */
@Service
public class StockWarmUpService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 預(yù)熱單個(gè)商品庫存到Redis
     */
    public void warmUpGoodsStock(GoodsDTO goodsDTO) {
        if (goodsDTO == null || goodsDTO.getStock() <= 0) {
            throw new IllegalArgumentException("商品信息或庫存無效");
        }
        
        // 1. 存儲(chǔ)商品庫存和狀態(tài)(Hash結(jié)構(gòu))
        String stockKey = SeckillConstant.STOCK_KEY_PREFIX + goodsDTO.getGoodsId();
        Map<String, Object> goodsMap = new HashMap<>();
        goodsMap.put("stock", goodsDTO.getStock());
        goodsMap.put("status", goodsDTO.getStatus());
        goodsMap.put("startTime", goodsDTO.getStartTime().getTime());
        goodsMap.put("endTime", goodsDTO.getEndTime().getTime());
        redisTemplate.opsForHash().putAll(stockKey, goodsMap);
        
        // 2. 初始化已秒殺用戶集合(Set結(jié)構(gòu),用于去重)
        String userKey = SeckillConstant.USER_KEY_PREFIX + goodsDTO.getGoodsId();
        redisTemplate.opsForSet().add(userKey, new Object[]{}); // 初始化空集合
    }

    /**
     * 批量預(yù)熱商品庫存(適用于多商品秒殺活動(dòng))
     */
    public void batchWarmUpGoodsStock(java.util.List<GoodsDTO> goodsDTOList) {
        for (GoodsDTO goodsDTO : goodsDTOList) {
            warmUpGoodsStock(goodsDTO);
        }
    }
}

3. 定時(shí)預(yù)熱觸發(fā)(可選)

通過定時(shí)任務(wù)在活動(dòng)開始前 10 分鐘自動(dòng)預(yù)熱庫存:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.List;

@Component
public class SeckillScheduledTask {

    @Autowired
    private StockWarmUpService stockWarmUpService;
    @Autowired
    private GoodsMapper goodsMapper; // 自定義Mapper,查詢待秒殺商品

    /**
     * 每天00:00預(yù)熱當(dāng)天秒殺商品庫存(可根據(jù)實(shí)際活動(dòng)時(shí)間調(diào)整)
     */
    @Scheduled(cron = "0 0 0 * * ?")
    public void scheduledWarmUpStock() {
        List<GoodsDTO> seckillGoods = goodsMapper.selectTodaySeckillGoods();
        stockWarmUpService.batchWarmUpGoodsStock(seckillGoods);
    }
}

四、核心:Lua 腳本實(shí)現(xiàn)原子扣減庫存

通過 Lua 腳本將 “庫存檢查、重復(fù)秒殺攔截、庫存扣減” 封裝為原子操作,從根本上解決并發(fā)沖突:

1. Lua 腳本文件(seckill_stock.lua)

resources/lua目錄下創(chuàng)建 Lua 腳本:

-- 傳入?yún)?shù):KEYS[1] = 商品ID,ARGV[1] = 用戶ID
local goodsId = KEYS[1]
local userId = ARGV[1]

-- 定義Redis鍵
local stockKey = "seckill:goods:" .. goodsId
local userKey = "seckill:goods:users:" .. goodsId

-- 1. 檢查商品活動(dòng)狀態(tài)和庫存
local goodsStatus = redis.call("HGET", stockKey, "status")
if goodsStatus ~= "1" then
    return 3  -- 活動(dòng)未開始或已結(jié)束
end

local stock = redis.call("HGET", stockKey, "stock")
if not stock or tonumber(stock) <= 0 then
    return 0  -- 庫存不足
end

-- 2. 檢查用戶是否已秒殺(避免重復(fù)下單)
if redis.call("SISMEMBER", userKey, userId) == 1 then
    return 2  -- 重復(fù)秒殺
end

-- 3. 原子扣減庫存并記錄用戶
redis.call("HINCRBY", stockKey, "stock", -1)
redis.call("SADD", userKey, userId)

return 1  -- 秒殺成功

2. Lua 腳本加載與執(zhí)行服務(wù)(LuaScriptService)

import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.StaticScriptSource;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.Collections;
import java.util.List;

/**
 * Lua腳本執(zhí)行服務(wù)
 */
@Service
public class LuaScriptService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    private DefaultRedisScript<Integer> seckillStockScript;

    /**
     * 初始化加載Lua腳本
     */
    @PostConstruct
    public void initLuaScript() {
        seckillStockScript = new DefaultRedisScript<>();
        // 加載腳本文件
        seckillStockScript.setScriptSource(new StaticScriptSource(
                new ClassPathResource("lua/seckill_stock.lua").getContentAsString()
        ));
        // 設(shè)置返回值類型
        seckillStockScript.setResultType(Integer.class);
    }

    /**
     * 執(zhí)行秒殺庫存扣減腳本
     */
    public Integer executeSeckillScript(Long goodsId, Long userId) {
        List<String> keys = Collections.singletonList(goodsId.toString());
        // 執(zhí)行腳本:keys=商品ID,args=用戶ID
        return redisTemplate.execute(
                seckillStockScript,
                keys,
                userId.toString()
        );
    }
}

五、秒殺接口實(shí)現(xiàn)(含限流、防重復(fù)提交)

1. 限流注解與 AOP 實(shí)現(xiàn)(RateLimitAspect)

基于 Redis 實(shí)現(xiàn)分布式限流,攔截超 QPS 請求:

import cn.hutool.core.util.StrUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.Collections;

/**
 * 限流AOP切面
 */
@Aspect
@Component
public class RateLimitAspect {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Value("${seckill.rate-limit.qps}")
    private int qps;

    private DefaultRedisScript<Long> rateLimitScript;

    @PostConstruct
    public void initRateLimitScript() {
        // 令牌桶限流Lua腳本
        String script = "local key = KEYS[1]\n" +
                        "local now = tonumber(ARGV[1])\n" +
                        "local qps = tonumber(ARGV[2])\n" +
                        "local window = 1\n" +  // 窗口時(shí)間1秒
                        "local maxToken = qps\n" +
                        "local lastTime = redis.call('HGET', key, 'lastTime')\n" +
                        "local tokenCount = redis.call('HGET', key, 'tokenCount')\n" +
                        "if not lastTime or not tokenCount then\n" +
                        "    redis.call('HSET', key, 'lastTime', now)\n" +
                        "    redis.call('HSET', key, 'tokenCount', maxToken - 1)\n" +
                        "    redis.call('EXPIRE', key, window)\n" +
                        "    return 1\n" +
                        "end\n" +
                        "local interval = now - tonumber(lastTime)\n" +
                        "local addToken = math.floor(interval / 1000 * qps)\n" +
                        "tokenCount = math.min(addToken + tonumber(tokenCount), maxToken)\n" +
                        "if tokenCount <= 0 then\n" +
                        "    return 0\n" +
                        "end\n" +
                        "redis.call('HSET', key, 'lastTime', now)\n" +
                        "redis.call('HSET', key, 'tokenCount', tokenCount - 1)\n" +
                        "return 1";
        
        rateLimitScript = new DefaultRedisScript<>(script, Long.class);
    }

    // 定義限流注解切入點(diǎn)
    @Pointcut("@annotation(com.seckill.annotation.RateLimit)")
    public void rateLimitPointcut() {}

    @Around("rateLimitPointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        String key = "seckill:rate:limit:global"; // 全局限流Key(可擴(kuò)展為IP/用戶維度)
        Long now = System.currentTimeMillis();
        Long result = redisTemplate.execute(
                rateLimitScript,
                Collections.singletonList(key),
                now.toString(),
                String.valueOf(qps)
        );
        
        if (result == 1) {
            return joinPoint.proceed(); // 獲得令牌,執(zhí)行方法
        } else {
            throw new RuntimeException("請求過于頻繁,請稍后再試");
        }
    }
}

2. 防重復(fù)提交注解(RepeatSubmit)

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit {
    // 防重復(fù)提交過期時(shí)間(默認(rèn)5秒)
    int expireSeconds() default 5;
}

3. 秒殺接口(SeckillController)

import cn.hutool.core.util.IdUtil;
import com.seckill.annotation.RateLimit;
import com.seckill.annotation.RepeatSubmit;
import com.seckill.dto.SeckillRequestDTO;
import com.seckill.dto.SeckillResponseDTO;
import com.seckill.service.LuaScriptService;
import com.seckill.service.OrderMessageService;
import com.seckill.service.StockWarmUpService;
import com.seckill.util.SeckillConstant;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import java.util.Objects;

/**
 * 秒殺接口
 */
@RestController
@RequestMapping("/seckill")
public class SeckillController {

    @Autowired
    private LuaScriptService luaScriptService;
    @Autowired
    private OrderMessageService orderMessageService;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 秒殺核心接口(含限流、防重復(fù)提交)
     */
    @PostMapping("/doSeckill")
    @RateLimit  // 限流注解
    @RepeatSubmit(expireSeconds = 5)  // 防重復(fù)提交(5秒內(nèi)同一請求ID不可重復(fù)提交)
    public SeckillResponseDTO doSeckill(@Valid @RequestBody SeckillRequestDTO requestDTO) {
        Long goodsId = requestDTO.getGoodsId();
        Long userId = requestDTO.getUserId();
        String requestId = requestDTO.getRequestId(); // 前端生成的唯一請求ID

        // 1. 防重復(fù)提交校驗(yàn)(基于Redis緩存請求ID)
        String requestKey = SeckillConstant.REQUEST_ID_KEY_PREFIX + requestId;
        Boolean hasRequest = redisTemplate.hasKey(requestKey);
        if (Boolean.TRUE.equals(hasRequest)) {
            return SeckillResponseDTO.fail("請勿重復(fù)提交請求");
        }
        redisTemplate.opsForValue().set(requestKey, "1", 5); // 緩存5秒

        try {
            // 2. 執(zhí)行Lua腳本扣減庫存
            Integer seckillResult = luaScriptService.executeSeckillScript(goodsId, userId);

            // 3. 處理秒殺結(jié)果
            if (SeckillConstant.SECKILL_SUCCESS == seckillResult) {
                // 秒殺成功,發(fā)送訂單消息到消息隊(duì)列
                orderMessageService.sendOrderMessage(goodsId, userId);
                return SeckillResponseDTO.success("秒殺成功,正在創(chuàng)建訂單...");
            } else if (SeckillConstant.SECKILL_FAIL_STOCK == seckillResult) {
                return SeckillResponseDTO.fail("庫存不足,秒殺失敗");
            } else if (SeckillConstant.SECKILL_FAIL_REPEAT == seckillResult) {
                return SeckillResponseDTO.fail("您已參與過該商品秒殺,請勿重復(fù)搶購");
            } else {
                return SeckillResponseDTO.fail("活動(dòng)未開始或已結(jié)束");
            }
        } finally {
            // 4. 移除防重復(fù)提交緩存(可選,根據(jù)業(yè)務(wù)需求調(diào)整)
            redisTemplate.delete(requestKey);
        }
    }
}

4. 秒殺請求 / 響應(yīng) DTO

import lombok.Data;
import javax.validation.constraints.NotNull;

/**
 * 秒殺請求DTO
 */
@Data
public class SeckillRequestDTO {
    @NotNull(message = "商品ID不能為空")
    private Long goodsId;

    @NotNull(message = "用戶ID不能為空")
    private Long userId;

    @NotNull(message = "請求ID不能為空")
    private String requestId; // 前端生成的唯一請求ID,用于防重復(fù)提交
}

/**
 * 秒殺響應(yīng)DTO
 */
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class SeckillResponseDTO {
    private int code; // 響應(yīng)碼:0-失敗,1-成功
    private String message; // 響應(yīng)消息

    public static SeckillResponseDTO success(String message) {
        return new SeckillResponseDTO(1, message);
    }

    public static SeckillResponseDTO fail(String message) {
        return new SeckillResponseDTO(0, message);
    }
}

六、消息隊(duì)列異步下單實(shí)現(xiàn)

1. 訂單消息實(shí)體(OrderMessage)

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;

/**
 * 訂單消息實(shí)體
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class OrderMessage {
    private Long orderId;        // 訂單ID(可在消息發(fā)送時(shí)生成)
    private Long goodsId;        // 商品ID
    private Long userId;         // 用戶ID
    private BigDecimal seckillPrice; // 秒殺價(jià)格
    private Long createTime;     // 創(chuàng)建時(shí)間戳
}

2. 消息隊(duì)列配置(RabbitMQConfig)

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * RabbitMQ配置
 */
@Configuration
public class RabbitMQConfig {

    /**
     * 聲明訂單隊(duì)列
     */
    @Bean
    public Queue orderQueue() {
        // 隊(duì)列持久化
        return new Queue(SeckillConstant.ORDER_QUEUE_NAME, true);
    }

    /**
     * 聲明訂單交換機(jī)
     */
    @Bean
    public DirectExchange orderExchange() {
        return new DirectExchange(SeckillConstant.ORDER_EXCHANGE_NAME, true, false);
    }

    /**
     * 綁定隊(duì)列和交換機(jī)
     */
    @Bean
    public Binding orderBinding() {
        return BindingBuilder.bind(orderQueue())
                .to(orderExchange())
                .with(SeckillConstant.ORDER_ROUTING_KEY);
    }
}

3. 訂單消息發(fā)送服務(wù)(OrderMessageService)

import cn.hutool.core.util.IdUtil;
import com.seckill.dto.OrderMessage;
import com.seckill.util.SeckillConstant;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;

/**
 * 訂單消息發(fā)送服務(wù)
 */
@Service
public class OrderMessageService {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 發(fā)送秒殺訂單消息
     */
    public void sendOrderMessage(Long goodsId, Long userId) {
        // 生成訂單ID(雪花算法或UUID)
        Long orderId = IdUtil.getSnowflake().nextId();
        
        // 構(gòu)建訂單消息
        OrderMessage orderMessage = new OrderMessage();
        orderMessage.setOrderId(orderId);
        orderMessage.setGoodsId(goodsId);
        orderMessage.setUserId(userId);
        orderMessage.setSeckillPrice(new BigDecimal("99.00")); // 示例秒殺價(jià)格
        orderMessage.setCreateTime(System.currentTimeMillis());

        // 發(fā)送消息到RabbitMQ
        rabbitTemplate.convertAndSend(
                SeckillConstant.ORDER_EXCHANGE_NAME,
                SeckillConstant.ORDER_ROUTING_KEY,
                orderMessage
        );
    }
}

4. 訂單消息消費(fèi)服務(wù)(OrderConsumerService)

import com.rabbitmq.client.Channel;
import com.seckill.dto.OrderMessage;
import com.seckill.mapper.OrderMapper;
import com.seckill.pojo.Order;
import com.seckill.util.SeckillConstant;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.io.IOException;

/**
 * 訂單消息消費(fèi)服務(wù)(異步創(chuàng)建訂單)
 */
@Service
public class OrderConsumerService {

    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @RabbitListener(queues = SeckillConstant.ORDER_QUEUE_NAME)
    public void consumeOrderMessage(OrderMessage orderMessage, Channel channel, Message message) throws IOException {
        try {
            // 1. 校驗(yàn)消息合法性
            if (orderMessage == null || orderMessage.getGoodsId() == null || orderMessage.getUserId() == null) {
                channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
                return;
            }

            // 2. 異步創(chuàng)建訂單
            Order order = new Order();
            order.setOrderId(orderMessage.getOrderId());
            order.setGoodsId(orderMessage.getGoodsId());
            order.setUserId(orderMessage.getUserId());
            order.setSeckillPrice(orderMessage.getSeckillPrice());
            order.setCreateTime(orderMessage.getCreateTime());
            order.setStatus(0); // 0-待支付
            orderMapper.insert(order);

            // 3. 手動(dòng)ACK確認(rèn)消息
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (Exception e) {
            // 4. 處理異常:消息重投或記錄日志(根據(jù)業(yè)務(wù)需求調(diào)整)
            channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
            // 回滾Redis庫存(補(bǔ)償機(jī)制)
            rollbackStock(orderMessage.getGoodsId());
        }
    }

    /**
     * 訂單創(chuàng)建失敗,回滾Redis庫存
     */
    private void rollbackStock(Long goodsId) {
        String stockKey = SeckillConstant.STOCK_KEY_PREFIX + goodsId;
        redisTemplate.opsForHash().increment(stockKey, "stock", 1);
    }
}

七、防重復(fù)提交 AOP 實(shí)現(xiàn)(RepeatSubmitAspect)

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import com.seckill.annotation.RepeatSubmit;
import com.seckill.dto.SeckillRequestDTO;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * 防重復(fù)提交AOP切面
 */
@Aspect
@Component
public class RepeatSubmitAspect {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Pointcut("@annotation(repeatSubmit)")
    public void repeatSubmitPointcut(RepeatSubmit repeatSubmit) {}

    @Around("repeatSubmitPointcut(repeatSubmit)")
    public Object around(ProceedingJoinPoint joinPoint, RepeatSubmit repeatSubmit) throws Throwable {
        // 獲取請求參數(shù)中的SeckillRequestDTO
        Object[] args = joinPoint.getArgs();
        SeckillRequestDTO requestDTO = null;
        for (Object arg : args) {
            if (arg instanceof SeckillRequestDTO) {
                requestDTO = (SeckillRequestDTO) arg;
                break;
            }
        }

        if (requestDTO == null || Objects.isNull(requestDTO.getRequestId())) {
            throw new IllegalArgumentException("請求ID不能為空");
        }

        // 構(gòu)建請求Key
        String requestKey = "seckill:request:" + requestDTO.getRequestId();
        Boolean hasKey = redisTemplate.hasKey(requestKey);
        if (Boolean.TRUE.equals(hasKey)) {
            throw new RuntimeException("請勿重復(fù)提交請求");
        }

        // 緩存請求ID
        redisTemplate.opsForValue().set(requestKey, "1", repeatSubmit.expireSeconds(), TimeUnit.SECONDS);
        try {
            return joinPoint.proceed();
        } finally {
            // 可根據(jù)業(yè)務(wù)需求決定是否移除緩存
            // redisTemplate.delete(requestKey);
        }
    }
}

八、核心代碼總結(jié)與部署建議

1. 代碼核心亮點(diǎn)

  1. 原子性保障:通過 Lua 腳本封裝庫存校驗(yàn)、扣減和用戶去重,避免超賣與重復(fù)秒殺;
  2. 流量分層攔截:結(jié)合 Redis 令牌桶限流、防重復(fù)提交 AOP,從源頭減少無效請求;
  3. 異步解耦:RabbitMQ 異步處理訂單創(chuàng)建,提升接口響應(yīng)速度,降低數(shù)據(jù)庫壓力;
  4. 數(shù)據(jù)一致性:消息消費(fèi)失敗時(shí)回滾 Redis 庫存,確保緩存與數(shù)據(jù)庫數(shù)據(jù)同步;
  5. 可擴(kuò)展性:支持庫存分片、預(yù)售模式等復(fù)雜場景擴(kuò)展,適配不同業(yè)務(wù)需求。

2. 部署與測試建議

  1. Redis 部署:采用主從復(fù)制 + 哨兵模式,確保高可用;超大規(guī)模秒殺建議使用 Redis Cluster 分片存儲(chǔ)庫存;
  2. 消息隊(duì)列:開啟 RabbitMQ 持久化與鏡像隊(duì)列,避免消息丟失;根據(jù)流量調(diào)整消費(fèi)者并發(fā)數(shù);
  3. 壓力測試:使用 JMeter 模擬 10 萬 + 并發(fā)請求,重點(diǎn)測試庫存準(zhǔn)確性、接口響應(yīng)時(shí)間和系統(tǒng)穩(wěn)定性;
  4. 監(jiān)控告警:通過 Prometheus+Grafana 監(jiān)控 Redis 庫存、消息隊(duì)列堆積量、接口 QPS 等核心指標(biāo),設(shè)置閾值告警。

本文完整呈現(xiàn)了 SpringBoot+Redis+Lua 秒殺系統(tǒng)的核心代碼,覆蓋從庫存預(yù)熱到異步下單的全流程,代碼可直接復(fù)制到項(xiàng)目中使用,只需根據(jù)實(shí)際業(yè)務(wù)調(diào)整參數(shù)與擴(kuò)展功能。

到此這篇關(guān)于基于SpringBoot+Redis+Lua 實(shí)現(xiàn)高并發(fā)秒殺系統(tǒng)的文章就介紹到這了,更多相關(guān)SpringBoot Redis Lua高并發(fā)秒殺內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Mybatis in條件傳參的三種實(shí)現(xiàn)方式(直接$,List,[])

    Mybatis in條件傳參的三種實(shí)現(xiàn)方式(直接$,List,[])

    這篇文章主要介紹了Mybatis in條件傳參的三種實(shí)現(xiàn)方式(直接$,List,[]),具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-12-12
  • Java數(shù)據(jù)結(jié)構(gòu)之常見排序算法(下)

    Java數(shù)據(jù)結(jié)構(gòu)之常見排序算法(下)

    這篇文章主要介紹了Java數(shù)據(jù)結(jié)構(gòu)之常見排序算法(下),與之相對有(上),想了解的朋友可以去本網(wǎng)站掃搜,在這兩篇文章里涵蓋關(guān)于八大排序算法的所有內(nèi)容,需要的朋友可以參考下
    2023-01-01
  • Spring?Boot?+?Canal?實(shí)現(xiàn)數(shù)據(jù)庫實(shí)時(shí)監(jiān)控

    Spring?Boot?+?Canal?實(shí)現(xiàn)數(shù)據(jù)庫實(shí)時(shí)監(jiān)控

    這篇文章主要介紹了Spring?Boot?+?Canal實(shí)現(xiàn)數(shù)據(jù)庫實(shí)時(shí)監(jiān)控,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下
    2022-08-08
  • 關(guān)于@Scheduled參數(shù)及cron表達(dá)式解釋

    關(guān)于@Scheduled參數(shù)及cron表達(dá)式解釋

    這篇文章主要介紹了關(guān)于@Scheduled參數(shù)及cron表達(dá)式解釋,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • springboot常用注釋的講解

    springboot常用注釋的講解

    今天小編就為大家分享一篇關(guān)于springboot常用注釋的講解,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧
    2019-04-04
  • Java流程控制語句最全匯總(上篇)

    Java流程控制語句最全匯總(上篇)

    這篇文章主要介紹了Java流程控制語句最全匯總(上篇),本文章內(nèi)容詳細(xì),通過案例可以更好的理解數(shù)組的相關(guān)知識(shí),本模塊分為了三部分,本次為上篇,需要的朋友可以參考下
    2023-01-01
  • Java常用類之日期相關(guān)類使用詳解

    Java常用類之日期相關(guān)類使用詳解

    這篇文章主要為大家介紹了Java中常用類的日期相關(guān)類的用法教程,文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)Java有一定的幫助,需要的可以參考一下
    2022-08-08
  • 詳解Spring MVC的異步模式(高性能的關(guān)鍵)

    詳解Spring MVC的異步模式(高性能的關(guān)鍵)

    本篇文章主要介紹了詳解Spring MVC的異步模式(高性能的關(guān)鍵),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-02-02
  • 參數(shù)校驗(yàn)Spring的@Valid注解用法解析

    參數(shù)校驗(yàn)Spring的@Valid注解用法解析

    這篇文章主要介紹了參數(shù)校驗(yàn)Spring的@Valid注解用法,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • Java多線程 原子操作類詳細(xì)

    Java多線程 原子操作類詳細(xì)

    這篇文章主要介紹了Java多線程中的原子操作類,原子的本意是不能被分割的粒子,而對于一個(gè)操作來說,如果它是不可被中斷的一個(gè)或者一組操作,那么他就是原子操作。顯然,原子操作是安全的,因?yàn)樗粫?huì)被打斷,需要的朋友可以參考下
    2021-10-10

最新評論

潮安县| 屏边| 潼关县| 子洲县| 台州市| 光山县| 浦城县| 永济市| 湟中县| 桃源县| 香港 | 神池县| 崇文区| 石泉县| 朝阳县| 新蔡县| 瑞丽市| 富蕴县| 大石桥市| 微山县| 共和县| 西吉县| 凌源市| 扶余县| 登封市| 安乡县| 鹤山市| 东兴市| 遂川县| 龙山县| 济源市| 肥东县| 静安区| 开鲁县| 库车县| 郁南县| 洪江市| 湘潭市| 樟树市| 北宁市| 库尔勒市|