springboot整合redis,使用redisTemplate实现简易秒杀功能,使用jmeter压力测试秒杀接口

项目结构
springboot整合redis,使用redisTemplate实现简易秒杀功能,使用jmeter压力测试秒杀接口_第1张图片

1.新建maven项目导入pom依赖

	<parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>2.1.4.RELEASEversion>
    parent>

    <dependencies>
        <dependency>
            <groupId>junitgroupId>
            <artifactId>junitartifactId>
            <version>4.11version>
            <scope>testscope>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-thymeleafartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-data-redisartifactId>
        dependency>
        
        <dependency>
            <groupId>org.apache.commonsgroupId>
            <artifactId>commons-pool2artifactId>
            <version>2.6.0version>
        dependency>
    dependencies>

2.配置application.properties

server.port=80
#配置html
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.cache=false
spring.mvc.static-path-pattern=/static/**
#redis
#Redis服务器地址
spring.redis.host=localhost
#Redis服务器连接端口
spring.redis.port=6379
#Redis数据库索引(默认为0)
spring.redis.database= 0
#连接超时时间(毫秒)
spring.redis.timeout=1800000
#连接池最大连接数(使用负值表示没有限制)
spring.redis.lettuce.pool.max-active=100
#最大阻塞等待时间(负数表示没限制)
spring.redis.lettuce.pool.max-wait=-1
#连接池中的最大空闲连接
spring.redis.lettuce.pool.max-idle=5
#连接池中的最小空闲连接
spring.redis.lettuce.pool.min-idle=0

3.导入jquery.js,编写前端界面index.html

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>redis秒杀title>
head>
<body>
<h3>访问成功h3>
<h1>iPhone 13 Pro !!! 1元秒杀!!!
h1>


<form id="msform" action="doseckill" enctype="application/x-www-form-urlencoded">
    <input type="hidden" id="prodid" name="prodid" value="0101">
    <input type="button" id="miaosha_btn" name="seckill_btn" value="秒杀点我"/>
form>
body>
<script  type="text/javascript" th:src="@{js/jquery-3.1.0.js}" src="js/jquery-3.1.0.js">script>
<script  type="text/javascript">
    $(function(){
        $("#miaosha_btn").click(function(){
            var url=$("#msform").attr("action");
            $.post(url,$("#msform").serialize(),function(data){
                if(data==false){
                    alert("抢光了" );
                    $("#miaosha_btn").attr("disabled",true);
                }
            } );
        })
    })
script>
html>

4.编写redis配置类

package com.springboot.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;

/**
 * @author TANGSHUAI
 * @version 1.0
 * @date 2021-08-30 22:50
 */
@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        template.setConnectionFactory(factory);
//key序列化方式
        template.setKeySerializer(redisSerializer);
//value序列化
        template.setValueSerializer(jackson2JsonRedisSerializer);
//value hashmap序列化
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        return template;
    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解决乱码的问题),过期时间600秒
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofSeconds(600))
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
                .disableCachingNullValues();
        RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
                .cacheDefaults(config)
                .build();
        return cacheManager;
    }
}

5.编写controller

不使用事务

package com.springboot.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.List;
import java.util.Random;

/**
 * @author TANGSHUAI
 * @version 1.0
 * @date 2021-08-30 22:41
 */
@Controller
public class RedisController {

    @Autowired
    private RedisTemplate redisTemplate;

	//测试redis连接是否正常
    @GetMapping("")
    public String index() {
        //设置值
        redisTemplate.opsForValue().set("name", "张三");
        //取值
        String name = (String) redisTemplate.opsForValue().get("name");
        System.out.println(name);
        return "index";
    }
	
	//秒杀业务
    @PostMapping("/doseckill")
    public @ResponseBody
    boolean doseckill(String prodid) {
        Random random = new Random();
        String result="";
        for (int i=0;i<6;i++)
        {
            result+=random.nextInt(10);
        }
        boolean b = this.doSecKill(result, prodid);
        return b;
    }

    //秒杀过程
    public boolean doSecKill(String uid, String prodid)  {
        //1 uid和prodid非空判断
        if (uid == null || prodid == null) {
            return false;
        }

        //3 拼接key
        // 3.1 库存key
        String kcKey = "sk:" + prodid + ":qt";
        // 3.2 秒杀成功用户key
        String userKey = "sk:" + prodid + ":user";

        //4 获取库存,如果库存null,秒杀还没有开始
        String kc = String.valueOf(redisTemplate.opsForValue().get(kcKey)) ;
        if (kc == null) {
            System.out.println("秒杀还没有开始,请等待");
            return false;
        }

        // 5 判断用户是否重复秒杀操作
        if (redisTemplate.opsForSet().isMember(userKey, uid)) {
            System.out.println("已经秒杀成功了,不能重复秒杀");
            return false;
        }

        //6 判断如果商品数量,库存数量小于1,秒杀结束
        if (Integer.parseInt(kc) <= 0) {
            System.out.println("秒杀已经结束了");
            return false;
        }

        //7 秒杀过程
        //7.1 库存-1
        redisTemplate.boundValueOps(kcKey).decrement(1);
        //7.2 把秒杀成功用户添加清单里面
        redisTemplate.opsForSet().add(userKey, uid);
        
        System.out.println("秒杀成功了..");
        return true;
    }
}
 

6.启动类,启动项目

package com.springboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @author TANGSHUAI
 * @version 1.0
 * @date 2021-08-30 22:40
 */
@SpringBootApplication
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
}

7.清空redis,设置redis内商品编号为0101的库存数为10,
注意设置的key格式为sk:0101:qt(系统写死)
springboot整合redis,使用redisTemplate实现简易秒杀功能,使用jmeter压力测试秒杀接口_第2张图片
8.浏览器访问

http://localhost/

springboot整合redis,使用redisTemplate实现简易秒杀功能,使用jmeter压力测试秒杀接口_第3张图片
疯狂点击秒杀按钮,查看后台变化,在点击时可能会出行失败的情况,我们刷新界面继续点击秒杀
springboot整合redis,使用redisTemplate实现简易秒杀功能,使用jmeter压力测试秒杀接口_第4张图片
当库存为0时界面会显示【抢光了】
springboot整合redis,使用redisTemplate实现简易秒杀功能,使用jmeter压力测试秒杀接口_第5张图片
上面是正常的业务演示,没有出现高并发,下面演示高并发情况

1.这里高并发我们使用的是Jmeter工具,我们需要进入官网下载

注意: 选择红框里面的下载链接,第一个是linux版本,第二个是window版本,soure下载的有问题。
springboot整合redis,使用redisTemplate实现简易秒杀功能,使用jmeter压力测试秒杀接口_第6张图片
2.解压进入bin目录,找到jmeter.bat启动jmeter,

进入界面设置为简体中文,
springboot整合redis,使用redisTemplate实现简易秒杀功能,使用jmeter压力测试秒杀接口_第7张图片
2.右击新增线程组

springboot整合redis,使用redisTemplate实现简易秒杀功能,使用jmeter压力测试秒杀接口_第8张图片
设置线程名称,线程数
springboot整合redis,使用redisTemplate实现简易秒杀功能,使用jmeter压力测试秒杀接口_第9张图片
3.新增HTTP请求
springboot整合redis,使用redisTemplate实现简易秒杀功能,使用jmeter压力测试秒杀接口_第10张图片

设置请求协议,服务器ip,端口号,请求方式,路径,参数

springboot整合redis,使用redisTemplate实现简易秒杀功能,使用jmeter压力测试秒杀接口_第11张图片
4.由于我们是form表单提交,所有需要新增请求头

springboot整合redis,使用redisTemplate实现简易秒杀功能,使用jmeter压力测试秒杀接口_第12张图片
springboot整合redis,使用redisTemplate实现简易秒杀功能,使用jmeter压力测试秒杀接口_第13张图片
5.新增观察结果数
用于查看运行结果
springboot整合redis,使用redisTemplate实现简易秒杀功能,使用jmeter压力测试秒杀接口_第14张图片
6.测试接口
清空redis,设置商品数量为10,
springboot整合redis,使用redisTemplate实现简易秒杀功能,使用jmeter压力测试秒杀接口_第15张图片

线程数设置为1秒80个,然后启动,查看结果数
springboot整合redis,使用redisTemplate实现简易秒杀功能,使用jmeter压力测试秒杀接口_第16张图片
springboot整合redis,使用redisTemplate实现简易秒杀功能,使用jmeter压力测试秒杀接口_第17张图片
查询redis商品库存与抢中商品用户,
此时会发现商品库存为-70,出现超卖的情况

springboot整合redis,使用redisTemplate实现简易秒杀功能,使用jmeter压力测试秒杀接口_第18张图片

解决超卖:秒杀方法添加事务

//秒杀过程
    public boolean doSecKill(String uid, String prodid)  {
        //1 uid和prodid非空判断
        if (uid == null || prodid == null) {
            return false;
        }

        //3 拼接key
        // 3.1 库存key
        String kcKey = "sk:" + prodid + ":qt";
        // 3.2 秒杀成功用户key
        String userKey = "sk:" + prodid + ":user";

        //开启事务
        redisTemplate.setEnableTransactionSupport(true);
        //监视库存
        redisTemplate.watch(kcKey);

        //4 获取库存,如果库存null,秒杀还没有开始
        String kc = String.valueOf(redisTemplate.opsForValue().get(kcKey)) ;
        if (kc == null) {
            System.out.println("秒杀还没有开始,请等待");
            return false;
        }

        // 5 判断用户是否重复秒杀操作
        if (redisTemplate.opsForSet().isMember(userKey, uid)) {
            System.out.println("已经秒杀成功了,不能重复秒杀");
            return false;
        }

        //6 判断如果商品数量,库存数量小于1,秒杀结束
        if (Integer.parseInt(kc) <= 0) {
            System.out.println("秒杀已经结束了");
            return false;
        }

        //7 秒杀过程
        //7.1 库存-1
        //redisTemplate.boundValueOps(kcKey).decrement(1);
        //7.2 把秒杀成功用户添加清单里面
        //redisTemplate.opsForSet().add(userKey, uid);
        //使用事务


        //2.使用事务
        redisTemplate.multi();

        //3.组队操作
        redisTemplate.boundValueOps(kcKey).decrement(1);

        redisTemplate.opsForSet().add(userKey, uid);

        //4.执行
        List<Object> exec = redisTemplate.exec();
        if(exec == null || exec.size()==0) {
            System.out.println("秒杀失败了....");
            return false;
        }
        System.out.println("秒杀成功了..");
        return true;
    }

运行结果
使用redis desktop manager查看数据
不会出现超卖,商品数量为0,用户为10位用户
springboot整合redis,使用redisTemplate实现简易秒杀功能,使用jmeter压力测试秒杀接口_第19张图片
springboot整合redis,使用redisTemplate实现简易秒杀功能,使用jmeter压力测试秒杀接口_第20张图片
码云地址

你可能感兴趣的:(后端,redis,spring,boot,spring)