【Redis分布式锁】学习笔记

1、 常见的分布式运用架构

nginx–> app1,app2 ->> redis

2、单体运用的代码

@RestController
public class IndexController {

    @Resource
    StringRedisTemplate stringRedisTemplate;

    /**
     * 秒杀
     * redis中有一个货物 stock,表示货物数量。运用是个分布式运用,去抢占这个货物,抢到了就stock--
     * @return
     */
    @RequestMapping("deduct_stock")
    public String deductStock() {

        int stock = Integer.parseInt(Objects.requireNonNull(stringRedisTemplate.opsForValue().get("stock")));
        if (stock > 0) {
            int realStock = stock - 1;
            stringRedisTemplate.opsForValue().set("stock", realStock + "");
            System.out.println("抢购成功,剩余库存:" + realStock);
        } else {
            System.out.println("抢购失败,库存不足");
        }
        return "end";
    }
}

上述代码单体运用就会有问题。加锁来解决。

@RestController
public class IndexController {

    private ReentrantLock lock = new ReentrantLock();
    @Resource
    StringRedisTemplate stringRedisTemplate;

    /**
     * 秒杀
     * redis中有一个货物 stock,表示货物数量。运用是个分布式运用,去抢占这个货物,抢到了就stock--
     *
     * @return
     */
    @RequestMapping("deduct_stock")
    public String deductStock() {
        lock.lock();
        try {
            int stock = Integer.parseInt(Objects.requireNonNull(stringRedisTemplate.opsForValue().get("stock")));
            if (stock > 0) {
                int realStock = stock - 1;
                stringRedisTemplate.opsForValue().set("stock", realStock + "");
                System.out.println("抢购成功,剩余库存:" + realStock);
            } else {
                System.out.println("抢购失败,库存不足");
            }
        } finally {
            lock.unlock();
        }
        return "end";
    }
}

单体运用时候没有问题,但是在分布式的时候,每个jvm进程之间就会并发的问题。

3、简单的分布式锁

在redis中设置一个字段,用redis的命令setnx设置,如果设置成功则继续执行,设置失败则直接返回。setnx表示如果不存在就设置set if not exit

@RestController
public class IndexController {

    @Resource
    StringRedisTemplate stringRedisTemplate;

    /**
     * 秒杀
     * redis中有一个货物 stock,表示货物数量。运用是个分布式运用,去抢占这个货物,抢到了就stock--
     *
     * @return
     */
    @RequestMapping("deduct_stock")
    public String deductStock() {
        String lockKey = "prod_001";
        String clientId = UUID.randomUUID().toString();
        try {
            // 1. 为了防止在抢的过程中抛出Throwable,需要最终删除key
            // 2. 为了防止在抢的过程中系统挂掉,需要设置key的有效时间
            // 3. 设置key有效时间的问题:致线程执行的时间大于有效时间,导致当前线程释放了别人的锁
            // 4. 为了防止3,key设置为每个线程的uuid。分布式uuid重复的几率很小
            Boolean lockSuccess = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 10, TimeUnit.SECONDS);
            if (Boolean.FALSE.equals(lockSuccess)) {
                return "当前系统繁忙,请稍后重试";
            }
            int stock = Integer.parseInt(Objects.requireNonNull(stringRedisTemplate.opsForValue().get("stock")));
            if (stock > 0) {
                int realStock = stock - 1;
                stringRedisTemplate.opsForValue().set("stock", realStock + "");
                System.out.println("抢购成功,剩余库存:" + realStock);
            } else {
                System.out.println("抢购失败,库存不足");
            }
        } finally {
            stringRedisTemplate.delete(lockKey);
        }
        return "end";
    }
}

4、使用redisson实现分布式锁

@RestController
public class DeductStockController {

    @Resource
    Redisson redisson;

    @Resource
    StringRedisTemplate stringRedisTemplate;
    @RequestMapping("deduct_stock_redisson")
    public String deductStock() {
        String lockKey = "prod_001";
        RLock lock = redisson.getLock(lockKey);
        try {
//            lock.lock();
            lock.lock(30, TimeUnit.SECONDS); // 锁的过期时间
            int stock = Integer.parseInt(Objects.requireNonNull(stringRedisTemplate.opsForValue().get("stock")));
            if (stock > 0) {
                int realStock = stock - 1;
                stringRedisTemplate.opsForValue().set("stock", realStock + "");
                System.out.println("抢购成功,剩余库存:" + realStock);
            } else {
                System.out.println("抢购失败,库存不足");
            }
        } finally {
            lock.unlock();
        }
        return "end";
    }
}

Redisson 配置

@Configuration
public class RedissonConfig {
    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private String port;

    @Bean
    public RedissonClient redisson() {
        Config config = new Config();
        config.useSingleServer().setAddress(host + ":" + port);
        return Redisson.create(config);
    }
}

5、redisson底层原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B0nUz8ZQ-1660458423409)(C:\Users\WadeHao\AppData\Roaming\Typora\typora-user-images\image-20220404220256151.png)]

6、上诉代码的问题

redis是单体的没有问题,redis集群(主从)可能会有问题。访问redis的时候,会优先访问空闲的redis服务器,然后空闲的redis可能还没有lockKey,所以就会导致问题。

7、 其他

maven相关依赖

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-webartifactId>
dependency>

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-testartifactId>
    <scope>testscope>
dependency>

<dependency>
    <groupId>org.redissongroupId>
    <artifactId>redissonartifactId>
    <version>3.7.4version>
dependency>

8、jmeter测试工具

8.1、启动

jmeter.bat

8.2、添加线程组(相当于一组用户)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dfzD9xAV-1660458423410)(C:\Users\WadeHao\AppData\Roaming\Typora\typora-user-images\image-20220405163035416.png)]

8.3、添加http请求

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mPCrFnXS-1660458423411)(C:\Users\WadeHao\AppData\Roaming\Typora\typora-user-images\image-20220405163100451.png)]

8.4、添加结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9b5sb3fW-1660458423411)(C:\Users\WadeHao\AppData\Roaming\Typora\typora-user-images\image-20220405163117887.png)]

8.4、参数

Number of Threads(users):线程个数

Ramp-up period(seconds):在时间内均匀的发请求

Loop count:测试次数

你可能感兴趣的:(redis,redis,分布式,学习)