基于Redis结合SpringBoot的秒杀案例详解


Posted in Redis onOctober 05, 2021

1、构建SpringBoot项目

搭建名为quickbuy的springboot项目,相关的依赖包如下所示:

<?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 https://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.1.13.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.baizhi</groupId>
    <artifactId>quickbuy</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>quickbuy</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <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.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.5</version>
        </dependency>

        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpcore</artifactId>
            <version>4.4.10</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

引入了Redis、HttpClient等依赖包。
项目结构

基于Redis结合SpringBoot的秒杀案例详解

2、启动类

package com.baizhi;

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

@SpringBootApplication
public class QuickbuyApplication {

    public static void main(String[] args) {
        SpringApplication.run(QuickbuyApplication.class, args);
    }
}

3、在Controller层里定义秒杀接口

@RestController
public class QuickBuyController {
    @Autowired
    private SellService sellService;

    @RequestMapping("/quickBuy/{item}/{owner}")
    public String quickbuy(@PathVariable String item,@PathVariable String owner){
        String result=sellService.quickBuy(item,owner);
        if(!result.equals("0")){
            return owner+"success";
        }else{
            return owner+"fail";
        }
    }
}

  通过@RequestMapping注解们可以把"/quickBuy/{item}/{owner}"格式的url映射到quickBuy方法上。
   quickBuy是秒杀接口,该接口包含的两个参数是item和owner,分别表示待秒杀的商品名和发起秒杀请求的用户。这两个参数均被@PathVariable注解修饰,说明来自于url里的{item}和{owner}部分。
  在这个quickBuy秒杀接口中调用了SellService类里的quickBuy方法实现了秒杀功能,并根据SellService类quickBuy方法返回的结果,向外部返回“秒杀成功”或“秒杀失败”的字符串语句。

4、在Service层里通过lua脚本实现秒杀效果

package com.baizhi.service;

import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class SellService {
    @Resource
    private RedisTemplate redisTemplate;

    public String quickBuy(String item, String owner) {
        //用lua脚本实现秒杀
        String luaScript="local owner=ARGV[1]\n" +
                "local item=KEYS[1] \n" +
                "local leftNum=tonumber(redis.call('get',item)) \n" +
                "if(leftNum>=1)\n" +
                "then redis.call('decrby',item,1)\n" +
                "redis.call('rpush','ownerList',owner)\n" +
                "return 1 \n" +
                "else \n" +
                "return 0 \n" +
                "end\n" +
                "\n";
        String key=item;
        String args=owner;
        DefaultRedisScript<String> redisScript=new DefaultRedisScript<String>();
        redisScript.setScriptText(luaScript);
        //调用lua脚本,请注意传入的参数
        Object luaResult=redisTemplate.execute((RedisConnection connection)->connection.eval(
           redisScript.getScriptAsString().getBytes(),
           ReturnType.INTEGER,
           1,
           key.getBytes(),
           args.getBytes()
        ));
        //根据lua脚本的执行情况返回结果
        return luaResult.toString();
    }
}

对lua脚本的解释如下:

   通过ARGV[1]参数传入发起秒杀请求的用户,用KEYS[1]参数传入待秒杀的商品。通过get item命令判断item商品在Redis里还有多少库存。
  if语句中判定剩余库存大于等于1,就会先执行decrby命令把库存数减1,随后调用第6行的rpush命令,在ownerList里记录当前秒杀成功的用户,并通过return 1表示秒杀成功。如果判断库存数已经小于1,那么return 0表示秒杀失败。
  其中将lua脚本赋予redisScript对象,并通过redisTemplate.execute方法执行lua脚本。

在调用redisTemplate.execute方法执行lua脚本时请注意以下三点:

  • 需要以butes方式传入脚本
  • 需要指定返回类型
  • 传入该lua脚本所包含的KEYS类型参数的个数是1.
  • 传入的KEYS和ARGV类型的参数需要转换成bytes类型

5、配置redis连接参数

application.properties

server.port=8081

spring.redis.host=192.168.159.22
spring.redis.port=6379

6、演示秒杀效果

 6.1 准备redis环境

我用的刚搭建的redis主从复制集群,一主二从

基于Redis结合SpringBoot的秒杀案例详解

设置10个商品

基于Redis结合SpringBoot的秒杀案例详解

6.2 启动项目

  在浏览器访问http://localhost:8081/quickBuy/Computer/abc,测试秒杀接口,该url传入的商品名是“Computer”,需要和上面设置的商品名称一致,传入的发起秒杀请求的客户端名字为abc。输入该url后,能看到表示秒杀成功的如下输出。

基于Redis结合SpringBoot的秒杀案例详解

进入redis查看

基于Redis结合SpringBoot的秒杀案例详解

发现商品数量变成了9,且能看到秒杀成功的用户列表。

6.3 多线程形式发起秒杀请求

QuickBuyClients.java

package com.baizhi.client;

import org.apache.http.HttpEntity;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;

public class QuickBuyClients extends Thread{
    @Override
    public void run() {
        QuickBuyUtil.quickBuy();
    }

    public static void main(String[] args) {
        //开启15个线程,线程数多余秒杀商品数
        for(int cnt=0;cnt<15;cnt++){
            new QuickBuyClients().start();
        }
    }
}
//封装秒杀方法的工具类
class QuickBuyUtil{
    //在这个方法里,用HttpGet对象发起秒杀请求
    public static void quickBuy(){
        String user=Thread.currentThread().getName();
        CloseableHttpClient httpClient= HttpClientBuilder.create().build();
        //创建秒杀Get类型的url请求
        HttpGet httpGet=new HttpGet("http://localhost:8081/quickBuy/Computer/"+user);
        //得到响应结果
        CloseableHttpResponse res=null;
        try{
            res=httpClient.execute(httpGet);
            HttpEntity responseEntity=res.getEntity();
            if(res.getStatusLine().equals("200")&&responseEntity!=null){
                System.out.println("秒杀结果:"+ EntityUtils.toString(responseEntity));
            }
        }catch (ClientProtocolException e){
            e.printStackTrace();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try{
                //回收http连接资源
                if(httpClient!=null){
                    httpClient.close();
                }
                if(res!=null){
                    res.close();
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

先重新设置商品数量为10

基于Redis结合SpringBoot的秒杀案例详解

启动上面的程序
再次进入Redis查看商品数量和秒杀成功的用户

基于Redis结合SpringBoot的秒杀案例详解

可以看到,15个线程秒杀商品,最终成功的只有10个。

到此这篇关于Redis结合SpringBoot的秒杀案例的文章就介绍到这了,更多相关Redis结合SpringBoot秒杀内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Redis 相关文章推荐
详解Redis基本命令与使用场景
Jun 01 Redis
深入理解redis中multi与pipeline
Jun 02 Redis
解析高可用Redis服务架构分析与搭建方案
Jun 20 Redis
压缩Redis里的字符串大对象操作
Jun 23 Redis
浅谈Redis中的RDB快照
Jun 29 Redis
浅谈Redis位图(Bitmap)及Redis二进制中的问题
Jul 15 Redis
Redis三种集群模式详解
Oct 05 Redis
Redis 中使用 list,streams,pub/sub 几种方式实现消息队列的问题
Mar 16 Redis
Redis高可用集群redis-cluster详解
Mar 20 Redis
Redis安装使用RedisJSON模块的方法
Mar 23 Redis
Redis超详细讲解高可用主从复制基础与哨兵模式方案
Apr 07 Redis
Redis唯一ID生成器的实现
Jul 07 Redis
Jedis操作Redis实现模拟验证码发送功能
Sep 25 #Redis
为什么RedisCluster设计成16384个槽
使用redis生成唯一编号及原理示例详解
Sep 15 #Redis
Redis读写分离搭建的完整步骤
Sep 14 #Redis
在项目中使用redis做缓存的一些思路
Redis RDB技术底层原理详解
Sep 04 #Redis
使用redis实现延迟通知功能(Redis过期键通知)
You might like
56.com视频采集接口程序(PHP)
2007/09/22 PHP
深入解析php之sphinx
2013/05/15 PHP
thinkphp模板输出技巧汇总
2014/11/24 PHP
php递归json类实例
2014/12/02 PHP
安装PHP扩展时解压官方 tgz 文件后没有configure文件无法进行配置编译的问题
2020/08/26 PHP
javascript 循环读取JSON数据的代码
2010/07/17 Javascript
jQuery 借助插件Lavalamp实现导航条动态美化效果
2013/09/27 Javascript
文本域中换行符的替换示例
2014/03/04 Javascript
HTML页面登录时的JS验证方法
2014/05/28 Javascript
Js实现滚动变色的文字效果
2014/06/16 Javascript
javascript实现unicode与ASCII相互转换的方法
2015/12/10 Javascript
Nodejs进阶:核心模块net入门学习与实例讲解
2016/11/21 NodeJs
Javascript计算二维数组重复值示例代码
2016/12/18 Javascript
vue中实现移动端的scroll滚动方法
2018/03/03 Javascript
JS实现滚动条触底加载更多
2019/09/19 Javascript
微信小程序调用wx.getImageInfo遇到的坑解决
2020/05/31 Javascript
解决removeEventListener 无法清除监听的问题
2020/10/30 Javascript
[20:46]Ti4循环赛第三日VG vs DK
2014/07/12 DOTA
[01:14]DOTA2 7.22版本新增神杖效果展示(智力英雄篇)
2019/05/29 DOTA
对python程序内存泄漏调试的记录
2018/06/11 Python
浅谈python在提示符下使用open打开文件失败的原因及解决方法
2018/11/30 Python
线程安全及Python中的GIL原理分析
2019/10/29 Python
JAVA及PYTHON质数计算代码对比解析
2020/06/10 Python
python3.5的包存放的具体路径
2020/08/16 Python
10个python爬虫入门基础代码实例 + 1个简单的python爬虫完整实例
2020/12/16 Python
苹果音乐订阅:Apple Music
2018/08/02 全球购物
初一体育教学反思
2014/01/29 职场文书
应届毕业生个人求职信范文
2014/01/29 职场文书
关爱残疾人标语
2014/06/25 职场文书
客房部经理岗位职责
2015/02/02 职场文书
销售经理岗位职责范本
2015/04/02 职场文书
青涩记忆观后感
2015/06/18 职场文书
如何书写邀请函?
2019/06/24 职场文书
幽默导游词应该怎么写?
2019/08/26 职场文书
Django如何与Ajax交互
2021/04/29 Python
VUE中的v-if与v-show区别介绍
2022/03/13 Vue.js