基础篇:分布式锁的入门学习

目录

基础篇:分布锁

为什么需要分布式锁?

分布式锁的特征

常用场景

Redis实现分布式锁

Redisson 分布式锁入门实战

参考文献


基础篇:分布锁

Java中的synchronized关键字和ReentrantLock可重入锁可以在多线程环境中控制对资源的并发访问,但是其是JVM级别的本地锁,在分布式场景下就会失效。分布式锁可以避免不同节点重复相同的工作,从而避免多个节点同时对数据进行操作影响正确性。

1、如果在集群部署下,需要执行定时任务,没有xxljob等分布式调度中心,那么该任务在多台服务器上同时执行就会产生异常。

2、如果用户在电商平台下单,由于网络问题连续点击两次,那么后端该如何响应呢?前后端都需要进行处理,后端需要考虑的就是处理重复订单的问题来保证幂等性。

分布式锁要解决的就是多机器部署时,相同请求并发访问时资源竞争问题。

为什么需要分布式锁?

如果现在需要一个统计登录人数的功能,那我们该如何做呢?

可以设计一个全局计数器,用户每次登录onlineCount ++,但这个操作不是原子性的,它会编译成onlineCount = onlineCount +1 ,其实是做了如下3个步骤:

  • 读内存到寄存器:将onlineCount的值从内存中读到CPU的寄存器上;
  • 在寄存器中自增:在CPU的寄存器里进行onlineCount + 1的操作;
  • 将数据写回内存:把寄存器里计算好的值写回内存。

如果两个客户端同时执行这个功能,那么客户端 1 和客户端 2 都先把 onlineCount 的值从内存中读到 CPU 的寄存器上,假设都是 0,然后两个客户端又都在寄存器里进行 +1 操作,变成了 1。最后客户端 1 将自增后的数据 1 写回内存,客户端 2 也将自增后的数据 1 写回内存,正常吗?不正常,正确结果应该是 2,结果却是 1。

锁能够保证多线程环境中对同一份资源竞争的数据安全性,分布式锁就是用于用于保证整个集群内的多线程并发线程安全性的一种手段。在分布式的集群环境下,就需要用分布式锁来保证整个集群的线程安全性;

分布式锁的特征

分布式锁必须具备四个特征:高性能、可重入、防死锁和互斥性;

  • 高性能:分布式锁的性能一定要高,如果在开锁和解锁的过程耗时就会影响系统在高并发秒杀场景下的性能体现;
  • 可重入:持有锁的线程是当前线程,那么就应该正常的执行逻辑,而不是阻塞等待;
  • 防死锁:客户端 1 加锁了,还没来得及释放,服务宕机了,那这把锁怎么释放?不释放的话其他客户端永远执行不了这个方法,当机器恢复后客户端 1 也无法执行,这也是一种死锁的表现;
  • 互斥性:不允许多个客户端同时执行一段代码;

常用场景

  • 防止缓存击穿:在查数据库的时候加一个分布式锁,查出来后再把数据缓存到 Redis,这样就能保证大流量请求进来的时候,即便 Redis 失效了也只会有一个请求同时打到数据库中;
  • 保证接口幂等性。举个最简单的例子:表单重复提交,点击提交后还没处理完,又点击了一次提交按钮,这时候就相当于提交了两份。分布式锁也可以解决此场景,提交接口添加分布式锁,第一次提交的时候会锁住,然后第二次提交发现有锁,就不会执行业务逻辑;
  • 任务调度:用分布式锁来保证集群部署的时候只有一台在工作,其他机器执行的时候发现有锁就不再执行;
  • 秒杀减库存等类似业务防止超卖的情况

Redis实现分布式锁

Redis 作为分布式锁的关键点有四个:原子性、过期时间、锁续期正确释放锁;

1、原子性 - 要么都成功,要么都失败

实现分布式锁的核心就是找一个集群内都可见的地方将锁存起来,同时还需要考虑死锁的问题,给锁设置过期时间,那么redis中只需要两条命令即可:

redis.set(key,value);
redis.expire(key,time,unit);

redis支持lua脚本,可以将两条命令采用lua脚本保证原子性;

2、过期时间

过期时间是巍峨避免死锁,防止服务宕机,一直无法释放;

3、锁续期

假设锁过期时间设置了 3s,但是业务代码执行了 4s 还没执行完,那锁过期自动释放了,其他线程在请求接口的时候发现目前没锁,就又加上了锁,这时候不就两个客户端并发执行了吗?相当于还是线程不安全。开辟另外一个线程,专门用于锁续期,上锁的时候就起个线程进行死循环续期,核心流程就是判断锁的时间过三分之一了就给他重新续期为上锁时间。比如设置的锁是 3s,检查超过 1s 了还没执行完,那就重新给这个锁续期为 3s,防止方法没执行完呢,锁先过期了的情况。

4、正确释放锁

利用锁续期机制防止出现业务没执行完成锁就到期了、提前被释放的情况,且释放锁的时候还要判断是不是自己加的锁,避免误释放了别人的锁。

Redisson 分布式锁入门实战

redisson官方地址

大家都知道 ReentrantLock 已经有了很好的锁性能和实现。 在互斥、可重入、锁超时、阻塞支持、公平锁支持方面有很好的性能和实现,但不适合分布式场景。 redisson 是一个分布式锁来弥补这个不足(分布式锁还有很多,其他的,这里不讨论了)RLock接口继承了Lock接口,自然优雅的实现了上面的锁需求。

1、在项目中引入相关的依赖

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <version>2.9.0</version>
        </dependency>
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.13.1</version>
        </dependency>

2、在配置文件中添加redis的配置

spring:
  redis:
    database: 0
    host: Your hostname
    password: Your password
    port: 6379
    lettuce:
      pool:
        max-active: 100
        max-wait: -1
        max-idle: 8
        min-idle: 0

3、RedissonConfig配置

import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Slf4j
@Configuration
public class RedissonConfig {


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

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

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

    @Value("${spring.redis.database}")
    private int database;

    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        String REDISSON_PREFIX = "redis://";
        String url = REDISSON_PREFIX + host + ":" + port;
        // Single Redis
        config.useSingleServer()
                .setAddress(url)
                .setPassword(password)
                .setDatabase(database);
        try {
            return Redisson.create(config);
        } catch (Exception e) {
            log.error("RedissonClient init redis url:[{}], Exception:", url, e);
            return null;
        }
    }
}

4、DistributedRedisLock

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;

import java.util.concurrent.TimeUnit;


@Slf4j
@Component
public class DistributedRedisLock {


    @Autowired
    RedissonClient redissonClient;

    public Boolean lock(String lockName){
        if(null == redissonClient){
            log.info("DistributedRedisLock redissonClient is null");
            return false;
        }
        try{
            RLock lock = redissonClient.getLock(lockName);
            lock.lock(10,TimeUnit.SECONDS);
            log.info("Thread [{}] DistributedRedisLock lock [{}] success Lock succeeded", Thread.currentThread().getName(), lockName);
            return true;
        } catch (Exception e){
            log.error("DistributedRedisLock lock [{}] Exception:", lockName, e);
            return false;
        }
    }

    public Boolean unlock(String lockName){
        if (redissonClient == null) {
            log.info("DistributedRedisLock redissonClient is null");
            return false;
        }
        try{
            RLock lock = redissonClient.getLock(lockName);
            lock.unlock();
            log.info("Thread [{}] DistributedRedisLock unlock [{}] success Unlock", Thread.currentThread().getName(), lockName);
            return true;
        } catch (Exception e){
            log.error("DistributedRedisLock unlock [{}] Exception:", lockName, e);
            return false;
        }
    }
}

5、LockController

import com.example.lock.util.DistributedRedisLock;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;


@Slf4j
@RestController
@RequestMapping("/lock")
public class LockController {

    @Autowired
    DistributedRedisLock distributedRedisLock;

    AtomicInteger ID = new AtomicInteger(0);
    AtomicInteger ID1 = new AtomicInteger(0);

    // Test Do Not Release Lock
    @GetMapping("/testLock")
    public void testLock() {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(5);
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
//                distributedRedisLock.lock(LOCK);
                try {
                    System.out.println(ID.addAndGet(1)+"Enter Wait");
                    cyclicBarrier.await();
                    System.out.println("Start execution");
                    post();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }

    // Test Do Not Release Lock
    @GetMapping("/testLock1")
    public void testLock1() {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(5);
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
//                distributedRedisLock.lock(LOCK);
                try {
                    System.out.println(ID1.addAndGet(1)+"Enter Wait");
                    cyclicBarrier.await();
                    System.out.println("Start execution");
                    post1();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }

    // How distributed locks are used in real-world business development
    public void post() throws InterruptedException {
        final String LOCK = "LOCK2LOCK";
        try {
            if (distributedRedisLock.lock(LOCK)) {
                log.info("No. e Two ready to start business logic");
                TimeUnit.SECONDS.sleep(1);
                // Business logic
                log.info("No. e Two Start Business Logics");
                TimeUnit.SECONDS.sleep(1);
            } else {
                // Handling logic that failed to acquire locks
                log.info("Failed to acquire lock");
            }
        } catch (Exception e) {
            log.error("Handle exceptions:", e);
        } finally {
            distributedRedisLock.unlock(LOCK);
            TimeUnit.SECONDS.sleep(1);
        }
    }


    // How distributed locks are used in real-world business development
    public void post1() throws InterruptedException {
        final String LOCK = "LOCK1LOCK";
        try {
            if (distributedRedisLock.lock(LOCK)) {
                // Business logic
                log.info("First Start Business Logic");
                TimeUnit.SECONDS.sleep(1);
            } else {
                // Handling logic that failed to acquire locks
                log.info("Failed to acquire lock");
            }
        } catch (Exception e) {
            log.error("Handle exceptions:", e);
        } finally {
            distributedRedisLock.unlock(LOCK);
            TimeUnit.SECONDS.sleep(1);
        }
    }

}

 参考文献:

1、Hello,分布式锁

2、Redisson

源码地址:github.com/redisson/re…

中文文档:github.com/redisson/re…

英文文档:github.com/redisson/re…

3、About Redisson's Distributed Lock

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值