基于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 21 Redis
你真的了解redis为什么要提供pipeline功能
Jun 22 Redis
redis使用不当导致应用卡死bug的过程解析
Jul 01 Redis
Redis入门教程详解
Aug 30 Redis
基于Redis zSet实现滑动窗口对短信进行防刷限流的问题
Feb 12 Redis
浅谈Redis 中的过期删除策略和内存淘汰机制
Apr 03 Redis
解决 Redis 秒杀超卖场景的高并发
Apr 12 Redis
windows安装 redis 6.2.6最新步骤详解
Apr 26 Redis
Redis入门基础常用操作命令整理
Jun 01 Redis
Redis过期数据是否会被立马删除
Jul 23 Redis
Redis配置外网可访问(redis远程连接不上)的方法
Dec 24 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
PHP中对数据库操作的封装
2006/10/09 PHP
php设计模式 Delegation(委托模式)
2011/06/26 PHP
php设计模式 Decorator(装饰模式)
2011/06/26 PHP
php实现将wav文件转换成图像文件并在页面中显示的方法
2015/04/21 PHP
JS+PHP实现用户输入数字后显示最大的值及所在位置
2017/06/19 PHP
提高Laravel应用性能方法详解
2019/06/24 PHP
JavaScript Base64编码和解码,实现URL参数传递。
2006/09/18 Javascript
js加解密 脚本解密
2008/02/22 Javascript
jQuery实现点击按钮弹出可关闭层的浮动层插件
2015/09/19 Javascript
Position属性之relative用法
2015/12/14 Javascript
webpack实现热加载自动刷新的方法
2017/07/30 Javascript
对mac下nodejs 更新到最新版本的最新方法(推荐)
2018/05/17 NodeJs
JavaScript实现简单音乐播放器
2020/04/17 Javascript
layui checkbox默认选中,获取选中值,清空所有选中项的例子
2019/09/02 Javascript
12 种使用Vue 的最佳做法
2020/03/30 Javascript
Vue解决移动端弹窗滚动穿透问题
2020/12/15 Vue.js
用Python的pandas框架操作Excel文件中的数据教程
2015/03/31 Python
Python使用QQ邮箱发送Email的方法实例
2017/02/09 Python
Python使用sort和class实现的多级排序功能示例
2018/08/15 Python
Django csrf 验证问题的实现
2018/10/09 Python
Django数据库类库MySQLdb使用详解
2019/04/28 Python
Python flask框架post接口调用示例
2019/07/03 Python
django数据关系一对多、多对多模型、自关联的建立
2019/07/24 Python
Python利用逻辑回归模型解决MNIST手写数字识别问题详解
2020/01/14 Python
使用tensorflow实现矩阵分解方式
2020/02/07 Python
使用Python求解带约束的最优化问题详解
2020/02/11 Python
keras中的loss、optimizer、metrics用法
2020/06/15 Python
Python用access判断文件是否被占用的实例方法
2020/12/17 Python
基于 HTML5 WebGL 实现的垃圾分类系统
2019/10/08 HTML / CSS
妇产医师自荐信
2014/01/29 职场文书
大学生社会实践评语
2014/04/25 职场文书
态度决定一切演讲稿
2014/05/20 职场文书
饭店服务员岗位职责
2015/02/09 职场文书
mysql部分操作
2021/04/05 MySQL
Nginx内网单机反向代理的实现
2021/11/07 Servers
关于JavaScript 中 if包含逗号表达式
2021/11/27 Javascript