解决Redis超卖问题

第一种:使用synchronized(只适用于单个tomcat)

@Autowired
private StringRedisTemplate stringRedisTemplate;
@GetMapping("/deduct")
    public String toDeduct(){
        synchronized (this) {
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
                Long stock1 = stringRedisTemplate.opsForValue().decrement("stock");
                System.out.println("扣减成功,剩余库存:" + stock1);
            }else{
                System.out.println("扣减失败,库存不足");
            }
            return "end";
        }
    }

第二种:使用SETNX命令(多台tomcat)

 @GetMapping("/deduct_stock")
    public String deductStock() throws InterruptedException {
        String lockKey = "lockKey";
        //设计唯一key值防止锁失效问题
        String clientId = UUID.randomUUID().toString();
        //设计锁超时时间,防止服务器宕机时锁没有释放掉(finally语句没有执行)
        Boolean result = stringRedisTemplate.opsForValue().
                setIfAbsent(lockKey, clientId, 10, TimeUnit.SECONDS);//jedis.setnx(key,value);
        //若键 key 已经存在, 则 SETNX 命令不做任何动作。result==false
        if (!result) {
            return "正在排队。。。";
        }
        try {
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
                Long realStock = stringRedisTemplate.opsForValue().decrement("stock");
                System.out.println("扣减成功,剩余库存:" + realStock);
            }else{
                System.out.println("扣减失败,库存不足");
            }
        } finally {
            //防止锁失效问题,在多线程的情况下,每个线程只释放自己创建的锁,线程之间互不干预。
            if(clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) {
                stringRedisTemplate.delete(lockKey);
            }
        }
        return "end";
    }

第三种:使用Redission框架(原理跟第二种一样)

1、配置config类

@Configuration
public class RedissonConfig {

    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.port}")
    private String port;

    @Value("${spring.redis.password}")
    private String password;

    @Bean
    public RedissonClient getRedisson(){
        Config config = new Config();
        config.useSingleServer().setAddress("redis://" + host + ":" + port).setPassword(password);
        //添加主从配置
//      config.useMasterSlaveServers().setMasterAddress("").setPassword("").addSlaveAddress(new String[]{"",""});
        return Redisson.create(config);
    }
}
@GetMapping("/deduct_stock1")
    public String deductStock1(){
        String lockKey = "lockKey";
        RLock redissonLock = redisson.getLock(lockKey);
        try {
            redissonLock.lock();
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
                Long realStock = stringRedisTemplate.opsForValue().decrement("stock");
                System.out.println("扣减成功,剩余库存:" + realStock);
            }else{
                System.out.println("扣减失败,库存不足");
            }
        } finally {
            redissonLock.unlock();
        }
        return "end";
    }

Redission原理:

Redission使用了Lua 脚本,实现原理跟原理跟第二种差不多。
当业务代码处理时间很长,像mysql慢查询等等,可以在try代码块中每隔10秒检查是否还持有锁,如果持有则延长锁的时间,防止还没执行完业务代码“lockKey”就到了失效时间。
解决Redis超卖问题_第1张图片


测试环境搭建

Redis命令大全

设置Redis密码
解决Redis超卖问题_第2张图片
pom:

		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.5.7</version>
        </dependency>

application.properties:

server.port=8090
# redis
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=888888
spring.redis.jedis.pool.max-active=500
spring.redis.jedis.pool.max-idle=1000
spring.redis.jedis.pool.max-wait=6000ms
spring.redis.jedis.pool.min-idle=4

Nginx负载均衡:

 upstream redislock{
	server 127.0.0.1:8080 weight = 1;
	server 127.0.0.1:8090 weight = 1;
    }
    server {
        listen       80;
        server_name  localhost;
   		location / {
		root html;
		index index.html index.htm;
		proxy_pass http://redislock;
        }
    }

Jmeter压力测试

解决Redis超卖问题_第3张图片
发送http请求:
解决Redis超卖问题_第4张图片
设置发送地址:
解决Redis超卖问题_第5张图片
表示在0秒内发送200个请求,处理完后再发送200 * 3次,总计200 * 4次请求:
解决Redis超卖问题_第6张图片
结果集:
解决Redis超卖问题_第7张图片

测试结果:
解决Redis超卖问题_第8张图片
解决Redis超卖问题_第9张图片
没有出现超卖问题

你可能感兴趣的:(Redis)