提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

1.引入库

maven引入:

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.20</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

2. 写lua脚本

写lua脚本,保存为.lua格式放到resource目录:

-- 参数1:剩余库存数量key;
-- 参数2:秒杀成功用户key;
-- 参数3:用户ID,userId;
local userId = ARGV[1]
local numKey = KEYS[1]
local userList = KEYS[2]
-- 获取库存
local num = redis.call('GET', numKey)
-- 库存不足:0
if(tonumber(num) <= 0 ) then return "0"
end
-- 重复秒杀
local alreadySec = redis.call("SISMEMBER", userList, userId)
if(alreadySec==1) then return "1"
end
-- 库存减1,秒杀限购1件
redis.call("DECR",numKey)
-- 加入成功集合
redis.call("SADD",userList,userId)
-- 秒杀成功
return "2"

3. 写业务代码

将lua文件加载到bean。


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;

/**
 * @description: 配置类
 * @author: Grace.Pan
 * @create: 2022-04-22 14:23
 */
@Configuration
public class RedisConfig {

    @Bean
    public DefaultRedisScript<String> redisScript() {
        DefaultRedisScript<String> redisScript = new DefaultRedisScript<>();
        redisScript.setResultType(String.class);
        redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/secKill.lua")));
        return redisScript;
    }

}

写业务代码

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.Objects;
import java.util.Set;

/**
 * @description:
 * @author: Grace
 * @create: 2022-05-16 10:47
 */
@Slf4j
@Service
public class SecKillService {

    /**
     * 库存key
     */
    public static final String REST_NUM_KEY = "secKillRestNum_itemId_1";

    /**
     * 秒杀成功Set
     */
    public static final String USER_LIST_KEY = "secKillSuccessUserListKey";

    @Autowired
    public StringRedisTemplate stringRedisTemplate;
    @Autowired
    public DefaultRedisScript<String> redisScript;

    /**
     * 初始化库存
     */
    public void init() {
        stringRedisTemplate.delete(USER_LIST_KEY);
        stringRedisTemplate.opsForValue().set(REST_NUM_KEY, "10");
    }

    /**
     * 打印结果
     */
    public void print() {
        Set<String> members = stringRedisTemplate.opsForSet().members(USER_LIST_KEY);
        log.info("秒杀成功用户ID结婚=" + members);
        String num = stringRedisTemplate.opsForValue().get(REST_NUM_KEY);
        log.info("剩余库存数=" + num);
    }

    /**
     * 秒杀
     *
     * @param userId
     * @return
     */
    public String secKillByRedis(String userId) {
        String result = stringRedisTemplate.execute(redisScript, Arrays.asList(REST_NUM_KEY, USER_LIST_KEY),
                userId);
        log.info("userId={}, result={}", userId, result);
        if (Objects.equals(result, "0")) {
            return "商品已抢完";
        } else if (Objects.equals(result, "1")) {
            return "一个人最多1件,请不要重复抢购";
        } else if (Objects.equals(result, "2")) {
            return "抢购成功";
        }
        return "抢购失败";
    }

}

写测试类

package com.example.distribution.api;

import com.example.distribution.service.SecKillService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @description: 秒杀
 * @author: Grace.Pan
 * @create: 2022-05-06 14:34
 */
@Slf4j
@RestController
public class SecKillController {

    @Autowired
    public SecKillService secKillService;

    @GetMapping("/item/seckill")
    public String secKill() throws InterruptedException {
        secKillByRedisTest();
        return "success";
    }

    public void secKillByRedisTest() throws InterruptedException {
        // 初始化redis数据,清除之前的测试数据残留
        secKillService.init();
        // 创建线程池
        ThreadPoolExecutor threadPoolExecutor = getThreadPoolExecutor();
        for (int i = 1; i < 20; i++) {
            int finalI = i;
            if (i % 2 == 0) {
                threadPoolExecutor.execute(() -> secKillService.secKillByRedis(finalI + ""));
            }
            threadPoolExecutor.execute(() -> secKillService.secKillByRedis(finalI + ""));
        }
        Thread.sleep(100);
        secKillService.print();
    }

    public ThreadPoolExecutor getThreadPoolExecutor() {
        // JVM 可用CPU个数
        int availableProcessors = Runtime.getRuntime().availableProcessors();
        // 线程池配置分析:
        // 计算密集型:CPU-多,内存-少。corePoolSize=N(cpu)+1。
        // 计算公式:N(thread) = N(cpu) * C(cpu) * W/C。
        // C(cpu):cpu的利用率;W:CPU的等待时间;C:cpu的计算时间。
        int corePoolSize = availableProcessors + 2;
        int maximumPoolSize = corePoolSize + 10;
        return new ThreadPoolExecutor(corePoolSize, maximumPoolSize, 10, TimeUnit.SECONDS, new LinkedBlockingDeque<>(500),
                new ThreadPoolExecutor.CallerRunsPolicy());
    }

    // 纪伯伦说:“当你不在渴望爱情,而去爱;当你不再追求成功,而去做;当你不再追求空泛的成长,而去做修身养性的事情,你的人生才刚刚开始。

}



总结

redisTemple结合脚本使用时,需注意接收格式。若格式错误,则返回结果为null。

未在线上实战过,请谨慎复制使用。
若redis集群,集群间同步不知道会不会出现问题。
有错误,欢迎指正。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐