redis(四) - springboot + redis实现分布式商品秒杀

1、index.html

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
    <script src="webjars/jquery/3.6.0/jquery.js">script>
    <script type="text/javascript">
        $(document).ready(function(){
            $("#secKill").click(function(){
                let prodId=$("#prodId").val();
                $.post(
                    "/test/secKill",
                    {prodId:prodId},
                    function(data){
                        if (!data){
                            alert("来晚了");
                        }
                    }
                );
            });
        });

    script>
head>
<body>
    <input type="text" id="prodId">
    <input type="button" value="秒杀" id="secKill" >
body>
html>

2、Controller

@RestController
@RequestMapping("/test")
public class SecKillController {

    @Autowired
    SecKillService secKillService;

    @RequestMapping("/secKill")
    public boolean secKill(String prodId){
        if (prodId == null || "".equals(prodId)){
            return false;
        }
        Random random = new Random();
        //随机用户ID
        int uid = random.nextInt(50000);
        return secKillService.secKillByScript(prodId,String.valueOf(uid));
    }
}

3、Service

@Service
public class SecKillService {

    @Autowired
    RedisTemplate redisTemplate;

    public boolean secKillByScript(String proId,String uId){

        if ("".equals(proId) || null == proId ||"".equals(uId) || null == uId){
            return false;
        }

        DefaultRedisScript redisScript = new DefaultRedisScript();
        //加载字符串格式脚本
//        redisScript.setScriptText();
        //加载文本格式脚本
        redisScript.setLocation(new ClassPathResource("secKill.lua"));
        redisScript.setResultType(String.class);
        List<String> list = new ArrayList<>();
        list.add(uId);
        list.add(proId);
        Object execute = redisTemplate.execute(redisScript, new StringRedisSerializer(),
                new StringRedisSerializer(), list, uId, proId);

        if ("0".equals(String.valueOf(execute))){
            System.out.println("未开始");
        }else if("1".equals(String.valueOf(execute))){
            System.out.println("已抢光");
        }else if("2".equals(String.valueOf(execute))){
            System.out.println("已秒杀");
        }else if("3".equals(String.valueOf(execute))){
            System.out.println("秒杀成功");
        }
        return true;
    }
}

4、lua 脚本

  • 在lua脚本中,有两个全局的变量,是用来接收redis应用端传递的键值和其它参数的,分别为KEYS、ARGV。

  • 在应用端传递给KEYS时是一个数组列表,在lua脚本中通过索引方式获取数组内的值。

  • 在应用端,传递给ARGV的参数比较灵活,可以是多个独立的参数,但对应到Lua脚本中是,统一用ARGV这个数组接收,获取方式也是通过数组下标获取

redisTemplate.execute(redisScript, new StringRedisSerializer(),
                new StringRedisSerializer(), list, uId, proId);

就拿 SecKillService 中的举例,参数列表中的 list就是有 KEYS 接收;而后面的参数( uId、proId )由 ARGV 接收

secKill.lua

放在 /resources 目录下

-- KEYS[1] =list(0) 
local userid=KEYS[1];
-- KEYS[2] =list(1) 
local prodid=KEYS[2];
local uidKey="prod:user:"..userid;
local proKey="prod:kc:"..prodid;
--redis.log(redis.LOG_NOTICE,uidKey);
--判断是否有库存,没有表示未开启秒杀
local userExists=redis.call("exists",proKey);
if tonumber(userExists)==0 then
    return "0";
end
--判断库存是否足够,不够停止秒杀
local num=redis.call("get",proKey);
--redis.log(redis.LOG_NOTICE,proKey);
if tonumber(num) <=0 then
    return "1";
end
--判断用户是否已参与过秒杀活动
local isMember=redis.call("sisMember",uidKey,userid);
if tonumber(isMember)==1 then
    return "2";
else
    redis.call("decr",proKey);
    redis.call("sadd",uidKey,userid);
end
return "3";

5、配置文件

application.properties

server.port=8001
#redis配置
spring.redis.host=xxx.xxx.xxx.xx
spring.redis.port=6379
spring.redis.password=redis123
spring.redis.database=0
spring.redis.timeout=1800000
spring.redis.lettuce.pool.max-active=20
spring.redis.lettuce.pool.max-wait=1
spring.redis.lettuce.pool.max-idle=50
spring.redis.lettuce.pool.min-idle=0



#redis集群配置
#spring.redis.cluster.nodes=192.168.117.130:6379,192.168.117.130:6380,192.168.117.130:6381,192.168.117.130:6382,192.168.117.130:6383,192.168.117.130:6384,192.168.117.130:6385,192.168.117.130:6386,192.168.117.130:6387
#spring.redis.cluster.max-redirects=3
#spring.redis.jedis.pool.max-active=20
#spring.redis.jedis.pool.max-wait=1
#spring.redis.jedis.pool.max-idle=50
#spring.redis.jedis.pool.min-idle=0
#spring.redis.timeout=60000
#spring.redis.sentinel.master=mysentinel
#spring.redis.sentinel.nodes=192.168.117.130:6379,192.168.117.130:6380,192.168.117.130:6381

6、依赖

pom.xml


 
<dependency>
	<groupId>org.springframework.bootgroupId>
	<artifactId>spring-boot-starter-data-redisartifactId>
	<version>2.5.6version>
dependency>


<dependency>
	<groupId>org.apache.commonsgroupId>
	<artifactId>commons-pool2artifactId>
dependency>

<dependency>
	<groupId>org.springframework.bootgroupId>
	<artifactId>spring-boot-starter-thymeleafartifactId>
	<version>2.5.6version>
dependency>
        

<dependency>
	<groupId>org.webjarsgroupId>
	<artifactId>jqueryartifactId>
	<version>3.6.0version>
dependency>

7、测试

通过 linux 中的 ab 测试接口

ab -n 2000 -c 200 -p ~/postfile -T application/x-www-form-urlencoded http://192.168.43.99:8001/test/secKill	
  • -n :总访问次数
  • -c :每次的并发量
  • -p :保存参数的文件
  • -T :请求类型

postfile

prodId=0001&

或者

ab -n 2000 -c 200 -T application/x-www-form-urlencoded http://192.168.43.99:8001/test/secKill?prodId=0001

你可能感兴趣的:(redis,springboot,java,redis,spring,boot)