当前位置: 首页 > article >正文

redis解决高并发抢购

对于前后端不分离的程序可以用悲观锁,对于前后端分离的程序可以用redis分布式锁

分布式锁 setnx key value,将key设置为value,当键不存在时,才能成功,若键存在,什么也不做,成功返回1,失败返回0 。 SETNX实际上就是SET IF NOT Exists的缩写

图中当redis分布式锁为1的时候就锁起来,其他线程在外面隔几秒休眠,当起来的时候发现锁为0然后进入项目再上锁,就这样循环

1.前后端不分离抢购:

加上悲观锁synchronized后100个库存有100个人抢到

如果不加的话会有好几百个人抢到

在启动类中初始化商品的key和value:
@SpringBootApplication
public class RedisQgApplication {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    //@Bean 可能是因为希望它在 Spring 容器初始化时执行,而不仅仅是一个普通的方法调用。
    // 如果不加 @Bean,这个方法就不会被 Spring 容器识别并调用。
    //初始化被抢购商品
    @Bean
    public void init(){
        stringRedisTemplate.opsForValue().set("goods:1001", "100");
    }

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

在controller里设置抢购程序:

思路:

  1. 判断是否购买过该商品
  2. 判断是否还有库存
  3. 库存减一
  4. 添加用户购买标识
@RestController
@RequestMapping("/index")
public class IndexController {
     int userId=100;
     int goodsId=1001;

     @Autowired
     private  StringRedisTemplate stringRedisTemplate;

    @GetMapping("/qg")
    public synchronized String qg(){
        userId++;
        //判断是否已经购买过该商品
        if (stringRedisTemplate.hasKey("user:"+userId+":"+goodsId)){
            //user在redis中有key就代表已经购买过了
            return "已经购买过该商品";
        }
        //判断是否还有库存
        Integer count = Integer.parseInt(stringRedisTemplate.opsForValue().get("goods:"+goodsId));
        if (count<=0){
            return "库存不足";
        }
        //减去库存
        count--;
        stringRedisTemplate.opsForValue().set("goods:"+goodsId, count.toString());
        //购买标识
        stringRedisTemplate.opsForValue().set("user:"+userId+":"+goodsId, "0");

        return "抢购成功";
    }

}

2.前后端分离抢购:

使用SETNX:分布式锁 setnx key value,将key设置为value,当键不存在时,才能成功,若键存在,什么也不做,成功返回1,失败返回0 。 SETNX实际上就是SET IF NOT Exists的缩写

controller:

思路:

  1. 先判断redis分布式锁上了没,上锁了就休眠
  2. 判断是否购买过该商品,买过的走
  3. 判断是否还有库存,没库存了走
  4. 库存减一
  5. 添加用户购买标识,购买完成走
  6. 购买成功后释放锁
@RestController
@RequestMapping("/index")
public class IndexController {
     int userId=100;
     int goodsId=1001;

     @Autowired
     private RedisUtil redisUtil;

    @GetMapping("/qg")
    public synchronized String qg(){
        userId++;
        //设置一个redis锁上锁时休眠
        while (redisUtil.lock("lock_:"+goodsId,10L)){
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //判断是否已经购买过该商品
        if (redisUtil.getStr("user:"+userId+":"+goodsId)!=null){
            //user在redis中有key就代表已经购买过了
            return "已经购买过该商品";
        }
        String msg;
        //判断是否还有库存
        Integer count = Integer.parseInt(redisUtil.getStr("goods:"+goodsId));
        if (count<=0){
            msg="库存不足";
        }else {
            //减去库存
            count--;
            redisUtil.setStr("goods:"+goodsId, count.toString());
            //购买标识
            redisUtil.setStr("user:"+userId+":"+goodsId, "0");

            //购买成功后释放锁
            redisUtil.unLock("lock_:"+goodsId);
            msg="抢购成功";
        }
        return msg;
    }
}

工具类:

这个工具类中setnx锁

@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){
       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);
    }
}

启动类:

同时启动类中的创建key也得替换成redisutil的


@SpringBootApplication
public class RedisQgApplication {

    @Autowired
    private RedisUtil redisUtil;

    //@Bean 可能是因为希望它在 Spring 容器初始化时执行,而不仅仅是一个普通的方法调用。
    // 如果不加 @Bean,这个方法就不会被 Spring 容器识别并调用。
    //初始化被抢购商品
    @Bean
    public void init(){
        redisUtil.setStr("goods:1001", "100");

    }

    public static void main(String[] args) {

        SpringApplication.run(RedisQgApplication.class, args);
    }

}

3.jmeter应用模拟高并发:

下载apache-jmeter后点开start.bat不要关,等几秒就会弹出这个程序

点击新建

新建线程组

添加查看结果树:这是用来查看结果的


http://www.kler.cn/a/468062.html

相关文章:

  • 在线机考|2024华为实习秋招春招编程题(最新)——第3题_个性化歌单推荐系统_300分(十一)
  • element-ui 2的级联选择器,回显已存储的子选项名称
  • 权限掩码umask
  • 谷歌浏览器的书签同步功能详解
  • Angular由一个bug说起之十三:Cross Origin
  • MarkDown 的 mermaid gantt(甘特图)、mermaid sequenceDiagram (流程图) 语法解析和应用
  • Go小技巧易错点100例(十九)
  • 2501d,jingo优化
  • Python如何实现与Colyseus的通信?
  • [React] 生态有哪些
  • 2024年, Milvus 社区的那些事
  • UCAS 24秋网络认证技术 CH10 SSL 复习
  • 蓝桥杯-Python
  • Colyseus 与 Cesium 集成:构建实时地理可视化应用
  • 声音是如何产生的
  • 语雀导入md文件图片丢失
  • Pytorch 三小时极限入门教程
  • [网络安全]DVWA之XSS(DOM)攻击姿势及解题详析合集
  • 111 - Lecture 6 - Objects and Classes
  • 《深度学习梯度消失问题:原因与解决之道》
  • 第9章 子程序与函数调用
  • 【LLM】概念解析 - Tensorflow/Transformer/PyTorch
  • MQTT学习笔记
  • php容器设计模式
  • 050_小驰私房菜_MTK Camera debug, data rate 、mipi_pixel_rate 确认
  • 基于图的去中心化社会推荐过滤器