Springboot+redis+Vue實(shí)現(xiàn)秒殺的項(xiàng)目實(shí)踐
1、Redis簡介
Redis是一個(gè)開源的key-value存儲(chǔ)系統(tǒng)。
Redis的五種基本類型:String(字符串),list(鏈表),set(集合),zset(有序集合),hash,stream(Redis5.0后的新數(shù)據(jù)結(jié)構(gòu))
這些數(shù)據(jù)類型都支持push/pop、add/remove及取交集并集和差集及更豐富的操作,而且這些操作都是原子性的。
Redis的應(yīng)用場景為配合關(guān)系型數(shù)據(jù)庫做高速緩存,降低數(shù)據(jù)庫IO
需要注意的是,Redis是單線程的,如果一次批量處理命令過多,會(huì)造成Redis阻塞或網(wǎng)絡(luò)擁塞(傳輸數(shù)據(jù)量大)
2、實(shí)現(xiàn)代碼

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>org.example</groupId>
<artifactId>seckill</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.2.1.RELEASE</version>
<scope>test</scope>
</dependency>
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- spring2.X集成redis所需common-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
</configuration>
</plugin>
</plugins>
</build>
</project>application.properties.xml
#Redis服務(wù)器地址 spring.redis.host=192.168.1.2 #Redis服務(wù)器連接端口 spring.redis.port=6379 #Redis數(shù)據(jù)庫索引(默認(rèn)為0) spring.redis.database=0 #連接超時(shí)時(shí)間(毫秒) spring.redis.timeout=1800000 #連接池最大連接數(shù)(使用負(fù)值表示沒有限制) spring.redis.lettuce.pool.max-active=20 #最大阻塞等待時(shí)間(負(fù)數(shù)表示沒限制) spring.redis.lettuce.pool.max-wait=-1 #連接池中的最大空閑連接 spring.redis.lettuce.pool.max-idle=5 #連接池中的最小空閑連接 spring.redis.lettuce.pool.min-idle=0 # 關(guān)閉超時(shí)時(shí)間 #pring.redis.lettuce.shutdown-timeout=100 #配置spring啟動(dòng)端口號(hào) server.port=8080
前端界面
seckillpage.html
<!DOCTYPE html>
<html lang="en" xmlns:v-on="http://www.w3.org/1999/xhtml" xmlns:v-bind="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>redis秒殺</title>
</head>
<body>
<!-- 開發(fā)環(huán)境版本,包含了有幫助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- 官網(wǎng)提供的 axios 在線地址 -->
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.20.0-0/axios.min.js"></script>
<div id="app">
<h1>商品1元秒殺</h1>
<!-- 左箭頭 -->
<input type="button" value="<" v-on:click="prov" v-show="this.index>-1"/>
<img v-bind:src="imgArr[index]" width="200px" />
<!-- 右箭頭 -->
<input type="button" value=">" v-on:click="next" v-show="this.index<2"/><br>
<input type="button" v-on:click="seckill" value="秒 殺"/>
</div>
<script>
var app = new Vue({
el: "#app",
data: {
productId: "01234",
imgArr:[
"/image/phone1.png",
"/image/phone2.png",
"/image/phone3.png",
],
index:0
},
methods: {
seckill: function () {
let param = new URLSearchParams()
param.append('productId', this.productId)
param.append('index', this.index)
axios({
method: 'post',
url: 'http://192.168.1.6:8080/index/doSeckill',
data: param
}).then(function (response) {
if (response.data == true) {
alert("秒殺成功");
} else {
alert("搶光了");
}
},
function(error){
alert("發(fā)生錯(cuò)誤");
});
},
prov:function(){
this.index--;
},
next:function(){
this.index++;
}
}
});
</script>
</body>
</html>相關(guān)配置類
Redis配置類
RedisConfig.java
package com.springboot_redis_seckill.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 WuL2
* @create 2021-05-27 14:26
* @desc
**/
@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);
template.setKeySerializer(redisSerializer); //key序列化方式
template.setValueSerializer(jackson2JsonRedisSerializer); //value序列化
template.setHashValueSerializer(jackson2JsonRedisSerializer); //value hashmap序列化
return template;
}
@Bean(name = "cacheManager")
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解決查詢緩存轉(zhuǎn)換異常的問題
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解決亂碼的問題),過期時(shí)間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;
}
}配置Vue獲取后端接口數(shù)據(jù)時(shí)出現(xiàn)的跨域請求問題。
CorsConfig.java
package com.springboot_redis_seckill.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
/**
* @author: wu linchun
* @time: 2021/5/28 22:22
* @description: 解決跨域問題(接口是http,而axios一般請求的是https。從https到http是跨域,因此要配置跨域請求)
*/
@Configuration
public class CorsConfig {
private CorsConfiguration buildConfig() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("*"); //允許任何域名
corsConfiguration.addAllowedHeader("*"); //允許任何頭
corsConfiguration.addAllowedMethod("*"); //允許任何方法
return corsConfiguration;
}
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", buildConfig()); //注冊
return new CorsFilter(source);
}
}服務(wù)層
SecKillService.java
package com.springboot_redis_seckill.service;
public interface SecKillService {
public boolean doSecKill(String uid,String productId);
}SecKillServiceImpl.java
package com.springboot_redis_seckill.service.impl;
import com.springboot_redis_seckill.service.SecKillService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
/**
* @author WuL2
* @create 2021-05-27 14:53
* @desc
**/
@Service
public class SecKillServiceImpl implements SecKillService {
@Autowired
@Qualifier("redisTemplate")
private RedisTemplate redisTemplate;
@Override
public synchronized boolean doSecKill(String uid, String productId) {
//1、uid和productId非空判斷
if (uid == null || productId == null) {
return false;
}
//2、拼接key
String kcKey = "sk:" + productId + ":qt"; //庫存
String userKey = "sk:" + productId + ":user"; //秒殺成功的用戶
//3、獲取庫存
String kc = String.valueOf(redisTemplate.opsForValue().get(kcKey)) ;
if (kc == null) {
System.out.println("秒殺還沒有開始,請等待");
return false;
}
//4、判斷用戶是否已經(jīng)秒殺成功過了
if (redisTemplate.opsForSet().isMember(userKey, uid)) {
System.out.println("已秒殺成功,不能重復(fù)秒殺");
return false;
}
//5、如果庫存數(shù)量小于1,秒殺結(jié)束
if (Integer.parseInt(kc) <=0) {
System.out.println("秒殺結(jié)束");
return false;
}
//6、秒殺過程
redisTemplate.opsForValue().decrement(kcKey); //庫存數(shù)量減1
redisTemplate.opsForSet().add(userKey, uid);
System.out.println("秒殺成功。。。");
return true;
}
}控制層
package com.springboot_redis_seckill.controller;
import com.alibaba.fastjson.JSONObject;
import com.springboot_redis_seckill.service.SecKillService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Random;
/**
* @author WuL2
* @create 2021-05-27 14:28
* @desc
**/
@Controller
@RequestMapping("/index")
public class MyController {
@Autowired
@Qualifier("secKillServiceImpl")
private SecKillService secKillService;
@RequestMapping(value = {"/seckillpage"}, method = RequestMethod.GET)
public String seckillpage() {
return "/html/seckillpage.html";
}
@RequestMapping(value = {"/doSeckill"}, method = RequestMethod.POST)
@ResponseBody //自動(dòng)返回json格式的數(shù)據(jù)
public Object doSeckill(HttpServletRequest request, HttpServletResponse response) {
System.out.println("doSeckill");
String productId = request.getParameter("productId");
String index = request.getParameter("index");
System.out.println(productId+index); //拼接成為商品ID
int id = new Random().nextInt(50000); //使用隨機(jī)數(shù)生成用戶ID
String uid = String.valueOf(id) + " ";
boolean flag = secKillService.doSecKill(uid, productId+index);
System.out.println(flag);
return flag;
}
}啟動(dòng)類
RunApplication.java
package com.springboot_redis_seckill;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author WuL2
* @create 2021-05-27 14:32
* @desc
**/
@SpringBootApplication
public class RunApplication {
public static void main(String[] args) {
SpringApplication.run(RunApplication.class, args);
}
}3、啟動(dòng)步驟
因?yàn)橐还灿腥唐芬霘?,所以在redis里面設(shè)置三個(gè)商品的庫存數(shù)量。這里數(shù)量都設(shè)置為10。
127.0.0.1:6379> set sk:012340:qt 10 OK 127.0.0.1:6379> set sk:012341:qt 10 OK 127.0.0.1:6379> set sk:012342:qt 10 OK
要確保redis能夠被訪問,要確保關(guān)閉linux的防火墻,以及關(guān)閉redis的保護(hù)模式。
vim redis.conf --打開redis配置 service iptables stop? --關(guān)閉防火墻 //關(guān)閉redis保護(hù)模式 redis-cli --進(jìn)入redis客戶端 config set protected-mode "no" --配置里面關(guān)閉redis保護(hù)模式(只是進(jìn)入redis.conf把protected-mode變?yōu)閚o是不行的,還要加一句config set protected-mode "no"
啟動(dòng)springboot項(xiàng)目


秒殺成功后,該商品在redis中的數(shù)量就減1。

當(dāng)數(shù)量減為0時(shí),則提示“搶光了”。


4、使用ab進(jìn)行并發(fā)測試
如果是centOS 6版本的linux都是默認(rèn)按照了ab工具的。
如果沒有安裝ab工具,可在linux終端用命令聯(lián)網(wǎng)下載安裝。
yum install httpd-tools
安裝完成后,就可以使用ab工具進(jìn)行并發(fā)測試了。
在linux終端輸入如下命令:
ab -n 2000 -c 200 -p '/root/Desktop/post.txt' -T 'application/x-www-form-urlencoded' 'http://192.168.1.6:8080/index/doSeckill/'

012341這個(gè)商品庫存變?yōu)?了

5、線程安全
為了防止出現(xiàn)“超買”的現(xiàn)象,需要讓操作redis的方法是線程安全的(即在方法上加上一個(gè)“悲觀鎖”synchronized)。

如果不加synchronized就會(huì)出現(xiàn)“超買”現(xiàn)象,即redis庫存會(huì)出現(xiàn)負(fù)數(shù)


之所以產(chǎn)生這種現(xiàn)象是由于并發(fā)導(dǎo)致多個(gè)用戶同時(shí)調(diào)用了doSecKill()方法,多個(gè)用戶同時(shí)修改了redis中的sk:012342:qt的值,但暫時(shí)都沒有提交存入到redis中去。等到后來一起提交,導(dǎo)致了sk:012342:qt的值被修改了多次,因此會(huì)出現(xiàn)負(fù)數(shù)。
因此在doSecKill()方法加上悲觀鎖,用戶調(diào)用該方法就對(duì)該方法加鎖,修改了sk:012342:qt的值后并提交存入redis中之后,才會(huì)釋放鎖。其他用戶才能得到鎖并操作該方法。
6、總結(jié)
redis作為一種Nosql的非關(guān)系型數(shù)據(jù)庫,因?yàn)槠鋯螌?shí)例,簡單高效的特性通常會(huì)被作為其他關(guān)系型數(shù)據(jù)庫的高速緩存。尤其是在秒殺這樣的高并發(fā)操作。先將要秒殺的商品信息從數(shù)據(jù)庫讀入到redis中,秒殺的過程實(shí)際上是在與redis進(jìn)行交互。等秒殺完成后再將秒殺的結(jié)果存入數(shù)據(jù)庫??梢杂行Ы档徒档蛿?shù)據(jù)庫IO,防止數(shù)據(jù)庫宕機(jī)。
7、參考資料
https://www.bilibili.com/video/BV1Rv41177Af?p=27
https://www.cnblogs.com/taiyonghai/p/5810150.html
到此這篇關(guān)于Springboot+redis+Vue實(shí)現(xiàn)秒殺的項(xiàng)目實(shí)踐的文章就介紹到這了,更多相關(guān)Springboot+redis+Vue 秒殺內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot+Redis隊(duì)列實(shí)現(xiàn)Java版秒殺的示例代碼
- 基于Redis結(jié)合SpringBoot的秒殺案例詳解
- SpringBoot之使用Redis實(shí)現(xiàn)分布式鎖(秒殺系統(tǒng))
- 基于SpringBoot構(gòu)建電商秒殺項(xiàng)目代碼實(shí)例
- springboot集成redis實(shí)現(xiàn)簡單秒殺系統(tǒng)
- springboot集成開發(fā)實(shí)現(xiàn)商場秒殺功能
- 如何通過SpringBoot實(shí)現(xiàn)商城秒殺系統(tǒng)
- SpringBoot微服務(wù)實(shí)現(xiàn)秒殺搶購代金券功能
相關(guān)文章
mybatis plus CU自動(dòng)填充 和 軟刪除自動(dòng)填充的實(shí)現(xiàn)方法
這篇文章主要介紹了mybatis plus CU自動(dòng)填充 和 軟刪除自動(dòng)填充的實(shí)現(xiàn)方法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-07-07
Springboot使用Spring Data JPA實(shí)現(xiàn)數(shù)據(jù)庫操作
Spring Data JPA 是 Spring 基于 Spring Data 框架、在JPA 規(guī)范的基礎(chǔ)上開發(fā)的一個(gè)框架,使用 Spring Data JPA 可以極大地簡化JPA 的寫法,本章我們將詳細(xì)介紹在Springboot中使用 Spring Data JPA 來實(shí)現(xiàn)對(duì)數(shù)據(jù)庫的操作2021-06-06
通過使用Byte?Buddy便捷創(chuàng)建Java?Agent
這篇文章主要為大家介紹了如何通過使用Byte?Buddy便捷創(chuàng)建Java?Agent的使用說明,有需要的朋友可以借鑒參考下希望能夠有所幫助,祝大家多多進(jìn)步2022-03-03
spring boot thymeleaf 圖片上傳web項(xiàng)目根目錄操作步驟
這篇文章主要介紹了spring boot thymeleaf 圖片上傳web項(xiàng)目根目錄步驟,本文給大家提到了thymeleaf的基礎(chǔ)知識(shí),需要的朋友可以參考下2018-03-03
java多線程批量處理百萬級(jí)的數(shù)據(jù)方法示例
這篇文章主要介紹了java多線程批量處理百萬級(jí)的數(shù)據(jù)的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用java多線程具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2025-02-02
運(yùn)行Jar包出現(xiàn)提示xxx中沒有主清單屬性報(bào)錯(cuò)問題解決方法
這篇文章主要介紹了運(yùn)行Jar包出現(xiàn):xxx中沒有主清單屬性報(bào)錯(cuò),當(dāng)出現(xiàn)報(bào)錯(cuò):xxx中沒有主清單屬性,解決方法也很簡單,在pom.xml配置中,加上相應(yīng)配置即可,需要的朋友可以參考下2023-08-08

