基于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 14 Redis
基于Redis实现分布式锁的方法(lua脚本版)
May 12 Redis
详解Redis集群搭建的三种方式
May 31 Redis
Redis可视化客户端小结
Jun 10 Redis
Redis 哨兵集群的实现
Jun 18 Redis
分布式架构Redis中有哪些数据结构及底层实现原理
Mar 13 Redis
redis数据一致性的实现示例
Mar 18 Redis
Redis基本数据类型Set常用操作命令
Jun 01 Redis
Redis实现分布式锁的五种方法详解
Jun 14 Redis
Redis实战之Lettuce的使用技巧详解
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
smarty 原来也不过如此~~呵呵
2006/11/25 PHP
phpMyAdmin下载、安装和使用入门教程
2007/05/31 PHP
关于php mvc开发模式的感想
2011/06/28 PHP
php使用ZipArchive函数实现文件的压缩与解压缩
2015/10/27 PHP
php求今天、昨天、明天时间戳的简单实现方法
2016/07/28 PHP
微信公众平台开发教程④ ThinkPHP框架下微信支付功能图文详解
2019/04/10 PHP
JS 统计时间
2021/03/09 Javascript
js阻止冒泡及jquery阻止事件冒泡示例介绍
2013/11/19 Javascript
Javascript学习笔记之 对象篇(一) : 对象的使用和属性
2014/06/24 Javascript
Javascript基础教程之关键字和保留字汇总
2015/01/18 Javascript
javascript加减乘除的简单实例
2016/07/12 Javascript
浅谈js函数中的实例对象、类对象、局部变量(局部函数)
2016/11/20 Javascript
JS根据生日月份和日期计算星座的简单实现方法
2016/11/24 Javascript
详解如何使用webpack+es6开发angular1.x
2017/08/16 Javascript
微信小程序之选项卡的实现方法
2017/09/29 Javascript
thinkjs 文件上传功能实例代码
2017/11/08 Javascript
详解Vuex管理登录状态
2017/11/13 Javascript
MockJs结合json-server模拟后台数据
2020/08/26 Javascript
javascript面向对象创建对象的方式小结
2019/07/29 Javascript
ReactRouter的实现方法
2021/01/25 Javascript
Python 快速实现CLI 应用程序的脚手架
2017/12/05 Python
用Python编写一个高效的端口扫描器的方法
2018/12/20 Python
python后端接收前端回传的文件方法
2019/01/02 Python
python列表,字典,元组简单用法示例
2019/07/11 Python
Python爬取新型冠状病毒“谣言”新闻进行数据分析
2020/02/16 Python
Pytorch高阶OP操作where,gather原理
2020/04/30 Python
Python json格式化打印实现过程解析
2020/07/21 Python
HTML5移动端手机网站开发流程
2016/04/25 HTML / CSS
享受加州生活方式的时尚舒适:XCVI
2018/07/09 全球购物
优秀乡村医生事迹材料
2014/05/28 职场文书
法院反腐倡廉心得体会
2014/09/09 职场文书
自愿离婚协议书范本
2014/09/13 职场文书
医疗纠纷调解协议书
2015/08/06 职场文书
Mybatis-plus在项目中的简单应用
2021/07/01 Java/Android
使用refresh_token实现无感刷新页面
2022/04/26 Javascript
mysql数据库实现设置字段长度
2022/06/10 MySQL