基于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遍历所有key的两个命令(KEYS 和 SCAN)
Apr 12 Redis
浅谈Redis在直播场景的实践方案
Apr 27 Redis
Redis5之后版本的高可用集群搭建的实现
Apr 27 Redis
Redis延迟队列和分布式延迟队列的简答实现
May 13 Redis
详解Redis基本命令与使用场景
Jun 01 Redis
Redisson实现Redis分布式锁的几种方式
Aug 07 Redis
Redis如何实现分布式锁
Aug 23 Redis
redis sentinel监控高可用集群实现的配置步骤
Apr 01 Redis
redis复制有可能碰到的问题汇总
Apr 03 Redis
Redis官方可视化工具RedisInsight安装使用教程
Apr 19 Redis
Redis 异步机制
May 15 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中socket的用法详解
2014/10/24 PHP
支持png透明图片的php生成缩略图类分享
2015/02/08 PHP
PHP排序算法之基数排序(Radix Sort)实例详解
2018/04/21 PHP
Ubuntu中支持PHP5与PHP7双版本的简单实现
2018/08/19 PHP
JQuery Tips(4) 一些关于提高JQuery性能的Tips
2009/12/19 Javascript
JavaScript对象、属性、事件手册集合方便查询
2010/07/04 Javascript
用nodejs访问ActiveX对象,以操作Access数据库为例。
2011/12/15 NodeJs
JS和函数式语言的三特性
2014/03/05 Javascript
javascript中的取反再取反~~没有意义
2014/04/06 Javascript
JavaScript之数组(Array)详解
2015/04/01 Javascript
jQuery时间插件jquery.clock.js用法实例(5个示例)
2016/01/14 Javascript
jquery 全选、全不选、反选效果的实现代码【推荐】
2016/05/05 Javascript
浅析Nodejs npm常用命令
2016/06/14 NodeJs
AngularJS 路由和模板实例及路由地址简化方法(必看)
2016/06/24 Javascript
最细致的vue.js基础语法 值得收藏!
2016/11/03 Javascript
Radio 单选JS动态添加的选项onchange事件无效的解决方法
2016/12/12 Javascript
javascript中闭包概念与用法深入理解
2016/12/15 Javascript
Node连接mysql数据库方法介绍
2017/02/07 Javascript
正则表达式基本语法及表单验证操作详解【基于JS】
2017/04/07 Javascript
thinkjs 文件上传功能实例代码
2017/11/08 Javascript
vue中路由参数传递可能会遇到的坑
2017/12/07 Javascript
vue2.0安装style/css loader的方法
2018/03/14 Javascript
使用vue-route 的 beforeEach 实现导航守卫(路由跳转前验证登录)功能
2018/03/22 Javascript
微信小程序使用npm支持踩坑
2018/11/07 Javascript
vue生命周期的探索
2019/04/03 Javascript
使用vue自定义指令开发表单验证插件validate.js
2019/05/23 Javascript
关于AngularJS中几种Providers的区别总结
2020/05/17 Javascript
[48:23]DOTA2上海特级锦标赛主赛事日 - 4 败者组第四轮#1COL VS EG第一局
2016/03/05 DOTA
Python实现的生成自我描述脚本分享(很有意思的程序)
2014/07/18 Python
python处理按钮消息的实例详解
2017/07/11 Python
Caffe均值文件mean.binaryproto转mean.npy的方法
2018/07/09 Python
python tkinter GUI绘制,以及点击更新显示图片代码
2020/03/14 Python
小学国庆节活动方案
2014/02/11 职场文书
初中同学会活动方案
2014/08/22 职场文书
委托培训协议书
2014/11/17 职场文书
汤姆叔叔的小屋读书笔记
2015/06/30 职场文书