Spring Boot 与分布式锁实现:确保分布式环境中的互斥访问!

举报
bug菌 发表于 2025/07/16 16:21:27 2025/07/16
【摘要】 🏆本文收录于「滚雪球学SpringBoot」专栏(全网一个名),手把手带你零基础入门Spring Boot,从入门到就业,助你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!环境说明:Windows 10 + IntelliJ IDEA 2021.3.2 + Jdk 1.8 📜 前言:分布式锁的必要性与挑战在现代分布式系统中,由于多个服务实例...

🏆本文收录于「滚雪球学SpringBoot」专栏(全网一个名),手把手带你零基础入门Spring Boot,从入门到就业,助你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!

环境说明:Windows 10 + IntelliJ IDEA 2021.3.2 + Jdk 1.8

📜 前言:分布式锁的必要性与挑战

在现代分布式系统中,由于多个服务实例常常需要并发处理请求,访问共享资源(如数据库、文件系统、缓存等)成为了一项常见需求。然而,在并发环境下,不同实例同时访问同一资源可能会导致数据竞争和一致性问题。为了避免这些问题,分布式锁应运而生,确保在任何给定时刻,只有一个实例能够访问共享资源,从而实现互斥访问。

🛠️ 什么是分布式锁?

分布式锁是一种机制,用于确保分布式系统中多个进程或线程之间在同一时间只允许一个进程或线程访问共享资源。在分布式系统中,服务实例运行在不同的节点上,因此,传统的单机锁无法满足需求,必须引入分布式锁。

分布式锁通常使用第三方分布式协调工具,如RedisZookeeper等来实现,能够保证跨多个节点的锁竞争和资源互斥访问。

🛠️ 分布式锁的应用场景

  • 防止并发执行同一任务:确保同一个任务(如定时任务)只会被一个实例执行。
  • 限流控制:在多个实例间确保同一时刻只有一个实例进行资源访问操作。
  • 数据库资源控制:例如,防止多个实例同时向数据库写入数据,避免产生数据不一致的情况。
  • 异步任务的串行化:确保分布式环境中的任务能够顺序执行,而非并发执行,防止竞态条件。

🛠️ 分布式锁的实现方式

常见的分布式锁实现方式有:

  1. 基于数据库的分布式锁:通过数据库的行级锁或表级锁来实现。
  2. 基于Redis的分布式锁:利用Redis的SETNX命令(即只在键不存在时设置键值)来实现分布式锁。
  3. 基于Zookeeper的分布式锁:利用Zookeeper的顺序节点和watch机制来实现分布式锁。
  4. 基于Etcd、Consul等分布式协调工具的锁:这些工具也提供分布式锁机制,能够保证一致性和可靠性。

在本文中,我们将详细介绍如何在Spring Boot中使用Redis(Redisson)Zookeeper来实现分布式锁,并且讨论如何通过这些工具来保证系统的性能、可靠性与高可用性。

🧑‍💻 1️⃣ 使用Spring Boot与Redis(Redisson)实现分布式锁

🛠️ Redis分布式锁概述

Redis作为高性能的键值存储系统,广泛用于分布式锁的实现。Redis的原子操作SETNX(Set if Not Exists)可以在键不存在时设置键值,从而实现锁的机制。为了提升分布式锁的可用性,我们可以使用Redisson库,它封装了Redis的分布式锁功能,提供了更为强大的分布式锁实现,包括自动释放锁、重入锁、读写锁等。

🛠️ 步骤 1:引入Redisson依赖

在Spring Boot项目的pom.xml中添加Redisson的依赖:

<dependencies>
    <dependency>
        <groupId>org.redisson</groupId>
        <artifactId>redisson-spring-boot-starter</artifactId>
        <version>3.16.3</version>
    </dependency>
</dependencies>
  • redisson-spring-boot-starter:这是Redisson的Spring Boot集成模块,简化了与Spring Boot的集成。

🛠️ 步骤 2:配置Redis连接

application.properties中配置Redis连接信息:

spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=yourpassword
spring.redis.timeout=60000
  • spring.redis.host:Redis服务器的主机地址,默认是localhost
  • spring.redis.port:Redis的默认端口是6379
  • spring.redis.password:Redis的密码(如果有)。
  • spring.redis.timeout:连接Redis的超时时间。

🛠️ 步骤 3:配置RedissonClient

创建Redisson客户端配置类来初始化连接:

import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.redisson.spring.starter.RedissonAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RedissonConfig {

    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://localhost:6379");
        return Redisson.create(config);
    }
}

代码解析:

  • config.useSingleServer().setAddress("redis://localhost:6379"):配置Redis服务器地址(redis://表示Redis协议)。
  • Redisson.create(config):创建Redisson客户端实例,用于获取锁。

🛠️ 步骤 4:实现分布式锁

利用Redisson的RLock接口来实现分布式锁。

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
public class DistributedLockService {

    @Autowired
    private RedissonClient redissonClient;

    public void executeTaskWithLock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        try {
            // 尝试获取锁,最多等待10秒,锁的自动释放时间为30秒
            boolean isLockAcquired = lock.tryLock(10, 30, TimeUnit.SECONDS);
            if (isLockAcquired) {
                // 执行任务
                System.out.println("Executing task with lock: " + lockKey);
                // 模拟任务执行
                Thread.sleep(5000);
            } else {
                System.out.println("Could not acquire lock, task skipped.");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();  // 释放锁
            }
        }
    }
}

代码解析:

  • redissonClient.getLock(lockKey):通过Redis连接客户端获取一个RLock对象。
  • lock.tryLock(10, 30, TimeUnit.SECONDS):尝试获取锁,最多等待10秒,锁的自动释放时间为30秒。如果10秒内无法获取锁,则任务跳过。
  • lock.unlock():释放锁。

🛠️ 步骤 5:使用分布式锁防止并发执行

我们可以使用分布式锁来保证定时任务在同一时刻只能有一个实例执行。下面是一个基于Spring的定时任务示例:

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class ScheduledTask {

    @Autowired
    private DistributedLockService distributedLockService;

    @Scheduled(cron = "0 0/1 * * * ?")  // 每分钟执行一次
    public void executeTask() {
        distributedLockService.executeTaskWithLock("taskLockKey");
    }
}

代码解析:

  • @Scheduled(cron = "0 0/1 * * * ?"):定时任务,每分钟执行一次。
  • distributedLockService.executeTaskWithLock("taskLockKey"):通过分布式锁确保任务的互斥执行,避免多个实例同时执行。

🧑‍💻 2️⃣ 使用Spring Boot与Zookeeper实现分布式锁

🛠️ Zookeeper分布式锁概述

Zookeeper是一个分布式协调服务,广泛用于管理配置和实现分布式锁。Zookeeper使用顺序节点来实现分布式锁,每个参与者都创建一个临时顺序节点,并通过比较节点的序号来确定谁拥有锁。Zookeeper保证顺序节点的唯一性和一致性,适合实现高可靠的分布式锁。

🛠️ 步骤 1:引入Zookeeper依赖

pom.xml中引入Zookeeper的依赖:

<dependencies>
    <dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-framework</artifactId>
        <version>5.1.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-recipes</artifactId>
        <version>5.1.0</version>
    </dependency>
</dependencies>
  • curator-framework:Zookeeper的客户端库,用于连接和操作Zookeeper集群。
  • curator-recipes:提供分布式锁等常用功能。

🛠️ 步骤 2:创建Zookeeper客户端

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.springframework.stereotype.Service;

@Service
public class ZookeeperLockService {

    private CuratorFramework client;

    public ZookeeperLockService() {
        client = CuratorFrameworkFactory.newClient("localhost:2181", new ExponentialBackoffRetry(1000, 3));
        client.start();
    }

    public void executeTaskWithLock(String lockPath) throws Exception {
        InterProcessMutex lock = new InterProcessMutex(client, lockPath);
        try {
            // 获取锁,最多等待10秒
            if (lock.acquire(10, TimeUnit.SECONDS)) {
                // 执行任务
                System.out.println("Executing task with Zookeeper lock: " + lockPath);
                Thread.sleep(5000); // 模拟任务执行
            } else {
                System.out.println("Could not acquire lock, task skipped.");
            }
        } finally {
            if (lock.isAcquiredInThisProcess()) {
                lock.release();
            }
        }
    }
}

代码解析:

  • CuratorFrameworkFactory.newClient:初始化Zookeeper客户端。
  • InterProcessMutex:Zookeeper的分布式锁,通过顺序节点实现锁机制。

🛠️ 步骤 3:使用Zookeeper分布式锁

将Zookeeper分布式锁集成到定时任务中,确保任务互斥执行:

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class ZookeeperScheduledTask {

    @Autowired
    private ZookeeperLockService zookeeperLockService;

    @Scheduled(cron = "0 0/1 * * * ?")  // 每分钟执行一次
    public void executeTask() {
        try {
            zookeeperLockService.executeTaskWithLock("/lock/taskLock");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

代码解析:

  • zookeeperLockService.executeTaskWithLock("/lock/taskLock"):通过Zookeeper分布式锁确保任务的互斥执行。

🧑‍💻 3️⃣ 分布式锁的性能优化与锁超时策略

🛠️ 锁超时策略

为避免死锁和资源浪费,分布式锁必须设计合理的超时策略。一般来说,锁应设置一个合理的超时时间,防止因为任务长时间执行而占用锁。

常见的超时策略:

  1. 自动过期:锁会在指定时间内自动过期,避免长时间被持有。
  2. 心跳机制:定期更新锁的过期时间,确保在任务执行期间不会过期。

示例:Redis分布式锁的自动过期

RLock lock = redissonClient.getLock("taskLockKey");
lock.lock(30, TimeUnit.SECONDS);  // 锁30秒,自动释放

代码解析:

  • lock.lock(30, TimeUnit.SECONDS):锁会在30秒后自动释放,防止死锁。

🛠️ 死锁检测与防止

在分布式环境中,死锁是不可忽视的问题。常见的死锁检测与防止方法:

  • 超时重试:设置锁获取的最大等待时间,如果超时未获取锁,则退出。
  • 重试机制:通过指定锁的最大重试次数和间隔时间,避免死锁的发生。

🧑‍💻 4️⃣ 分布式锁的高可用性设计

🛠️ 高可用性设计

为了避免单点故障,分布式锁需要设计成高可用。以下是常见的高可用设计:

  1. Redis集群模式:使用Redis集群提供高可用和数据冗余。
  2. Zookeeper集群:Zookeeper的集群模式保证了分布式锁的高可用性。
  3. 心跳机制:定期检查锁持有者的状态,避免长时间占用锁。

步骤 1:配置Redis集群

spring.redis.cluster.nodes=192.168.0.101:6379,192.168.0.102:6379

步骤 2:配置Zookeeper集群

zookeeper.servers=zk1:2181,zk2:2181,zk3:2181

🚀 小结:实现高效的分布式锁与任务调度

通过Spring Boot与Redis(Redisson)或Zookeeper的集成,我们可以实现高效的分布式锁,确保多个服务实例间的资源互斥访问。结合定时任务调度和分布式锁机制,能够确保定时任务的互斥执行,提高系统的可靠性和稳定性。

🚀 总结:分布式锁的挑战与解决方案

分布式锁的实现虽然解决了分布式系统中的资源争用问题,但在高并发、大规模系统中,性能和高可用性依然是挑战。通过合理的设计与优化,我们可以确保分布式锁在高并发环境下的可靠性和高效性。Spring Boot与Redis、Zookeeper等工具的结合,为开发者提供了强大的支持,帮助构建稳定、高效的分布式锁系统。

🧧福利赠与你🧧

  无论你是计算机专业的学生,还是对编程有兴趣的小伙伴,都建议直接毫无顾忌的学习此专栏「滚雪球学SpringBoot」专栏(全网一个名),bug菌郑重承诺,凡是学习此专栏的同学,均能获取到所需的知识和技能,全网最快速入门SpringBoot,就像滚雪球一样,越滚越大, 无边无际,指数级提升。

  最后,如果这篇文章对你有所帮助,帮忙给作者来个一键三连,关注、点赞、收藏,您的支持就是我坚持写作最大的动力。

  同时欢迎大家关注公众号:「猿圈奇妙屋」 ,以便学习更多同类型的技术文章,免费白嫖最新BAT互联网公司面试题、4000G pdf电子书籍、简历模板、技术文章Markdown文档等海量资料。

✨️ Who am I?

我是bug菌(全网一个名),CSDN | 掘金 | InfoQ | 51CTO | 华为云 | 阿里云 | 腾讯云 等社区博客专家,C站博客之星Top30,华为云多年度十佳博主/价值贡献奖,掘金多年度人气作者Top40,掘金等各大社区平台签约作者,51CTO年度博主Top12,掘金/InfoQ/51CTO等社区优质创作者;全网粉丝合计 30w+;更多精彩福利点击这里;硬核微信公众号「猿圈奇妙屋」,欢迎你的加入!免费白嫖最新BAT互联网公司面试真题、4000G PDF电子书籍、简历模板等海量资料,你想要的我都有,关键是你不来拿。

-End-

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。