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不要关,等几秒就会弹出这个程序

点击新建

新建线程组

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值