redis(三)redis锁的使用

减少库存

不做处理的代码

编写最普通的程序,不考虑任何并发的代码

需求

  1. 假定现在有10000库存,访问一次接口库存减1

  2. 当库存等于0,返回库存不足

需求分析

  1. 因为需要模拟接口访问,所以需要一个web项目,我这里使用的是一个可以运行起来的springboot项目

  2. 之前有讲过如何快速创建springboot项目,这里不重复赘述了。快速通道

  3. 只有让接口被调用10000次或者更多,才能获得较准确的结果

  4. ok,多余的没什么了,先编写代码试试

代码

新建controller->StringRedisTestController

  1. 建立一个static变量->nowCount,用来做全局值统一

  2. 访问一次,让nowCount减一,直到小于等于0为止

编写完成的代码如下:

package com.yxj.spring.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @version v1.0
 * @Copyright(c): 2020-2020
 * @FileName: RedisTestController.java
 * @Description:
 * @autho Admin
 * @date 2020/1/17 13:55
 */
@RestController
@RequestMapping("/test")
public class StringRedisTestController {
   
    private static Integer nowCount = 10000;
    /**
     * 减少,库存等等
     * @return
     */
    @GetMapping("/redis/reduce")
    public String lockTest(){
        if(nowCount<=0){
            System.out.println("------库存不足------");
            return "------库存不足------";
        }
        --nowCount;
        System.out.println("----库存剩余----"+nowCount);
        return String.valueOf(nowCount);
    }
}

测试

  1. 这里为了测试方便,特意下载了jmeter,我的版本号apache-jmeter-2.12

  2. 启动一个线程,访问10000

  1. 编写http请求,这里写controller的请求路径

  1. 点击启动按钮(第一个绿色启动按钮就可以)

测试结果(单线程)

因为代码较多,这里我只贴出最终结果

第一次测试

----库存剩余----4
----库存剩余----3
----库存剩余----2
----库存剩余----1
----库存剩余----0

结果正常

第二次测试(需要重启项目,重新给static变量赋值)

----库存剩余----4
----库存剩余----3
----库存剩余----2
----库存剩余----1
----库存剩余----0

结果正常

第三次测试(最后一次)

----库存剩余----4
----库存剩余----3
----库存剩余----2
----库存剩余----1
----库存剩余----0

结果正常

综上所诉,单线程访问代码,是没有什么问题

启动多个线程,进行访问

  1. 打开jemeter设置的线程组,将线程数更改为两个或者更多

  2. 修改循环次数,这里的循环次数指的是每个线程的循环次数,两个线程或者更多线程去访问,让他们的乘积等于10000

  3. 这里我设置2个线程,循环次数5000

测试结果(多线程)

测试(别忘记重启项目,重新获取static的值)

----库存剩余----2699
----库存剩余----2698
----库存剩余----2696
----库存剩余----2696

可以看到,数据已经错乱了,还有重复值

增加synchronized的代码

使用JAVA原生锁

修改代码,增加synchronized

package com.yxj.spring.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Date;

/**
 * @version v1.0
 * @Copyright(c): 2020-2020
 * @FileName: RedisTestController.java
 * @Description:
 * @autho Admin
 * @date 2020/1/17 13:55
 */
@RestController
@RequestMapping("/test")
public class StringRedisTestController {

    private static Integer nowCount = 10000;
    /**
     * 减少,库存等等
     * @return
     */
    @GetMapping("/redis/reduce")
    public synchronized String lockTest(){
        if(nowCount<=0){
            System.out.println("------库存不足------");
            return "------库存不足------";
        }
        --nowCount;
        System.out.println("----库存剩余----"+nowCount);
        return String.valueOf(nowCount);
    }
}

测试结果

----库存剩余----3
----库存剩余----2
----库存剩余----1
----库存剩余----0

可以看到,结果又正常了

使用redis锁的代码

synchronized确实可以解决库存不一致问题,但是,因为线上服务大部分都是多节点部署,两台或者两天以上的服务器,代码加synchronized肯定是不好使的

所以这里推荐使用redis

redis锁的优势

  1. redis锁基于redis实现

  2. 数据存储在内存,操作较快

  3. redis是单线程,安全

修改后的代码

  1. 使用setIfAbsent来判断key是否存在

  2. 使用expire来设置超时时间

  3. 使用delete来删除key

package com.yxj.spring.controller;

import com.yxj.spring.utils.RedisUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * @version v1.0
 * @Copyright(c): 2020-2020
 * @FileName: RedisTestController.java
 * @Description:
 * @autho Admin
 * @date 2020/1/17 13:55
 */
@RestController
@RequestMapping("/test")
public class StringRedisTestController {

    @Autowired
    private RedisUtils redisUtils;

    private static Integer nowCount = 10000;
    /**
     * 减少,库存等等
     * @return
     */
    @GetMapping("/redis/reduce")
    public String lockTest(){
        if(nowCount<=0){
            System.out.println("------库存不足------");
            return "------库存不足------";
        }
        if(redisUtils.setIfAbsent(String.valueOf(nowCount), "true")){
            redisUtils.expire(String.valueOf(nowCount),1,TimeUnit.SECONDS);
            Integer count = nowCount;
            --nowCount;
            redisUtils.delete(String.valueOf(count));
            System.out.println("----库存剩余----"+nowCount);
            return String.valueOf(nowCount);
        }else{
            System.out.println("系统繁忙");
            return "系统繁忙";
        }
    }
}

测试结果

两个线程再跑,第一个线程redis key还没有删除,第二个线程已经进入方法了,所以被拦截,无法执行

----库存剩余----4525
系统繁忙
----库存剩余----4524
系统繁忙
----库存剩余----4523
系统繁忙
----库存剩余----4522
系统繁忙
----库存剩余----4521
系统繁忙
----库存剩余----4520
系统繁忙
----库存剩余----4519
系统繁忙
----库存剩余----4518
系统繁忙
----库存剩余----4517
系统繁忙
----库存剩余----4516
系统繁忙
----库存剩余----4515
----库存剩余----4514
----库存剩余----4513
系统繁忙
----库存剩余----4512
系统繁忙
----库存剩余----4511
系统繁忙
----库存剩余----4510
----库存剩余----4509
----库存剩余----4508
----库存剩余----4507
----库存剩余----4506

总结

  1. 快过年了,提前祝大家新年快乐

  2. 因为快过年的原因,大脑无法正常运转,导致本文有点别扭(不是有点,我感觉别扭的很 o(╥﹏╥)o

  3. redis锁主要用作分布式的安全方面,可以通过最后的redis锁测试的结果看出,虽然库存没有减到0,但是安全得到了保障,每个数,只被用了一次

  4. 我这是第一次使用StringRedisTemplate(之前都是使用Jedis),没有踩过太大的坑,所以不敢保障这个redis使用一定正确,但是效果达到了

  5. 本文使用的redisUtils,点击下面的阅读原文,即可找着

推荐阅读

● redis(二)redis的运用与缓存穿透,雪崩的处理

● redis(一)数据类型与应用场景

● 数据结构-hash表

扫描二维码

加我好友

个人博客地址:

https://www.yangxj.top/

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值