Redis学习笔记(四)-- 高并发分布式锁

Redis学习笔记(四)-- 高并发分布式锁

高并发分布式锁

在我们平时写代码过程中,会遇到很多高并发的场景,比如双十一的秒杀场景,本文就以此举例分析

Spring Boot整合Redis锁代码

1、引入依赖

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

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

        <dependency>
			<groupId>org.redisson</groupId>
			<artifactId>redisson</artifactId>
			<version>3.6.5</version>
		</dependency>
		
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
</dependency>

2、项目启动文件

package com.redisson;

import org.redisson.Redisson;
import org.redisson.config.Config;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class Application {

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

    @Bean
    public Redisson redisson() {
        // 此为单机模式
        Config config = new Config();
        config.useSingleServer().setAddress("redis://localhost:6379").setDatabase(0);
        return (Redisson) Redisson.create(config);
    }

}

3、主要分析文件代码

// 1.0版本
@RequestMapping("/deduct_stock")
    public String deductStock() {
		// 初始化redis的stock的值为30
        int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); // jedis.get("stock")
        if (stock > 0) {
            int realStock = stock - 1;
            stringRedisTemplate.opsForValue().set("stock", realStock + ""); // jedis.set(key,value)
            System.out.println("扣减成功,剩余库存:" + realStock);
        } else {
            System.out.println("扣减失败,库存不足");
        }
        return "end";
    }

1.0版本的代码,通过读取存贮在redis的值进行扣减,然后将值再次更新到redis中,但是针对多线程的场景下,就会有问题了,于是2.0版本来了

// 2.0版本 
@RequestMapping("/deduct_stock")
 public String deductStock() {

       synchronized (this){
           int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); // jedis.get("stock")
           if (stock > 0) {
               int realStock = stock - 1;
               stringRedisTemplate.opsForValue().set("stock", realStock + ""); // jedis.set(key,value)
               System.out.println("扣减成功,剩余库存:" + realStock);
           } else {
               System.out.println("扣减失败,库存不足");
           }
       }
        return "end";
    }

我们在生产环境下,可能应用程序不一定部署在一台机器上,可能有很多台机器,通过nginx来进行负载,在这种情形下,synchronized是jvm进程级别锁,在集群或者分布式场景下,就不起作用了,所以我们可以用redis分布式锁

// 3.0
@RequestMapping("/deduct_stock")
    public String deductStock() {
        String lockKey = "product_101";
        //jedis.setnx(k,v)
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "zhangsan"); 
        stringRedisTemplate.expire(lockKey, 10, TimeUnit.SECONDS);
        if (!result) {
            return "error_code";
        }
        try {
            //jedis.get("stock")
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
                int realStock = stock - 1;
                // jedis.set(key,value)
                stringRedisTemplate.opsForValue().set("stock", realStock + ""); 
                System.out.println("扣减成功,剩余库存:" + realStock);
            } else {
                System.out.println("扣减失败,库存不足");
            }
        } finally {
            stringRedisTemplate.delete(lockKey);
        }

        return "end";
    }

通过redis.setnx(k,v)并设置超时时间来实现分布式锁,但是在高并发场景下,可能存在a机器访问15s才会结束,当访问10s的时候,key已经过了超时时间但是a机器的进程没有结束,这个时候b机器也可以访问了,就存在a进程去删除b的lockKey。这个场景咋处理呢?

// 4.0
 @RequestMapping("/deduct_stock")
    public String deductStock() {
        String lockKey = "product_101";
        String clientId = UUID.randomUUID().toString();
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS);
        if (!result) {
            return "error_code";
        }
        try {

            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); // jedis.get("stock")
            if (stock > 0) {
                int realStock = stock - 1;
                stringRedisTemplate.opsForValue().set("stock", realStock + ""); // jedis.set(key,value)
                System.out.println("扣减成功,剩余库存:" + realStock);
            } else {
                System.out.println("扣减失败,库存不足");
            }
        } finally {
            if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) {
                stringRedisTemplate.delete(lockKey);
            }
        }

        return "end";
    }

其实已经有框架redisson帮我们实现了

// 5.0
  @RequestMapping("/deduct_stock")
  public String deductStock() {
        String lockKey = "product_101";
      
       /* 读写锁示例
        RReadWriteLock readWriteLock = redisson.getReadWriteLock(lockKey);
        RLock rLock = readWriteLock.readLock();
        RLock wLock = readWriteLock.writeLock();
        */

        RLock redissonLock = redisson.getLock(lockKey);
        try {
            /*rLock.lock();
            wLock.lock();*/
            redissonLock.lock();  //setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS);
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); // jedis.get("stock")
            if (stock > 0) {
                int realStock = stock - 1;
                stringRedisTemplate.opsForValue().set("stock", realStock + ""); // jedis.set(key,value)
                System.out.println("扣减成功,剩余库存:" + realStock);
            } else {
                System.out.println("扣减失败,库存不足");
            }
        } finally {
            /*rLock.unlock();
            wLock.unlock();*/
            redissonLock.unlock();
        }

        return "end";
    }

你可能感兴趣的:(redis,分布式锁,高并发)