spring boot redis实现秒杀
spring boot redis实现秒杀
·
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
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集群,集群间同步不知道会不会出现问题。
有错误,欢迎指正。

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