Redision实现分布式锁案例分析

视频及代码地址:

  • https://www.bilibili.com/video/BV1d4411y79Y 【视频p6从48分钟后开始看】
  • https://www.bilibili.com/video/BV1U3411J7k7 【视频p1-p6】
  • https://github.com/yjiewei/Detail/tree/master/redis_lock 【代码下载地址】

测试代码:

(1)maven依赖

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

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

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

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

        <!-- redisson 分布式专用-->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.20.0</version>
        </dependency>

 (2)启动类配置

package com.study;

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 SpringBootRedisLockApplication {

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

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

(3)案例代码

package com.study.controller;

import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @ClassName RedisLockController
 * @Description
 * https://www.bilibili.com/video/BV1d4411y79Y 【视频p6从48分钟后开始看】
 * https://www.bilibili.com/video/BV1U3411J7k7 【视频p1-p6】
 * https://github.com/yjiewei/Detail/tree/master/redis_lock 【代码下载地址】
 * @Author Jiangnan Cui
 * @Date 2023/3/19 22:13
 * @Version 1.0
 */
@RestController
public class RedisLockController {
    @Resource
    private Redisson redisson;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 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("扣减失败,库存不足!");
     * }
     * 上面代码有什么问题?
     * 1.线程安全问题:多个线程同时访问可能出现错误,用synchronized可以解决,但是性能不好
     *   synchronized适用于单机模式,在高并发情况下还是有bug出现,会出现超卖,可以用jmeter压测。
     *
     * 2.设置redis锁解决分布式场景之后,超时时间设置10s合理吗?适合场景问题?如果10s中之内没有处理完,处理到一半呢?
     *   假设锁有效时间为10s
     *   a.当一个需要15s才能执行完的线程1到来后,其执行到第10s时,请求还没执行完,但其对应的锁已经到期删除了
     *   b.与此同时,当一个需要8s才能执行完的线程2到来后,其执行到第5s时,线程1又执行5s,删除锁,但线程1此时释放的是线程2的锁
     *   即:前一个请求释放了后一个的锁,如此反复,锁会一直失效。
     *   解决方法:你不是可能存在释放别人的锁的情况吗?那就设置识别号,识别到只能是自己的才能被释放
     *           这只是解决了释放别人的锁的问题,你自己没有执行完就已经超时的问题呢?
     *           答案:开启子线程定时器来延长超时时间咯,子线程每隔一段时间就去查看是否完成,没完成就加时,那这个一段时间要多长呢?
     *               三分之一过期时间,其他人的实践经验。
     *           所以:我们现在又要造轮子了吗?是否有其他人已经考虑过这个问题并做开源了呢?
     *           那肯定有啊,不然我写这个干吗。redisson,比jedis强大,专对分布式
     *
     * 3.redisson
     *   大概阐述一下这个锁的操作:
     *   当一个redisson线程过来获取到锁时,后台会有其他线程去检查是否还持有锁,
     *    还持有说明还没执行结束,就会继续延长锁的时间,大概10s去轮询。(三分之一)
     *    另外一个线程过来,如果没有获取锁成功,就会while自旋尝试加锁。
     *    clientId他在底层实现了。
     *
     *  3.1如果使用的是Redis主从架构呢,主节点宕了,从节点怎么处理?但这是锁还没有被同步过去,其他线程就过来访问了呢?
     *  3.2另外如何提升性能呢?
     *      - 商品库存分段存储,key不一样,每个段的数量越小性能不就越高嘛,而且锁定不同的key值
     *
     * @return
     */
    @RequestMapping("/deduct_stock")
    public String deductStock() {

        // 0.标识号:UUID会重复,但是重复的几率很小
        String clientID = UUID.randomUUID().toString();

        // 1.这个相当于一把锁,控制只能一个人来
        String lockKey = "product_001";
        // 获取锁对象
        RLock lock = redisson.getLock(lockKey); // 3.1
        long startMillis = 0;
        long endMillis = 0;
        try{
/*
            // 2.获取锁
            // jedis.setnx(key, value);  SET if Not eXists
            Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "cjn");
            // 3.如果突发宕机,锁没有释放掉怎么办,key过期处理(10s),但是如果在获取锁之后就出问题呢,这一步也没有成功,大招:二合一
            stringRedisTemplate.expire(lockKey, 10, TimeUnit.SECONDS);
*/

            // 2-3
/*          Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientID, 10, TimeUnit.SECONDS);
            if (!result){
                return "error";
            }
*/

            // 3.1 解决过期时间内还未完成操作的问题
            // 加锁 + 设置超时时间
            lock.lock(30, TimeUnit.SECONDS);
            startMillis = System.currentTimeMillis();

            // 4.真正操作商品库存
            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("扣减失败,库存不足!");
                }
            }
            endMillis = System.currentTimeMillis();
        }finally {
            lock.unlock(); // 释放锁
            /*
            // 存在原子性问题
            if (clientID.equals(stringRedisTemplate.opsForValue().get(lockKey))) {
                // 5.释放锁,放在finally块中防止没有释放锁导致死锁问题
                stringRedisTemplate.delete(lockKey);
            }
            */
            System.out.println("200个库存 同步时,耗时: " + (endMillis - startMillis));
        }
        return "end";
    }
}

Redisson机制流程: 

可以优化Redis性能的方向:

a.RedLock 目前该技术还存在问题,不能保证100%可靠

 b.采用分段机制,类似ConcurrentHashMap

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值