Java 分布式锁:原理与实践

news2024/9/22 10:42:08

在分布式系统中,多个节点同时操作共享资源的情况非常普遍。为了保证数据的一致性,分布式锁 应运而生。分布式锁 是一种跨多个服务器的互斥锁,用于协调分布式环境下的资源访问。

本文将介绍 Java 实现分布式锁 的几种常见方式,并结合 Redis、Zookeeper 等框架给出代码实例,探讨如何在实际项目中运用这些技术。


在这里插入图片描述

一、什么是分布式锁?

分布式锁的本质是保证在分布式环境下,多个进程或者节点可以按照正确的顺序来访问共享资源,避免数据冲突。具体场景包括:

  1. 限流控制:确保多个服务器上的相同任务不会被重复执行。
  2. 资源竞争:多个节点对数据库或文件等共享资源的并发修改。
  3. 任务调度:某些任务在同一时间只能由一个节点执行。

二、分布式锁的常见实现方式

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

  1. 基于数据库的分布式锁
  2. 基于 Redis 的分布式锁
  3. 基于 Zookeeper 的分布式锁

下面我们分别介绍这些方法及其在 Java 中的实现。


三、基于数据库的分布式锁

1. 使用数据库表实现分布式锁

通过创建一张 锁表,在锁定资源时插入一条记录,通过 唯一约束 来确保同一时间只有一个节点能成功插入数据。

代码示例:
CREATE TABLE distributed_lock (
    lock_name VARCHAR(64) PRIMARY KEY,
    locked_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Java 实现:

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

@Service
public class DatabaseLockService {

    private final JdbcTemplate jdbcTemplate;

    public DatabaseLockService(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public boolean acquireLock(String lockName) {
        try {
            jdbcTemplate.update("INSERT INTO distributed_lock (lock_name) VALUES (?)", lockName);
            return true;
        } catch (Exception e) {
            return false; // 如果插入失败,表示锁已经被占用
        }
    }

    public void releaseLock(String lockName) {
        jdbcTemplate.update("DELETE FROM distributed_lock WHERE lock_name = ?", lockName);
    }
}

注意事项

  • 加锁失败的处理:如果锁已被占用,可能需要设置重试机制或返回失败。
  • 锁超时机制:为了避免死锁,可以在数据库中设置锁的超时时间。

四、基于 Redis 的分布式锁

Redis 是分布式锁最常用的实现之一,主要依赖于 Redis 提供的 SETNX(SET if Not Exists)命令,来确保只有一个客户端能成功设置某个键值。

1. 使用 Redis 实现分布式锁

代码示例:
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
public class RedisLockService {

    private final StringRedisTemplate redisTemplate;

    public RedisLockService(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    public boolean acquireLock(String lockKey, String lockValue, long timeout) {
        Boolean success = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, timeout, TimeUnit.SECONDS);
        return success != null && success;
    }

    public void releaseLock(String lockKey, String lockValue) {
        String currentValue = redisTemplate.opsForValue().get(lockKey);
        if (lockValue.equals(currentValue)) {
            redisTemplate.delete(lockKey); // 删除锁
        }
    }
}

注意事项

  • 过期时间:设置锁的过期时间,避免因某个节点宕机导致死锁。
  • 锁的唯一标识:每个锁需要有一个唯一标识,释放锁时必须检查该标识是否一致,防止误删他人的锁。

2. 基于 Redisson 实现分布式锁

Redisson 是 Redis 官方推荐的 Java 客户端,提供了原生的分布式锁实现。

代码示例:
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
public class RedissonLockService {

    private final RedissonClient redissonClient;

    public RedissonLockService(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

    public void lock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock(10, TimeUnit.SECONDS); // 获取锁并设置过期时间
    }

    public void unlock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        if (lock.isHeldByCurrentThread()) {
            lock.unlock();
        }
    }
}

优点

  • 原生支持锁的自动续期:避免因超时导致的锁失效。
  • 高性能:Redisson 提供了分布式锁的高效实现,适合高并发场景。

五、基于 Zookeeper 的分布式锁

Zookeeper 是一个分布式协调服务,天生支持分布式锁的实现。Zookeeper 中的 临时节点顺序节点 是实现分布式锁的关键。

1. 使用 Zookeeper 实现分布式锁

通过 Zookeeper 的 临时顺序节点 实现锁机制,保证只有第一个创建的节点可以获取锁,其他节点需要监听前一个节点的删除事件。

代码示例:
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;

import java.io.IOException;
import java.util.Collections;
import java.util.List;

public class ZookeeperLockService {

    private final ZooKeeper zooKeeper;
    private String lockPath;

    public ZookeeperLockService(String zkAddress) throws IOException {
        zooKeeper = new ZooKeeper(zkAddress, 3000, null);
    }

    public boolean acquireLock(String lockName) throws Exception {
        lockPath = zooKeeper.create("/locks/" + lockName + "/lock_", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
        List<String> children = zooKeeper.getChildren("/locks/" + lockName, false);
        Collections.sort(children);
        String smallestChild = children.get(0);
        if (lockPath.endsWith(smallestChild)) {
            return true; // 获取锁成功
        } else {
            // 等待前一个节点删除
            int previousNodeIndex = Collections.binarySearch(children, lockPath.substring(lockPath.lastIndexOf('/') + 1)) - 1;
            String previousNodePath = "/locks/" + lockName + "/" + children.get(previousNodeIndex);
            Stat stat = zooKeeper.exists(previousNodePath, new LockWatcher());
            return stat == null;
        }
    }

    public void releaseLock() throws Exception {
        zooKeeper.delete(lockPath, -1);
    }

    private class LockWatcher implements Watcher {
        @Override
        public void process(WatchedEvent event) {
            synchronized (this) {
                this.notifyAll(); // 通知等待的线程
            }
        }
    }
}

注意事项

  • 节点监听:Zookeeper 提供了节点监听机制,确保当锁被释放时,等待中的节点能够及时获取锁。
  • 临时节点:使用临时节点确保当某个节点挂掉时,锁能够自动释放。

六、分布式锁的最佳实践

  1. 设置过期时间:无论使用 Redis 还是 Zookeeper,都应该为锁设置过期时间,防止死锁。
  2. 锁的粒度:锁的粒度应当尽量细,避免将过多的操作放在同一个锁中,影响系统并发性能。
  3. 重试机制:当锁获取失败时,可以采用 重试机制,或根据业务场景采取合适的降级方案。

七、总结

分布式锁在分布式系统中是一个非常重要的工具,尤其是在处理资源竞争、任务调度等场景时。RedisZookeeper 是目前最常用的分布式锁实现方式。

  • Redis 的分布式锁实现简单高效,适合大多数高并发场景,结合 Redisson 可以进一步简化开发。
  • Zookeeper 的分布式锁适用于需要严格一致性的场景,得益于其 临时顺序节点,能够提供更可靠的锁机制。

在实际使用中,开发者需要根据业务需求选择合适的分布式锁方案,并合理配置锁的过期时间和重试机制,避免因锁竞争或死锁导致的系统性能下降。

分布式锁是保障分布式系统一致性的基础,而合理的锁机制设计能极大提升系统的稳定性和并发处理能力。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2154881.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于VUE的医院抗生素使用审核流程信息化管理系统

开发背景 随着医疗行业的快速发展和信息技术的不断进步&#xff0c;医院内部管理系统的信息化建设变得尤为重要。抗生素作为治疗感染性疾病的重要药物&#xff0c;在临床使用过程中需要严格控制以避免滥用导致的耐药性问题。传统的抗生素使用审核流程往往依赖于人工审核&#x…

一,初始 MyBatis-Plus

一&#xff0c;初始 MyBatis-Plus 文章目录 一&#xff0c;初始 MyBatis-Plus1. MyBatis-Plus 的概述2. 入门配置第一个 MyBatis-Plus 案例3. 补充说明&#xff1a;3.1 通用 Mapper 接口介绍3.1.1 Mapper 接口的 “增删改查”3.1.1.1 查询所有记录3.1.1.2 插入一条数据3.1.1.3 …

推荐3个AI论文、AI查重、AI降重工具

什么是AI论文、AI查重、AI降重工具&#xff1f; AI论文 AI论文指的是以人工智能&#xff08;AI&#xff09;相关主题为研究对象的学术论文。这类论文通常包含以下内容&#xff1a; 研究问题&#xff1a;针对某个特定的AI问题或领域的研究。方法&#xff1a;介绍用于解决问题…

UnLua实现继承

一、在蓝图中实现继承 1、创建父类&#xff0c;并绑定Lua脚本 2、创建子类蓝图&#xff0c;如果先创建的子类&#xff0c;可以修改父类继承 注意&#xff0c;提示选择继承父类的接口&#xff01; 二、在Lua中实现继承 1、在父类Lua脚本中实现函数 BP_CharacterBase.lua func…

SysML图例-智能家居

DDD领域驱动设计批评文集>> 《软件方法》强化自测题集>> 《软件方法》各章合集>>

spring boot(学习笔记第二十课) vue + spring boot前后端分离项目练习

spring boot(学习笔记第二十课) vue spring boot前后端分离项目练习 学习内容&#xff1a; 后端程序构建前端程序构建 1. 后端程序构建 前后端分离结构 前后端就是前端程序和后端程序独立搭建&#xff0c;通过Restful API进行交互&#xff0c;进行松耦合的设计。后端程序构建…

【吊打面试官系列-MySQL面试题】MySQL_fetch_array 和 MySQL_fetch_object 的区别是什么?

大家好&#xff0c;我是锋哥。今天分享关于【MySQL_fetch_array 和 MySQL_fetch_object 的区别是什么&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; MySQL_fetch_array 和 MySQL_fetch_object 的区别是什么&#xff1f; 以下是 MySQL_fetch_array 和 MySQL_fe…

VisionPro - 基础 - 模板匹配技术和在VP中的使用 - PMAlign - PatMax (5)- 非线性模板变形匹配

前言&#xff1a; 本机继续对VP的PatMax 算子进行说明&#xff1a;本节讲非线性变形的模板匹配。 Non-Linear Pattern Deformation By default, PatMax requires that each boundary point in the instance of a pattern found in a run-time image closely correspond to a b…

低空经济火爆,稀缺无人机教员培训详解

随着科技的飞速发展和低空经济的日益火爆&#xff0c;无人机技术已广泛应用于航拍、农业、物流、救援、环境监测等多个领域&#xff0c;成为推动社会经济发展的新引擎。然而&#xff0c;无人机行业的快速发展也催生了对专业无人机教员的迫切需求。本文将从基础理论学习、实操技…

[Redis][List]详细讲解

目录 0.前言1.常用命令1.LPUSH / RPUSH2.LPUSHX / RPUSHX3.LRANGE4.LPOP / RPOP5.LINDEX6.LINSERT7.LLEN8.LREM9.LTRIM10.LSET 2.阻塞版本命令0.是什么&#xff1f;1.BLPOP / BRPOP 3.内部编码(旧版本&#xff0c;仅供参考)1.ziplist(压缩链表)2.linkedlist(链表)3.quicklist(快…

yolov8旋转目标检测之绝缘子检测-从数据加载到模型训练、部署

YOLOv8 是 YOLO (You Only Look Once) 系列目标检测算法的最新版本&#xff0c;以其高速度和高精度而著称。在电力行业中&#xff0c;绝缘子是电力传输线路上的重要组件之一&#xff0c;它们用于支撑导线并保持电气绝缘。由于长期暴露在户外环境中&#xff0c;绝缘子容易出现损…

详细分析Spring的动态代理机制

文章目录 1. JDK动态代理和CGLIB动态代理的区别1.1 适用范围1.2 生成的代理类1.3 调用方式 2. 问题引入3. 创建工程验证 Spring 默认采用的动态代理机制3.1 引入 Maven 依赖3.2 UserController.java3.3 UserService.java3.4 UserServiceImpl.java&#xff08;save方法添加了Tra…

【Python】探索 TensorFlow:构建强大的机器学习模型

TensorFlow 是一个开源的深度学习框架&#xff0c;由 Google 开发&#xff0c;广泛应用于机器学习和人工智能领域。自从 2015 年推出以来&#xff0c;它已成为研究人员、开发者和数据科学家们不可或缺的工具。TensorFlow 提供了灵活、高效的工具集&#xff0c;可以帮助我们构建…

API接口在金融科技领域的创新应用

导语&#xff1a; 随着互联网的发展和技术的进步&#xff0c;API接口在金融科技领域的创新应用越来越受到关注。本文将介绍API接口的基本概念&#xff0c;以及它在金融科技领域的应用案例。 第一部分&#xff1a;API接口简介及原理 API是Application Programming Interface&…

2021-03-03人工智能应用的就业效应

【摘要】文章从人工智能的概念出发&#xff0c;在总结已有研究方法的基础上&#xff0c;回顾了人工智能对就业的产业分布、岗位、工资等方面影响的理论与实证研究。文章发现&#xff0c;人工智能技术在替代部分岗位、促使劳动力在不同产业间流动的同时&#xff0c;还会加快劳动…

java 获取集合a比集合b多出来的对象元素

public class OrderListEntity {/*** deprecated 对象集合的处理* param aData 集合a* param bData 集合b* return 返回集合a比集合b多出来的部分, 通过id判断*/public static List<OrderListEntity> AHasMoreThanBData(List<OrderListEntity> aData, List<Ord…

LEAN 赋型唯一性(Unique Typing)之 Church-Rosser 定理 (Church-Rosser Theorem)及 赋型唯一性的证明

有了并行K简化的概念及其属性&#xff0c;以及其在LEAN类型理论中的相关证明&#xff0c;就可以证明&#xff0c;在K简化下的Church-Rosser 定理。即&#xff1a; 其过程如下&#xff1a; 证明如下&#xff1a; 其中的 lemma 4.9 和 4.10 &#xff0c;及 4.8 是 这整个证明过程…

华为云centos7.9按装ambari 2.7.5 hostname 踩坑记录

华为云centos7.9按装ambari 2.7.5踩坑记录 前言升华总结 前言 一般都是废话&#xff0c;本人专业写bug业余运维。起初找了三台不废弃的台式机&#xff0c;开始重装centos系统&#xff0c;开始了HDP3.1.5Ambari2.7.5安装。 推荐一波好文&#xff0c;一路长绿。跑了一段时间没啥…

2024年最新版TypeScript学习笔记——泛型、接口、枚举、自定义类型等知识点

今天带来的是来自尚硅谷禹神2024年8月最新的TS课程的学习笔记&#xff0c;不得不说禹神讲的是真的超级棒&#xff01; 文章目录 TS入门JS中的困扰静态类型检查编译TS命令行编译自动化编译 类型检查变量和函数类型检查字面量类型检查 类型推断类型声明声明对象类型声明函数类型…

深度学习02-pytorch-08-自动微分模块

​​​​​​​ 其实自动微分模块&#xff0c;就是求相当于机器学习中的线性回归损失函数的导数。就是求梯度。 反向传播的目的&#xff1a; 更新参数&#xff0c; 所以会使用到自动微分模块。 神经网络传输的数据都是 float32 类型。 案例1: 代码功能概述&#xff1a; 该…