0


Redission实现分布式锁

小示例

在说到分布式锁的时候,大家第一反应想到的应该是用redis实现分布式锁。
那么就来看一下下面的几个代码(如果不想看示例,可以直接跳到正文)
#示例1

public class RedisLock {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    private static final String REQUEST_CONTAINER = "TEST_LOCK";

    /**
     * 锁的过期时间可设置的长一些(业务执行完就释放了),至少要比预计的业务执行时间长(防止业务没执行完就释放了锁)
     */
    private static final long EXPIRE_TIME = 300L;

    public void lock(String lockName) {

        String key = REQUEST_CONTAINER + lockName;

        boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(key,1);
        if (aBoolean) {
            try {
                log.info("====================进入锁:{}==================",key);
                // 这里设置锁的过期时间和finally代码块都是为了防止死锁(JVM 崩溃,操作系统死掉等,finally是不会执行的)
                redisTemplate.expire(key,EXPIRE_TIME, TimeUnit.SECONDS);
                /*已进入业务*/
                // ********业务逻辑处理中****** //
                log.info("处理业务中");
                // *******业务逻辑处理结束****** //
            }catch (Exception e) {
                log.info("出现了点小问题-{}", e.getMessage());
            } finally {
                // 业务异常,手动释放锁
                redisTemplate.delete(key);
                log.info("------------redis锁释放成功-----------");
            }
        }else {
            log.info("获取不到锁");
        }
    }
}

问题:在工作中,我发现是有人会和上面一样实现分布式锁的,那存在一个问题,当我们执行完setIfAbsent操作,设置了锁,但是在执行expire之前,宕机了或者服务器挂掉了,这个锁就没法释放了。

#示例2

public class RedisLock {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    private static final String REQUEST_CONTAINER = "TEST_LOCK";

    /**
     * 锁的过期时间可设置的长一些(业务执行完就释放了),至少要比预计的业务执行时间长(防止业务没执行完就释放了锁)
     */
    private static final long EXPIRE_TIME = 300L;

    public void lock(String lockName) {

        String key = REQUEST_CONTAINER + lockName;
        // 每个人进来先要进行加锁,key值为"good_lock",一般可以用实际业务中的key
        String value = UUID.randomUUID().toString().replace("-","");
        try {
            boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(REQUEST_CONTAINER, value,EXPIRE_TIME,TimeUnit.SECONDS);
            if (aBoolean) {
                    log.info("====================进入锁:{}==================",key);
                    // 这里设置锁的过期时间和finally代码块都是为了防止死锁(JVM 崩溃,操作系统死掉等,finally是不会执行的)
                    redisTemplate.expire(key,EXPIRE_TIME, TimeUnit.SECONDS);
                    /*已进入业务*/
                    // ********业务逻辑处理中****** //
                    log.info("处理业务中");
                    // *******业务逻辑处理结束****** //
            }else {
                log.info("获取不到锁");
            }
        }catch (Exception e) {
            log.info("出现了点小问题-{}", e.getMessage());
        } finally {
            // 谁加的锁,谁才能删除!!!!
            if(redisTemplate.opsForValue().get(REQUEST_CONTAINER).equals(value)){
                redisTemplate.delete(REQUEST_CONTAINER);
            }
        }
    }
}

注: redis在2.1.3和之后的版本里,才可以同时设置key和过期时间
问题: 上面的代码是更完善的一个版本,但同样存在两个问题:
1、finally块的判断和del删除操作不是原子操作,并发的时候也会出问题
2、若业务逻辑复杂,不能在锁过期时间之内完成操作,锁过期了,其他线程就进来了,这也是一个很严重的问题
针对这两个问题,是有解决办法
1、用lua脚本保证finally块的判断和del删除是原子性的
2、通过额外的定时任务完成锁续命的逻辑

重点来了: 说了这么多,大家应该觉得这个分布式锁好麻烦啊,要注意的东西太多,那就看一下Redission如何实现的分布式锁吧

正文—Redission实现分布式锁

1、引入依赖

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

2、application.yml配置

spring:
  redis:
    host: 127.0.0.1
    password: redis123
    port: 6379
    timeout: 0

3、Redission配置类

@Configuration
public class RedissonConfig {

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

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

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

    @Bean
    public RedissonClient getRedisson(){

        Config config = new Config();
        //单机模式  依次设置redis地址和密码
        config.useSingleServer().
                setAddress("redis://" + host + ":" + port).
                setPassword(redisPassword);
        return Redisson.create(config);
    }
}

4、Redission分布式锁对应业务类

@RestController
@RequestMapping("/redisLock")
public class RedisLockController {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private RedissonClient redisson;

    private static final String REDIS_KEY = "redis_oa_01";

    private static final int MAX_SIZE = 1000;
    
    /**
     * 初始化库存
     */
    @PostMapping("/init")
    public void init() {
        stringRedisTemplate.opsForValue().set(REDIS_KEY, String.valueOf(MAX_SIZE));
    }

    /**
     * 扣库存业务
     */
    @PostMapping("/exportInventory")
    public void exportInventory() {

        String lockKey = "product001";
        RLock lock = redisson.getLock(lockKey);
        try {
            lock.lock();
            int s = Integer.parseInt(Objects.requireNonNull(stringRedisTemplate.opsForValue().get(REDIS_KEY)));
            s--;
            System.out.printf("1号服务:库存当前为:" + s + "\n");
            stringRedisTemplate.opsForValue().set(REDIS_KEY, String.valueOf(s));
        }catch (Exception e){}
        finally {
            lock.unlock();
        }
    }

以上操作分别写两份,并启动两个服务,模拟分布式的情况
在这里插入图片描述
现在我们可以下载一个jmeter,进行压测,看看我们的代码抗不抗揍
jmeter的使用教程,参考一下这个:jmeter的使用教程
jmeter操作配置
在这里插入图片描述在这里插入图片描述

Idea打印执行结果
在这里插入图片描述
我设置的是1000个库存,由两个服务去执行扣除,3s,执行1000次,没有一个重复的,还是抗揍的吧。

标签: 分布式 java redis

本文转载自: https://blog.csdn.net/weixin_45807440/article/details/126122646
版权归原作者 眺望的长颈鹿 所有, 如有侵权,请联系我们删除。

“Redission实现分布式锁”的评论:

还没有评论