Redis 秒杀整合Spring Boot 2.x实现例子

编程教程 > Java > Spring (32798) 2024-11-26 14:39:04

前言

继续上一篇Spring Boot Redis 秒杀实现 的一个修改版本,主要实现用ab工具进行网页正式访问的一个版本,其主要目的还是介绍Redis实现秒杀活动的一种方式。

Redis 秒杀活动项目结构图

项目结果图

与上一篇文章中的区别在于多了一个GoodService服务。该服务主要提供秒杀业务。

Redis秒杀活动业务层

$title(GoodsService.java)
package com.xqlee.demo.demoredisseckill.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Transaction;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;

/**
 * 商品服务
 */
@Service
public class GoodsService {

    @Autowired
    JedisPool jedisPool;

    public static String productKey="GOODS_001";//某产品的ID
    int goodsStock=10;//某产品用于秒杀的库存
    String successKey="Success_User_List";//成功秒杀用户的集合


    /**
     * 初始化一些默认数据(正常情况这些数据来源于数据库)
     */
    @PostConstruct
    public void init(){
        Jedis jedis=jedisPool.getResource();
        jedis.set(productKey,String.valueOf(goodsStock));//设置产品默认库存数量
        while (jedis.lpop(successKey)!=null){

        }//清空秒杀成功人用户列表
        //end
       new Thread(()->{
           long size=jedis.llen(successKey);
           while (true){
               if (size==goodsStock){
                   break;
               }else{
                   size=jedis.llen(successKey);
               }
           }
           List<String> successUsers=new ArrayList<>();
           String  user=jedis.lpop(successKey);
           while (user!=null){
               successUsers.add(user);
               user=jedis.lpop(successKey);
           }
           System.out.println("活动结束>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>活动结束");
           System.out.println("抢购名单:"+successUsers);
           //可以在名单拿到后生成订单等其他业务操作。
           String num=jedis.get(productKey);
           System.out.println("剩余库存:"+num);
       }).start();
    }

    /**
     * 获取库存
     * @param productKey
     * @return
     */
    public int getStock(String productKey){
        try (Jedis jedis=jedisPool.getResource();){
            String val=jedis.get(productKey);
            if (val!=null){
                return Integer.valueOf(val);
            }
            return  -1;
        }
    }

    /**
     * 秒杀商品
     *
     * @param productKey
     * @return
     */
    public boolean seckill(String productKey, String userName) {

        if (getStock(productKey)<=0){
            System.out.println("商品已抢购完毕>>>>>>>>>>>>>>>>>>>>>>>>>>欢迎下次再来。");
            return false;
        }
        try(Jedis jedis = jedisPool.getResource();) {
            jedis.watch(productKey);
            String val = jedis.get(productKey);
            int valInt = Integer.valueOf(val);
            if (valInt >= 1) {
                Transaction tx = jedis.multi();
                tx.incrBy(productKey, -1);//原子操作
                List<Object> list = tx.exec();
                if (list == null || list.isEmpty()) {
                    //System.out.println("用户:" + userName + " 抢购失败。");
                    this.seckill(productKey, userName);//再抢
                } else {
                    System.out.println("用户:" + userName + " 抢购成功!!!");
//                    jedis.setnx(productKey,)
                    jedis.rpush(successKey, userName);//成功用户添加入队列
                    //处理成功后的业务逻辑
                    return true;
                }
            } else {
                System.out.println("商品已抢购完毕>>>>>>>>>>>>>>>>>>>>>>>>>>欢迎下次再来。");
                return false;
            }
            return false;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
}
主要模拟环境与之前的文章一样。作为秒杀模拟场景。

其他相关文件清单:
pom.xml依赖
$title(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.1.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.xqlee.demo</groupId>
    <artifactId>demo-redis-seckill</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo-redis-seckill</name>
    <description>Redis 实现产品秒杀</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>
        <!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.0.1</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

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

</project>


redis配置
$title(JedisConfig.java)
package com.xqlee.demo.demoredisseckill;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import java.util.HashSet;
import java.util.Set;

@Configuration
public class JedisConfig {
    /***
     * 单机连接池
     * @return
     */
    @Bean
    public JedisPool jedisPool() {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        // 设置配置
        jedisPoolConfig.setMaxTotal(2048);
        jedisPoolConfig.setMaxIdle(400);
        jedisPoolConfig.setMaxWaitMillis(100);
        jedisPoolConfig.setTestOnBorrow(false);//jedis 第一次启动时,会报错
        jedisPoolConfig.setTestOnReturn(true);
        JedisPool pool = new JedisPool(jedisPoolConfig, "127.0.0.1", 6379);
        return pool;
    }

    /***
     * redis集群用(这里暂时没用)
     * @return
     */
//    @Bean
    public JedisCluster jedisCluster() {
        //创建jedisCluster对象,有一个参数 nodes是Set类型,Set包含若干个HostAndPort对象
        Set<HostAndPort> nodes = new HashSet<>();
        nodes.add(new HostAndPort("127.0.0.1", 7001));
        nodes.add(new HostAndPort("127.0.0.1", 7002));
        nodes.add(new HostAndPort("127.0.0.1", 7003));
        nodes.add(new HostAndPort("127.0.0.1", 7004));
        nodes.add(new HostAndPort("127.0.0.1", 7005));
        nodes.add(new HostAndPort("127.0.0.1", 7006));
        JedisCluster jedisCluster = new JedisCluster(nodes);
        //使用jedisCluster操作redis
//        jedisCluster.set("test", "my forst jedis");
//        String str = jedisCluster.get("test");
//        System.out.println(str);
//        //关闭连接池
//        jedisCluster.close();
        //注意由于集群式单例,不要再其他地方关闭连接池!!!!由系统关闭时统一关闭。
        return jedisCluster;
    }
}
web 接口
$title(SecKillController.java)
package com.xqlee.demo.demoredisseckill.controller;

import com.xqlee.demo.demoredisseckill.service.GoodsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.UUID;

/**
 * 秒杀接口
 */
@RestController
public class SecKillController {
    @Autowired
    GoodsService goodsService;

    @GetMapping("/seckill")
    public Object seckill() {
        //创建随机用户名
        String userName="用户+"+ UUID.randomUUID().toString().replace("-","").toUpperCase();
        boolean suc=goodsService.seckill(GoodsService.productKey,userName);
        return suc?"Succes":"Fail";
    }
}

Redis 秒杀活动测试

这里的测试主要使用ab工具进行测试。

首先启动秒杀应用(注意启动一次只能测一次。再次测试请重启服务,例子简单就这么处理的。哈哈)

ab测试命令:
ab -c 1000 -n 3000 http://localhost:8080/seckill
执行结果:
ab测试结果

通过测试的访问来看,错误请求有2994.好吧我目前也不知道啥情况。反正后台的输出是正常的。来看看后台的日志吧
 
2019-07-10 11:49:10.592  INFO 2656 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 9 ms
用户:用户+28C21F33CBD3452EB5C67B14A3939356 抢购成功!!!
用户:用户+126334102697433C8BB5B5D6E4061E38 抢购成功!!!
用户:用户+9ED7D0B472E9490E9C780B61B881E41B 抢购成功!!!
用户:用户+5AD862F2A389473AB2B4FB5EDC187617 抢购成功!!!
用户:用户+A264C1B9CB69498D8402B2E6C0B47589 抢购成功!!!
用户:用户+FC3D45232C73446E8032FDC36B0B8430 抢购成功!!!
用户:用户+D0F11B01EAB24CB385C23580CFC4E671 抢购成功!!!
用户:用户+73FD6DAED8564FEF8315CC4D6181ED77 抢购成功!!!
用户:用户+6967C042ADCA4FC9B42B70A51EE10752 抢购成功!!!
用户:用户+BBB5FF4878844B79BD11DC8C15EA9500 抢购成功!!!
商品已抢购完毕>>>>>>>>>>>>>>>>>>>>>>>>>>欢迎下次再来。
商品已抢购完毕>>>>>>>>>>>>>>>>>>>>>>>>>>欢迎下次再来。
商品已抢购完毕>>>>>>>>>>>>>>>>>>>>>>>>>>欢迎下次再来。
商品已抢购完毕>>>>>>>>>>>>>>>>>>>>>>>>>>欢迎下次再来。
商品已抢购完毕>>>>>>>>>>>>>>>>>>>>>>>>>>欢迎下次再来。
活动结束>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>活动结束
抢购名单:[用户+28C21F33CBD3452EB5C67B14A3939356, 用户+126334102697433C8BB5B5D6E4061E38, 用户+9ED7D0B472E9490E9C780B61B881E41B, 用户+5AD862F2A389473AB2B4FB5EDC187617, 用户+A264C1B9CB69498D8402B2E6C0B47589, 用户+FC3D45232C73446E8032FDC36B0B8430, 用户+D0F11B01EAB24CB385C23580CFC4E671, 用户+73FD6DAED8564FEF8315CC4D6181ED77, 用户+6967C042ADCA4FC9B42B70A51EE10752, 用户+BBB5FF4878844B79BD11DC8C15EA9500]
剩余库存:0
商品已抢购完毕>>>>>>>>>>>>>>>>>>>>>>>>>>欢迎下次再来。
商品已抢购完毕>>>>>>>>>>>>>>>>>>>>>>>>>>欢迎下次再来。
商品已抢购完毕>>>>>>>>>>>>>>>>>>>>>>>>>>欢迎下次再来。

细数了下获奖名单还是没错的。哈哈

谁知道ab为啥那么多错误请求?评论告诉我一下谢谢。
 

评论
User Image
提示:请评论与当前内容相关的回复,广告、推广或无关内容将被删除。

相关文章
前言继续上一篇Spring Boot Redis 秒杀实现 的一个修改版本,主要实现用ab工具进行网页正式访问的一个版本,其主要目的还是介绍Redis实现秒杀活动的一种方式
简述本文主要通过一个简单的例子模拟实现秒杀情景,其中主要使用Redis事物进行实现spring boot为提供方便的环境
Spring Boot 2.0 Redis整合,通过spring boot 2.0整合Redis作为spring缓存框架的实现。
spring boot 1.5整合redis实现spring的缓存框架,spring boot,redis
spring boot 入门之整合spring session实现session共享。一直以来Java编程中web项目中的session共享问题都是一个很难解决的问题。接下来将讲解通过sprin...
spring data redis设置缓存的过期时间,spring data redis更新缓存的过期时间
spring boot 2.x设置静态资源缓存时间
一、spring boot shiro 无状态token认证项目结构图​二、无状态spring boot shiro相关配置2.1shiro redis 缓存配置首先是实现shiro的cache...
Java编程之spring boot shiro redis整合基于角色和权限的安全管理,Java编程,spring boot,shiro,权限控制
Spring Boot 2.0 有哪些新特性_Spring Boot 2.0新功能,在本文中,我们将探讨为Spring Boot 2.0计划的一些更改和功能。我们还会描述这些变化如何帮助我们提高...
Spring Boot 2.0,Spring框架的Spring Boot 中的Spring Boot Actuator变化讲解。并且了解如何在Spring Boot 2.0中使用Actuator...
spring boot 2.0 security 5.0 整合,实现自定义表单登录。spring boot 2.0框架使用。
Spring Boot 2.1 新特性,已升级Spring 版本为5.1,支持servlet 4.0,支持Tomcat 9.0等等
Spring Boot 1.x升级到Spring Boot 2.0迁移指南
思路通过redis的有效期和切面来配合处理环境springboot2.7.xspring-boot-starter-webspring-boot-starter-aopjava1.8redis编...