浅谈分布式锁

浅谈分布式锁

一、 现象实例

1.修改定时任务

`package com.qfedu.springboot03.task;

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;

@Component
public class PrintTask {

@Autowired
private RedissonClient redissonClient;

// 任务默认同步执行的
@Async  // 表示异步执行的注解
// 每分钟的第5秒触发定时任务
@Scheduled(cron = "5 * * * * ?")
public void printInfo() {
    System.out.println(count);
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSS");
    System.out.println(Thread.currentThread().getName() + ":" + sdf.format(new Date()));
}

}
    

2.启动同一个项目的多个实例

配置:

image.png

image.png

切换不同的配置,启动项目

image.png

3、观察执行结果

两个项目实例的任务"同时"执行了

img

如果不想任务同时执行,需要怎么办?这时,可以借助分布式锁实现。

二、分布式锁介绍

​ 随着业务的发展,传统单机部署无发很好的解决高并发的问题,于是,原来的单机部署演化成分布式集群部署,如果在集群环境下,我们想针对某个共享的操作加锁,这时用传统的策略就无法实现。为了解决这个问题,就需要一种跨机器的互斥机制来控制共享资源的访问,通过分布式锁就能解决这样的问题。

注意:本文通过Redission框架(相当于redis的客户端工具),借助redis实现分布式锁。

三、分布式锁的解决方案

redis命令

(1)setnx key value + expire(超时设置)

(2)SET key value ex seconds nx,相当于setnx 和 expire的组合,2.8版本后提供的命令ex seconds 过期时间,防止加锁线程死掉不能解锁nx 如果没有这个key则设置,存在key返回失败。

比如:set name zhang ex 300 nx

RedLock

http://redis.cn/topics/distlock.html

在redis集群环境下,上面的方案并不是绝对安全的。

img

下面提到的Redission提供了红锁的机制。

https://github.com/redisson/redisson/wiki/8.-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%92%8C%E5%90%8C%E6%AD%A5%E5%99%A8#84-%E7%BA%A2%E9%94%81redlock

第三方工具Redission

img

注意,图片来自网络

线程去获取锁,获取成功则执行lua脚本,保存数据到redis数据库。

如果获取失败,一直通过while循环尝试获取锁(相关方法中可设置获取锁的等待时间,如果超时返回失败),获取成功后,执行lua脚本,保存数据到redis数据库。

Redisson提供的分布式锁是支持锁自动续期的,也就是说,如果线程仍旧没有执行完,那么redisson会自动给redis中的目标key延长超时时间,这在Redisson中称之为 Watch Dog 机制。

Redission实现分布式锁

导入jar

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.18.0</version>
</dependency>

<!--      如果项目中,导入过redis的包,这里直接导入redisson的包即可      -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.18.0</version>
</dependency>

image.png

配置类

以单机redis为例

package com.qfedu.springboot03.config;

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RedissonConfig {
    @Bean
    public RedissonClient getRedisson() {
        Config config = new Config();
        config.useSingleServer()
                .setAddress("redis://127.0.0.1:6379")
                .setRetryInterval(5000)
                .setTimeout(10000)
                .setConnectTimeout(10000);
        return Redisson.create(config);
    }
}

修改SpringTask任务

核心方法:tryLock()

`package com.qfedu.springboot03.task;

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;

@Component
public class PrintTask {

@Autowired
private RedissonClient redissonClient;

// 任务默认同步执行的
@Async  // 表示异步执行的注解
@Scheduled(cron = "5 * * * * ?")
public void printInfo() {
    // 获取锁对象,参数表示redis中锁的key值
    RLock lock = redissonClient.getLock("testlock");
    try {
        // 尝试加锁,如果可以加锁,返回true
        // 第一个参数表示加锁等待的延迟
        // 第二个参数表示锁的自动释放时间
        if (lock.tryLock(0, 10, TimeUnit.SECONDS)) {
            System.out.println(count);
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSS");
            System.out.println(Thread.currentThread().getName() + ":" + sdf.format(new Date()));
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        if (taskLock.isLocked()) {
            // 判断是否当前线程拥有该锁
            if (taskLock.isHeldByCurrentThread()) {
                // 释放锁
                taskLock.unlock();
            }
        }
    }
}
}

`

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值