首页 > 数据库 >SpringBoot+Redis+消息队列 技术的抢购方案【附有图文+示例代码】

SpringBoot+Redis+消息队列 技术的抢购方案【附有图文+示例代码】

时间:2025-01-16 14:57:38浏览次数:3  
标签:SpringBoot 示例 Redis springframework key org import public String

文章目录

SpringBoot+Redis+消息队列 技术的抢购方案

提前部署好Redis和RabbitMQ

RabbitMQ部署参考:https://cloud.tencent.com/developer/article/2303666

13.1简单抢购

创建springboot项目,导入redis相关依赖

     <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>
              spring-boot-starter-amqp
            </artifactId>
      </dependency>

        <!--redis依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>
              spring-boot-starter-data-redis
            </artifactId>
        </dependency>
server:
  port: 80
spring:
  redis:
    # 连接超时 毫秒
    connectTimeout: 1800
    # 连接超时时间
    timeout: 60s
    host: 127.0.0.1
    port: 6379
    database: 1
    password: 123456
    jedis:
      pool:
        # 连接池中的最小空闲连接
        min-idle: 0
        # 连接池中的最大空闲连接
        max-idle: 8
        # 连接池的最大数据库连接数
        max-active: 8
        # #连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: 3600ms

首先模拟商品初始化信息

@SpringBootApplication
public class HzQianggouApplication {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

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

    @Bean
    public void goodsInfo() {
        //初始化抢购商品信息,假设有100个商品
        stringRedisTemplate.opsForValue()
          .set("goods:1001","100");
    }

}

package com.hz.controller;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/qg")
public class QiangGouController01 {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    Long userId = 110L;//用户id
    Long goodsId = 1001L;//商品id

    @RequestMapping("/qg01")
    public String qianggou(){


        //判断是否购买过次商品
        if(stringRedisTemplate.opsForValue()
           .get("user:"+userId+":goods:"+goodsId) != null){
            return "抢购失败,您已经购买过次商品";
        }
        //判断库存是否足够
        Integer kc = Integer.parseInt(stringRedisTemplate.opsForValue().get("goods:" + goodsId));
        if (kc <= 0){
            return "抢购失败,商品已售罄";
        }
        kc--;
        stringRedisTemplate.opsForValue().set("goods:"+goodsId,kc.toString());

        //添加抢购成功标识 1:抢购成功 0:抢购失败
        stringRedisTemplate.opsForValue().set("user:"+userId+":goods:"+goodsId,"1");

        return "抢购成功";
    }


}

在这里插入图片描述

在这里插入图片描述

13.2 模拟高并下发抢购

jmeter工具使用

首先安装jmeter工具,模拟高并发

解压即能用,找到bin下的jmeter.bat双击运行。

在这里插入图片描述

设置,放大

在这里插入图片描述

模拟1000个人每秒点击3次

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

实现

package com.hz.controller;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/qg02")
public class QiangGouController02 {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    Long userId = 110L;//用户id
    Long goodsId = 1001L;//商品id

    @RequestMapping("/qginfo01")
    public String qianggou(){

        userId ++;//模拟多个用户进来抢购

        //判断是否购买过次商品
        if(stringRedisTemplate.opsForValue()
           .get("user:"+userId+":goods:"+goodsId) != null){
            return "抢购失败,您已经购买过次商品";
        }
        //判断库存是否足够
        Integer kc = Integer.parseInt(stringRedisTemplate.opsForValue().get("goods:" + goodsId));
        if (kc <= 0){
            return "抢购失败,商品已售罄";
        }
        kc--;
        stringRedisTemplate.opsForValue().set("goods:"+goodsId,kc.toString());

        //添加抢购成功标识 1:抢购成功 0:抢购失败
        stringRedisTemplate.opsForValue().set("user:"+userId+":goods:"+goodsId,"1");

        return "抢购成功";
    }


}

在这里插入图片描述

在这里插入图片描述

结果显示496个用户抢到,但是商品只有100个,出现数据不安全问题。解决方案如下。

13.3数据安全问题

加锁synchronized

在这里插入图片描述

再次运行,测试

在这里插入图片描述

在这里插入图片描述

数据正确。但是执行速度比较慢。

分布式锁

如果是分布式系统的话,synchronized无法解决。

在这里插入图片描述

【RedisUtils】

package com.hz.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

@Component
public class RedisUtil {

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    @Autowired
    RedisTemplate<Object, Object> redisTemplate;

    @Resource(name = "stringRedisTemplate")
    ValueOperations<String, String> valOpsStr;

    @Resource(name = "redisTemplate")
    ValueOperations<Object, Object> valOpsObj;

    /**
     * 根据指定key获取String
     * @param key
     * @return
     */
    public String getStr(String key){
        return valOpsStr.get(key);
    }



    /**
     * 设置Increment缓存
     * @param key
     */
    public long getIncrement(String key){

        return valOpsStr.increment(key);
    }
    /**
     * 设置Increment缓存
     * @param key
     * @param val
     */
    public long setIncrement(String key, long val){

       return valOpsStr.increment(key,val);
    }

    /**
     * 设置Str缓存
     * @param key
     * @param val
     */
    public void setStr(String key, String val){
        valOpsStr.set(key,val);
    }

    /***
     * 设置Str缓存
     * @param key
     * @param val
     * @param expire 超时时间
     */
    public void setStr(String key, String val,Long expire){
        valOpsStr.set(key,val,expire, TimeUnit.MINUTES);
    }

    /**
     * 删除指定key
     * @param key
     */
    public void del(String key){
        stringRedisTemplate.delete(key);
    }

    /**
     * 根据指定o获取Object
     * @param o
     * @return
     */
    public Object getObj(Object o){
        return valOpsObj.get(o);
    }

    /**
     * 设置obj缓存
     * @param o1
     * @param o2
     */
    public void setObj(Object o1, Object o2){
        valOpsObj.set(o1, o2);
    }

    /**
     * 删除Obj缓存
     * @param o
     */
    public void delObj(Object o){
        redisTemplate.delete(o);
    }
    /***
     * 加锁的方法
     * @return
     */
    public boolean lock(String key,Long expire){
        // expire:时间
       RedisConnection redisConnection=redisTemplate.getConnectionFactory().getConnection();
       //设置序列化方法
       redisTemplate.setKeySerializer(new StringRedisSerializer());
       redisTemplate.setValueSerializer(new StringRedisSerializer());

       if(redisConnection.setNX(key.getBytes(),new byte[]{1})){
           redisTemplate.expire(key,expire,TimeUnit.SECONDS);
           redisConnection.close();
           return true;
       }else{
           redisConnection.close();
           return false;
       }
    }
    /***
     * 解锁的方法
     * @param key
     */
    public void unLock(String key){
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        redisTemplate.delete(key);
    }
}

package com.hz.controller;


import com.hz.utils.RedisUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/qg03")
public class QiangGouController03 {

    // @Autowired
    // private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private RedisUtil redisUtil;


    Long userId = 110L;//用户id
    Long goodsId = 1001L;//商品id

    @RequestMapping("/qginfo01")
    public  String qianggou(){

        userId ++;//模拟多个用户进来抢购

        while (!redisUtil.lock("look_",3L)){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        String msg= "";
        //判断是否购买过次商品
        if(redisUtil.getStr("user:"+userId+":goods:"+goodsId) == null){
            //判断库存是否足够
            Integer kc = Integer.parseInt(redisUtil.getStr("goods:" + goodsId));
            if (kc <= 0){
                msg =  "抢购失败,商品已售罄";
            }else{
                kc--;
                redisUtil.setStr("goods:"+goodsId,kc.toString());

                //添加抢购成功标识 1:抢购成功 0:抢购失败
                redisUtil.setStr("user:"+userId+":goods:"+goodsId,"1");
                msg = "抢购成功";
            }

        }else {
            msg =  "抢购失败,您已经购买过次商品";
        }

        // 释放锁
        redisUtil.unLock("look_");
        return msg;
    }


}

测试

在这里插入图片描述

在这里插入图片描述

13.4 消息队列完成抢购

为了方便,直接在启动类中初始化商品信息

@SpringBootApplication
public class HzQinggou02Application {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

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

    @Bean
    public void info(){
        //初始化抢购商品
        stringRedisTemplate.opsForValue()
          .increment("goods:1001",100);
    }

}

【yml配置文件】

server:
  port: 80

spring:
  redis:
    # 连接超时 毫秒
    connectTimeout: 1800
    # 连接超时时间
    timeout: 60s
    host: 127.0.0.1
    port: 6379
    database: 1
    password: 123456
    jedis:
      pool:
        # 连接池中的最小空闲连接
        min-idle: 0
        # 连接池中的最大空闲连接
        max-idle: 8
        # 连接池的最大数据库连接数
        max-active: 8
        # #连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: 3600ms
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest
    listener:
      simple:
        acknowledge-mode: manual #手动反馈
        prefetch: 1 #限流 (消费者每次从队列中获得的消息数量)
        concurrency: 1 #消费者数据量
        max-concurrency: 1 #消费者最大数据量
        auto-startup: true #是否自动启动

【实体类】

@Data
@ToString
public class UserGoods implements Serializable {
    private Integer userId;
    private String goodsId;
}

【redis工具类】

package com.hz.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

@Component
public class RedisUtil {

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    @Autowired
    RedisTemplate<Object, Object> redisTemplate;

    @Resource(name = "stringRedisTemplate")
    ValueOperations<String, String> valOpsStr;

    @Resource(name = "redisTemplate")
    ValueOperations<Object, Object> valOpsObj;

    /**
     * 根据指定key获取String
     * @param key
     * @return
     */
    public String getStr(String key){
        return valOpsStr.get(key);
    }
    public boolean isKey(String key, String val){

        return stringRedisTemplate.opsForHash().hasKey(key,val);
    }

//    public Long get(String key){
//        return redisTemplate.opsForSet().members(key);
//    }

    public void set(String key,String val,String val1){
        stringRedisTemplate.opsForHash().put(key,val,val);
    }

    /**
     * 设置Increment缓存
     * @param key
     */
    public long getIncrement(String key){

        return valOpsStr.increment(key);
    }
    /**
     * 设置Increment缓存
     * @param key
     * @param val
     */
    public long setIncrement(String key, long val){

       return valOpsStr.increment(key,val);
    }

    /**
     * 设置Str缓存
     * @param key
     * @param val
     */
    public void setStr(String key, String val){
        valOpsStr.set(key,val);
    }

    /***
     * 设置Str缓存
     * @param key
     * @param val
     * @param expire 超时时间
     */
    public void setStr(String key, String val,Long expire){
        valOpsStr.set(key,val,expire, TimeUnit.MINUTES);
    }

    /**
     * 删除指定key
     * @param key
     */
    public void del(String key){
        stringRedisTemplate.delete(key);
    }

    /**
     * 根据指定o获取Object
     * @param o
     * @return
     */
    public Object getObj(Object o){
        return valOpsObj.get(o);
    }

    /**
     * 设置obj缓存
     * @param o1
     * @param o2
     */
    public void setObj(Object o1, Object o2){
        valOpsObj.set(o1, o2);
    }

    /**
     * 删除Obj缓存
     * @param o
     */
    public void delObj(Object o){
        redisTemplate.delete(o);
    }
    /***
     * 加锁的方法
     * @return
     */
    public boolean lock(String key,Long expire){
       RedisConnection redisConnection=redisTemplate.getConnectionFactory().getConnection();
       //设置序列化方法
       redisTemplate.setKeySerializer(new StringRedisSerializer());
       redisTemplate.setValueSerializer(new StringRedisSerializer());

       if(redisConnection.setNX(key.getBytes(),new byte[]{1})){
           redisTemplate.expire(key,expire,TimeUnit.SECONDS);
           redisConnection.close();
           return true;
       }else{
           redisConnection.close();
           return false;
       }
    }
    /***
     * 解锁的方法
     * @param key
     */
    public void unLock(String key){
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        redisTemplate.delete(key);
    }
}

【队列配置类】

package com.hz.config;

import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class RabbitConfig {
    public static final String QUEUE_NAME = "test_qg";
    /*
     * 队列配置
     *  // durable  持久化队列
        //QueueBuilder.Overflow.rejectPublish 枚举类型  当队列满时,多余的消息直接拒绝接收,多余的消息被丢弃
     */
    @Bean
    public Queue queue() {
        return  QueueBuilder.durable(QUEUE_NAME)
                .maxLength(50)
                .overflow(QueueBuilder
                          .Overflow.rejectPublish)
                .build();
    }


}

【controller】

package com.hz.controller;

import com.hz.config.RabbitConfig;
import com.hz.pojo.UserGoods;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
@RequestMapping("/rabbit")
public class RabbitController {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    Integer userId = 110;//用户ID
    String goodsId = "1001";//要抢购的商品ID


    @GetMapping("/qg")
    public String test(){
        userId++;
        UserGoods userGoods = new UserGoods();
        userGoods.setUserId(userId);
        userGoods.setGoodsId(goodsId);
        //发送消息到队列
        rabbitTemplate.convertAndSend(RabbitConfig.QUEUE_NAME,userGoods);
        return "用户"+userId+"正在抢购商品"+goodsId;
    }
//    @GetMapping("/refund")
//    public String refund(Integer userId,String goodsId){
//        HashOperations hashOperations = stringRedisTemplate.opsForHash();
//        if(hashOperations.hasKey(goodsId+":success",userId.toString()))
//        {
//            return "抢购成功!";
//        }else if(hashOperations.hasKey(goodsId+":error",userId.toString())){
//            return "抢购失败!";
//        }else {
//            return "等待";
//        }
//    }



}

【队列监听器RabbitQMListener】

package com.hz.controller;

import com.hz.config.RabbitConfig;
import com.hz.pojo.UserGoods;
import com.hz.utils.RedisUtil;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.utils.SerializationUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;

import java.io.IOException;


@Component
public class RabbitQMListener {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @RabbitListener(queues = RabbitConfig.QUEUE_NAME)
    public void onMessage(Channel channel, Message message) {

        // 用于操作Redis中的Hash类型数据结构。
        HashOperations hashOperations =  stringRedisTemplate
          .opsForHash();
        // 用于操作Redis中的String类型数据结构。
       ValueOperations valueOperations = stringRedisTemplate
          .opsForValue();

        try {
            //将字节数组转换为对象
            UserGoods userGoods =
              (UserGoods) SerializationUtils
              .deserialize(message.getBody());
            //判断是否还有库存  并且已经购买过
            if(!(hashOperations.hasKey(userGoods.getGoodsId()+":success",userGoods.getUserId().toString()))
                    &&Integer.valueOf(valueOperations.get("goods:"+userGoods.getGoodsId()).toString())>0){
                //减库存
                valueOperations.increment("goods:"+userGoods.getGoodsId(),-1);
                //添加抢购标识  0:抢购成功 1:抢购失败
                hashOperations.put(userGoods.getGoodsId()+":success",userGoods.getUserId().toString(),"0");//抢到了


            }else{
                hashOperations.put(userGoods.getGoodsId()+":error",userGoods.getUserId().toString(),"1");
            }
            //手动反馈
            channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
        }catch (Exception e){
            e.printStackTrace();
            try {
                //放回队列
                channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,false);
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }
        }


    }


}

【测试】

启动MQ

在这里插入图片描述

启动Redis

在这里插入图片描述

启动SpringBoot程序后输入网址

http://localhost:80/rabbit/qg

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

标签:SpringBoot,示例,Redis,springframework,key,org,import,public,String
From: https://blog.csdn.net/weixin_54555405/article/details/145183150

相关文章

  • 如何开启主机上的MongoDB和Redis服务?
    关于您提到的如何开启主机上的MongoDB和Redis服务的问题,我们将为您提供详细的解决方案。MongoDB和Redis是两种广泛使用的NoSQL数据库,分别用于存储结构化数据和缓存数据。确保这些服务正常运行对于提升网站性能至关重要。以下是针对这两种服务的具体操作步骤。开启MongoDB服务......
  • 计算机毕业设计Springboot冰城商户管理系统 冰城商家运营管理系统基于SpringBoot的开
    计算机毕业设计Springboot冰城商户管理系统t8n7qzwr(配套有源码程序mysql数据库论文)本套源码可以先看具体功能演示视频领取,文末有联xi可分享随着数字化浪潮的席卷,传统商业模式正面临前所未有的变革。在冰城这样的特色区域,商户们在激烈的市场竞争中寻求突破,渴望借助科技力......
  • 计算机毕业设计Springboot便利店连锁经营管理系统 Spring Boot 便利店连锁管理系统 基
    计算机毕业设计Springboot便利店连锁经营管理系统95z197da(配套有源码程序mysql数据库论文)本套源码可以先看具体功能演示视频领取,文末有联xi可分享博客开头内容随着城市化进程的加快,便利店作为提供日常便捷服务的重要商业形态,在城市生活中扮演着越来越重要的角色。为了满......
  • 计算机毕业设计Springboot“茶文化”网站 “茶韵在线”Spring Boot 网站开发 Spring B
    计算机毕业设计Springboot“茶文化”网站2p9kxpza(配套有源码程序mysql数据库论文)本套源码可以先看具体功能演示视频领取,文末有联xi可分享随着茶文化在全球范围内的影响力日益扩大,越来越多的人渴望深入了解这一古老而深邃的文化。为了满足这一需求,我们开发了“茶文化”网......
  • Redis安装配置与使用
    Redis是什么Redis(RemoteDictionaryServer)是一个开源的内存数据库,遵守BSD协议,它提供了一个高性能的键值(key-value)存储系统,常用于缓存、消息队列、会话存储等应用场景。它的优点很多,Redis与其他key-value存储系统的主要区别在于其提供了丰富的数据类型、高性能的读写能力、......
  • 计算机毕业设计Springboot毕业学员志愿填报系统设计与实现 基于SpringBoot的毕业生志
    计算机毕业设计Springboot毕业学员志愿填报系统设计与实现f710g1r7(配套有源码程序mysql数据库论文)本套源码可以先看具体功能演示视频领取,文末有联xi可分享随着互联网技术的飞速发展,传统的毕业学员志愿填报方式已逐渐无法满足现代社会的需求。纸质填报不仅效率低下,而且容......
  • 计算机毕业设计Springboot毕业生线上招聘平台 基于Springboot的大学生就业招聘在线平
    计算机毕业设计Springboot毕业生线上招聘平台bvc1qz7p(配套有源码程序mysql数据库论文)本套源码可以先看具体功能演示视频领取,文末有联xi可分享随着互联网技术的飞速发展,传统的招聘方式已经无法满足现代求职者和企业的需求。越来越多的毕业生和企业开始寻求更高效、便捷的......
  • 计算机毕业设计Springboot“飞卷”窗帘报价管理系统的设计与实现 基于Springboot的“
    计算机毕业设计Springboot“飞卷”窗帘报价管理系统的设计与实现q62s9t2z(配套有源码程序mysql数据库论文)本套源码可以先看具体功能演示视频领取,文末有联xi可分享随着窗帘行业的不断发展,市场竞争日益激烈,传统的窗帘报价方式已难以满足现代企业对效率和准确性的要求。为了......
  • 【附源码】JAVA大学生竞赛管理系统源码+SpringBoot+VUE+前后端分离
    学弟,学妹好,我是爱学习的学姐,今天带来一款优秀的项目:大学生竞赛管理系统 。本文介绍了系统功能与部署安装步骤,如果您有任何问题,也请联系学姐,偶现在是经验丰富的程序员!一.系统演示系统测试截图   系统视频演示 https://githubs.xyz/show/343.mp4 二.系统概述 ......
  • 【开源免费】基于Vue和SpringBoot的欢迪迈手机商城(附论文)
    本文项目编号T141,文末自助获取源码\color{red}{T141,文末自助获取源码}......