分布式事务的锁

简介: 分布式事务的锁

1,课程回顾
2,本章重点
3,具体内容
3.1 前言

微服务的流行,使得现在基本都是分布式开发,也就是同一份代码会在多台机器上部署运行,此时若多台机器需要同步访问同一个资源(同一时间只能有一个节点机器在运行同一段代码),就需要使用到分布式锁。

3.2 什么是分布式锁

在非分布式系统中(单机应用)一个共享的变量或者一个方法进行多线程同步访问,可以使用简单加锁(synchronized)方式实现,让同一时刻,只有一个线程执行。随着互联网发展,单机应用已经满足不了需求,分布式系统就出现了,假如不同系统或者是同一系统的不同节点(满足高并发实现集群),需要有共享资源(共享变量或者业务方法),控制他们同步访问的方式就叫分布式锁。通俗的说,就是在同一时刻,大量先线程访问分布式中的共享资源时,为了防止相互干扰,排斥其他线程,只让一个线程对共享变量进行更改访问,或者进行业务方法操作。

3.3 为什么要使用分布式锁

为了保证一个方法在高并发情况下的同一时间只能被同一个线程执行,在传统单体应用单机部署的情况下,可以使用Java并发处理相关的API(如ReentrantLcok或synchronized)进行互斥控制。但是,随着业务发展的需要,原单体单机部署的系统被演化成分布式系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题。

3.4 分布式锁应具备哪些条件

高可用 ,高性能获取和释放锁

具备锁的失效机制,防止死锁

具备非阻塞锁的特性,就是没有获取到锁的时候直接返回获取锁失败。

3.5 分布式锁常见实现方式

借助于数据库实现

要实现分布式锁,最简单的方式可能就是直接创建一张锁表,然后通过操作该表中的数据来实现了。当我们要锁住某个方法或资源时,我们就在该表中增加一条记录,想要释放锁的时候就删除这条记录。

除了可以通过增删操作数据表中的记录以外,其实还可以借助数据中自带的锁来实现分布式的锁。 还可以通过数据库的排他锁来实现分布式锁。 基于MySql的InnoDB引擎,可以使用查询语句加上for update实现加锁操作。

这两种方式都是依赖数据库的一张表,一种是通过表中的记录的存在情况确定当前是否有锁存在,另外一种是通过数据库的排他锁来实现分布式锁。

基于数据库的锁设计存在一下问题:

l 这把锁强依赖数据库的可用性,数据库是一个单点,一旦数据库挂掉,会导致业务系统不可用。

l 这把锁没有失效时间,一旦解锁操作失败,就会导致锁记录一直在数据库中,其他线程无法再获得到锁。

l 这把锁只能是非阻塞的,因为数据的insert操作,一旦插入失败就会直接报错。没有获得锁的线程并不会进入排队队列,要想再次获得锁就要再次触发获得锁操作。

l 这把锁是非重入的,同一个线程在没有释放锁之前无法再次获得该锁。因为数据中数据已经存在了。

使用redis实现

相比较于基于数据库实现分布式锁的方案来说,基于缓存来实现在性能方面会表现的更好一点。而且很多缓存是可以集群部署的,可以解决单点问题。

本章下面内容就是基于redis实现,在此不再做单独讲解。

使用zookeeper实现

基于zookeeper临时有序节点可以实现的分布式锁。

实现思路:

首先zookeeper中我们可以创建一个/distributed_lock持久化节点

然后再在/distributed_lock节点下创建自己的临时顺序节点,比如:/distributed_lock/task_00000000000, task_00000000001…

获取所有的/distributed_lock下的所有子节点,并排序

判读自己创建的节点是否最小值(第一位)

如果是,则获取得到锁,执行自己的业务逻辑,最后删除这个临时节点。(如果出错可以让会话断开,创建临时节点消失,有效防止锁定或者死锁)

如果不是最小值,则需要监听自己创建节点前一位节点的数据变化,并阻塞。

当前一位节点被删除时,我们需要通过递归来判断自己创建的节点是否在是最小的,如果是则执行5);如果不是则执行6)(就是递归循环的判断)

3.6 Redission简介

Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。Redisson提供了使用Redis的最简单和最便捷的方法。Redisson的宗旨是促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。

3.7 Springcloud+Redisson+redis集群实现分布式锁

1.1.1 引入JAR包

父项目中:

<!--分布式锁需要jar-->
                 <!-- https://mvnrepository.com/artifact/org.redisson/redisson -->
                 <dependency>
                     <groupId>org.redisson</groupId>
                     <artifactId>redisson</artifactId>
                     <version>3.12.0</version>
                 </dependency>

common项目引入:

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

micro_servieces中引入jar:

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

在common项目中编写相关工具类

package com.aaa.common.util;
import java.util.concurrent.TimeUnit;
/**
 * fileName:DistributedLocker
 * description:
 * author:zz
 * createTime:2020/1/29 10:48
 * version:1.0.0
 */
public interface DistributedLocker {
    /**
     * 不带时间的锁定方法
     * @param lockKey
     */
    void lock(String lockKey);
    /**
     * 带时间的锁定方法,默认为秒(实现类中自己定义)
     * @param lockKey
     * @param timeout
     */
    void lock(String lockKey, int timeout);
    /**
     * 带时间和单位的锁定方法
     * @param lockKey
     * @param unit
     * @param timeout
     */
    void lock(String lockKey, TimeUnit unit , int timeout);
    /**
     * 解锁方法
     * @param lockKey
     */
    void unlock(String lockKey);
}

接口实现类:RedissonDistributedLocker

package com.aaa.common.util;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import java.util.concurrent.TimeUnit;
/**
 * fileName:RedissonDistributedLocker
 * description:
 * author:zz
 * createTime:2020/1/29 10:49
 * version:1.0.0
 */
public class RedissonDistributedLocker implements DistributedLocker {
    private RedissonClient redissonClient;
    @Override
    public void lock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock();
    }
    @Override
    public void unlock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.unlock();
    }
    @Override
    public void lock(String lockKey, int timeout) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock(timeout, TimeUnit.SECONDS);
    }
    @Override
    public void lock(String lockKey, TimeUnit unit, int timeout) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock(timeout, unit);
    }
    /**
     * 手动注入redissonClient方法
     * @param redissonClient
     */
    public void setRedissonClient(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }
}

工具类调用上面接口的进行锁定或者解锁操作RedissLockUtil:

package com.aaa.common.util;

import java.util.concurrent.TimeUnit;
/**
 * fileName:RedissLockUtil
 * description:
 * author:zz
 * createTime:2020/1/29 10:56
 * version:1.0.0
 */
public class RedissLockUtil {
    private static DistributedLocker redissLock;
    public static void setLocker(DistributedLocker locker) {
        redissLock = locker;
    }
    public static void lock(String lockKey) {
        redissLock.lock(lockKey);
    }
    public static void unlock(String lockKey) {
        redissLock.unlock(lockKey);
    }
    /**
     * 带超时的锁
     * @param lockKey
     * @param timeout 超时时间   单位:秒
     */
    public static void lock(String lockKey, int timeout) {
        redissLock.lock(lockKey, timeout);
    }
    /**
     * 带超时的锁
     * @param lockKey
     * @param unit 时间单位
     * @param timeout 超时时间
     */
    public static void lock(String lockKey, TimeUnit unit , int timeout) {
        redissLock.lock(lockKey, unit, timeout);
    }
}

加载配置文件的配置类 RedissonProperties:

package com.aaa.common.util;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
 * fileName:RedissonProperties
 * description:
 * author:zz
 * createTime:2020/1/29 10:54
 * version:1.0.0
 */
@Configuration
@ConfigurationProperties(prefix = "redisson")
public class RedissonProperties {
    private int timeout;
    private String address;
    private String password;
    private int connectionPoolSize;
    private int connectionMinimumIdleSize;
    private int slaveConnectionPoolSize;
    private int masterConnectionPoolSize;
    private String[] sentinelAddresses;
    private String masterName;
    public int getTimeout() {
        return timeout;
    }
    public void setTimeout(int timeout) {
        this.timeout = timeout;
    }
    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public int getConnectionPoolSize() {
        return connectionPoolSize;
    }
    public void setConnectionPoolSize(int connectionPoolSize) {
        this.connectionPoolSize = connectionPoolSize;
    }
    public int getConnectionMinimumIdleSize() {
        return connectionMinimumIdleSize;
    }
    public void setConnectionMinimumIdleSize(int connectionMinimumIdleSize) {
        this.connectionMinimumIdleSize = connectionMinimumIdleSize;
    }
    public int getSlaveConnectionPoolSize() {
        return slaveConnectionPoolSize;
    }
    public void setSlaveConnectionPoolSize(int slaveConnectionPoolSize) {
        this.slaveConnectionPoolSize = slaveConnectionPoolSize;
    }
    public int getMasterConnectionPoolSize() {
        return masterConnectionPoolSize;
    }
    public void setMasterConnectionPoolSize(int masterConnectionPoolSize) {
        this.masterConnectionPoolSize = masterConnectionPoolSize;
    }
    public String[] getSentinelAddresses() {
        return sentinelAddresses;
    }
    public void setSentinelAddresses(String[] sentinelAddresses) {
        this.sentinelAddresses = sentinelAddresses;
    }
    public String getMasterName() {
        return masterName;
    }
    public void setMasterName(String masterName) {
        this.masterName = masterName;
    }
}

在微服务项目中真正使用redisson做分布式锁

application.properties中添加下面配置:

#redisson配置   官网地址:https://github.com/redisson/redisson/wiki/2.-%E9%85%8D%E7%BD%AE%E6%96%B9%E6%B3%95
#配置redis链接地址
redisson.address=redis://127.0.0.1:6379
#密码
redisson.password=
#命令等待超时
redisson.timeout = 3000
#连接池大小
redisson.connectionPoolSize=64
#最小空闲连接数
redisson.connectionMinimumIdleSize=10
#从节点连接池大小
redisson.slaveConnectionPoolSize = 250
#主节点连接池大小
redisson.masterConnectionPoolSize = 250

在微服务中编写RedissonAutoConfiguration:

package com.aaa.pay.config;
import com.aaa.common.util.DistributedLocker;
import com.aaa.common.util.RedissLockUtil;
import com.aaa.common.util.RedissonDistributedLocker;
import com.aaa.common.util.RedissonProperties;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.redisson.config.SentinelServersConfig;
import org.redisson.config.SingleServerConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
 * fileName:RedissonAutoConfiguration
 * description:
 * author:zz
 * createTime:2020/1/29 11:03
 * version:1.0.0
 */
@Configuration
@ConditionalOnClass(Config.class)
/**
 * @ConditionalOnBean         //   当给定的在bean存在时,则实例化当前Bean
 @ConditionalOnMissingBean  //   当给定的在bean不存在时,则实例化当前Bean
 @ConditionalOnClass        //   当给定的类名在类路径上存在,则实例化当前Bean
 @ConditionalOnMissingClass //   当给定的类名在类路径上不存在,则实例化当前Bean
 */
@EnableConfigurationProperties(RedissonProperties.class)
public class RedissonAutoConfiguration {
    @Autowired
    private RedissonProperties redssionProperties;
    /**
     * 哨兵模式自动装配
     * @return
     */
   // @Bean
   // @ConditionalOnProperty(name="redisson.master-name")
    RedissonClient redissonSentinel() {
        Config config = new Config();
        SentinelServersConfig serverConfig = config.useSentinelServers().addSentinelAddress(redssionProperties.getSentinelAddresses())
                .setMasterName(redssionProperties.getMasterName())
                .setTimeout(redssionProperties.getTimeout())
                .setMasterConnectionPoolSize(redssionProperties.getMasterConnectionPoolSize())
                .setSlaveConnectionPoolSize(redssionProperties.getSlaveConnectionPoolSize());
        if(redssionProperties.getPassword() != null && !"".equals(redssionProperties.getPassword())) {
            serverConfig.setPassword(redssionProperties.getPassword());
        }
        return Redisson.create(config);
    }
    /**
     * 单机模式自动装配
     * @return
     */
    @Bean
    @ConditionalOnProperty(prefix = "redisson",name="address") //ConditionalOnProperty 可以控制类或者方法是否生效
    RedissonClient redissonSingle() {
        Config config = new Config();
       // System.out.println(redssionProperties.getAddress()+","+redssionProperties.getTimeout()+"...");
        SingleServerConfig serverConfig = config.useSingleServer()
                .setAddress(redssionProperties.getAddress())
                .setTimeout(redssionProperties.getTimeout())
                .setConnectionPoolSize(redssionProperties.getConnectionPoolSize())
                .setConnectionMinimumIdleSize(redssionProperties.getConnectionMinimumIdleSize());
        if(redssionProperties.getPassword() != null && !"".equals(redssionProperties.getPassword())) {
            serverConfig.setPassword(redssionProperties.getPassword());
        }
        return Redisson.create(config);
    }
    /**
     * 装配locker类,并将实例注入到RedissLockUtil中
     * @return
     */
    @Bean
    DistributedLocker distributedLocker(RedissonClient redissonClient) {
        RedissonDistributedLocker locker = new RedissonDistributedLocker();
        locker.setRedissonClient(redissonClient);
        RedissLockUtil.setLocker(locker);
        return locker;
    }
}

4,知识点总结
5,本章面试题

目录
相关文章
|
NoSQL 调度 Redis
分布式锁—3.Redisson的公平锁
Redisson公平锁(RedissonFairLock)是一种基于Redis实现的分布式锁,确保多个线程按申请顺序获取锁,从而实现公平性。其核心机制是通过队列和有序集合管理线程的排队顺序。加锁时,线程会进入队列并等待,锁释放后,队列中的第一个线程优先获取锁。RedissonFairLock支持可重入加锁,即同一线程多次加锁不会阻塞。新旧版本在排队机制上有所不同,新版本在5分钟后才会重排队列,而旧版本在5秒后就会重排。释放锁时,Redisson会移除队列中等待超时的线程,并通知下一个排队的线程获取锁。通过这种机制,RedissonFairLock确保了锁的公平性和顺序性。
|
NoSQL Java 调度
分布式锁与分布式锁使用 Redis 和 Spring Boot 进行调度锁(不带 ShedLock)
分布式锁是分布式系统中用于同步多节点访问共享资源的机制,防止并发操作带来的冲突。本文介绍了基于Spring Boot和Redis实现分布式锁的技术方案,涵盖锁的获取与释放、Redis配置、服务调度及多实例运行等内容,通过Docker Compose搭建环境,验证了锁的有效性与互斥特性。
1044 0
分布式锁与分布式锁使用 Redis 和 Spring Boot 进行调度锁(不带 ShedLock)
|
安全
【📕分布式锁通关指南 07】源码剖析redisson利用看门狗机制异步维持客户端锁
Redisson 的看门狗机制是解决分布式锁续期问题的核心功能。当通过 `lock()` 方法加锁且未指定租约时间时,默认启用 30 秒的看门狗超时时间。其原理是在获取锁后创建一个定时任务,每隔 1/3 超时时间(默认 10 秒)通过 Lua 脚本检查锁状态并延长过期时间。续期操作异步执行,确保业务线程不被阻塞,同时仅当前持有锁的线程可成功续期。锁释放时自动清理看门狗任务,避免资源浪费。学习源码后需注意:避免使用带超时参数的加锁方法、控制业务执行时间、及时释放锁以优化性能。相比手动循环续期,Redisson 的定时任务方式更高效且安全。
1486 24
【📕分布式锁通关指南 07】源码剖析redisson利用看门狗机制异步维持客户端锁
|
NoSQL Redis
分布式锁设计吗,你是如何实现锁类型切换、锁策略切换基于限流的?
本方案基于自定义注解与AOP实现分布式锁,支持锁类型(如可重入锁、公平锁等)与加锁策略(如重试、抛异常等)的灵活切换,并结合Redisson实现可重入、自动续期等功能,通过LUA脚本保障原子性,兼顾扩展性与实用性。
347 0
|
NoSQL 调度 Redis
分布式锁—5.Redisson的读写锁
Redisson读写锁(RedissonReadWriteLock)是Redisson提供的一种分布式锁机制,支持读锁和写锁的互斥与并发控制。读锁允许多个线程同时获取,适用于读多写少的场景,而写锁则是独占锁,确保写操作的互斥性。Redisson通过Lua脚本实现锁的获取、释放和重入逻辑,并利用WatchDog机制自动续期锁的过期时间,防止锁因超时被误释放。 读锁的获取逻辑通过Lua脚本实现,支持读读不互斥,即多个线程可以同时获取读锁。写锁的获取逻辑则确保写写互斥和读写互斥,即同一时间只能有一个线程获取写锁,
692 17
【📕分布式锁通关指南 08】源码剖析redisson可重入锁之释放及阻塞与非阻塞获取
本文深入剖析了Redisson中可重入锁的释放锁Lua脚本实现及其获取锁的两种方式(阻塞与非阻塞)。释放锁流程包括前置检查、重入计数处理、锁删除及消息发布等步骤。非阻塞获取锁(tryLock)通过有限时间等待返回布尔值,适合需快速反馈的场景;阻塞获取锁(lock)则无限等待直至成功,适用于必须获取锁的场景。两者在等待策略、返回值和中断处理上存在显著差异。本文为理解分布式锁实现提供了详实参考。
742 11
【📕分布式锁通关指南 08】源码剖析redisson可重入锁之释放及阻塞与非阻塞获取
|
消息中间件 NoSQL Java
Redis系列学习文章分享---第六篇(Redis实战篇--Redis分布式锁+实现思路+误删问题+原子性+lua脚本+Redisson功能介绍+可重入锁+WatchDog机制+multiLock)
Redis系列学习文章分享---第六篇(Redis实战篇--Redis分布式锁+实现思路+误删问题+原子性+lua脚本+Redisson功能介绍+可重入锁+WatchDog机制+multiLock)
954 0
|
缓存 NoSQL Java
【📕分布式锁通关指南 11】源码剖析redisson之读写锁的实现
Redisson 的 `RedissonReadWriteLock` 提供了高效的分布式读写锁实现,适用于读多写少的场景。通过 Redis 与 Lua 脚本结合,确保读锁并行、写锁互斥,以及读写之间的互斥,保障了分布式环境下的数据一致性。它支持可重入、自动过期和锁释放机制,提升了系统并发性能与资源控制能力。
468 0
|
监控 NoSQL Java
分布式锁—2.Redisson的可重入锁
本文主要介绍了Redisson可重入锁RedissonLock概述、可重入锁源码之创建RedissonClient实例、可重入锁源码之lua脚本加锁逻辑、可重入锁源码之WatchDog维持加锁逻辑、可重入锁源码之可重入加锁逻辑、可重入锁源码之锁的互斥阻塞逻辑、可重入锁源码之释放锁逻辑、可重入锁源码之获取锁超时与锁超时自动释放逻辑、可重入锁源码总结。
|
SQL Java 关系型数据库
【📕分布式锁通关指南 01】从解决库存超卖开始加锁的初体验
本文通过电商场景中的库存超卖问题,深入探讨了JVM锁、MySQL悲观锁和乐观锁的实现及其局限性。首先介绍了单次访问下库存扣减逻辑的正常运行,但在高并发场景下出现了超卖问题。接着分析了JVM锁在多例模式、事务模式和集群模式下的失效情况,并提出了使用数据库锁机制(如悲观锁和乐观锁)来解决并发问题。 悲观锁通过`update`语句或`select for update`实现,能有效防止超卖,但存在锁范围过大、性能差等问题。乐观锁则通过版本号或时间戳实现,适合读多写少的场景,但也面临高并发写操作性能低和ABA问题。 最终,文章强调没有完美的方案,只有根据具体业务场景选择合适的锁机制。
840 12
【📕分布式锁通关指南 01】从解决库存超卖开始加锁的初体验

热门文章

最新文章