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(系统写死)
8.浏览器访问
http://localhost/
疯狂点击秒杀按钮,查看后台变化,在点击时可能会出行失败的情况,我们刷新界面继续点击秒杀
当库存为0时界面会显示【抢光了】
上面是正常的业务演示,没有出现高并发,下面演示高并发情况
1.这里高并发我们使用的是Jmeter工具,我们需要进入官网下载
注意: 选择红框里面的下载链接,第一个是linux版本,第二个是window版本,soure下载的有问题。
2.解压进入bin目录,找到jmeter.bat启动jmeter,
设置请求协议,服务器ip,端口号,请求方式,路径,参数
5.新增观察结果数
用于查看运行结果
6.测试接口
清空redis,设置商品数量为10,
线程数设置为1秒80个,然后启动,查看结果数
查询redis商品库存与抢中商品用户,
此时会发现商品库存为-70,出现超卖的情况
解决超卖:秒杀方法添加事务
//秒杀过程
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位用户
码云地址