分布式锁的3种实现!附代码

分布式锁是一种用于保证分布式系统中多个进程或线程同步访问共享资源的技术。同时它又是面试中的常见问题,所以我们本文就重点来看分布式锁的具体实现(含实现代码)。

在分布式系统中,由于各个节点之间的网络通信延迟、故障等原因,可能会导致数据不一致的问题。分布式锁通过协调多个节点的行为,保证在任何时刻只有一个节点可以访问共享资源,以避免数据的不一致性和冲突。

1.分布式锁要求

分布式锁通常需要满足以下几个要求:

  1. 互斥性:在任意时刻只能有一个客户端持有锁。
  2. 不会发生死锁:即使持有锁的客户端发生故障,也能保证锁最终会被释放。
  3. 具有容错性:分布式锁需要能够容忍节点故障等异常情况,保证系统的稳定性。

2.实现方案

在 Java 中,实现分布式锁的方案有多种,包括:

  1. 基于数据库实现的分布式锁:可以通过数据库的乐观锁或悲观锁实现分布式锁,但是由于数据库的 IO 操作比较慢,不适合高并发场景。
  2. 基于 ZooKeeper 实现的分布式锁:ZooKeeper 是一个高可用性的分布式协调服务,可以通过它来实现分布式锁。但是使用 ZooKeeper 需要部署额外的服务,增加了系统复杂度。
  3. 基于 Redis 实现的分布式锁:Redis 是一个高性能的内存数据库,支持分布式部署,可以通过Redis的原子操作实现分布式锁,而且具有高性能和高可用性。

3.数据库分布式锁

数据库的乐观锁或悲观锁都可以实现分布式锁,下面分别来看。

3.1 悲观锁

在数据库中使用 for update 关键字可以实现悲观锁,我们在 Mapper 中添加 for update 即可对数据加锁,实现代码如下:

<!-- UserMapper.xml -->
<select id="selectByIdForUpdate" resultType="User">SELECT * FROM user WHERE id = #{id} FOR UPDATE
</select>

在 Service 中调用 Mapper 方法,即可获取到加锁的数据:

@Transactional
public void updateWithPessimisticLock(int id, String name) {User user = userMapper.selectByIdForUpdate(id);if (user != null) {user.setName(name);userMapper.update(user);} else {throw new RuntimeException("数据不存在");}
}

3.2 乐观锁

在 MyBatis 中,可以通过给表添加一个版本号字段来实现乐观锁。在 Mapper 中,使用 标签定义更新语句,同时使用 set 标签设置版本号的增量。

<!-- UserMapper.xml -->
<update id="updateWithOptimisticLock">UPDATE user SETname = #{name},version = version + 1WHERE id = #{id} AND version = #{version}
</update>

在 Service 中调用 Mapper 方法,需要传入更新数据的版本号。如果更新失败,说明数据已经被其他事务修改,具体实现代码如下:

@Transactional
public void updateWithOptimisticLock(int id, String name, int version) {User user = userMapper.selectById(id);if (user != null) {user.setName(name);user.setVersion(version);int rows = userMapper.updateWithOptimisticLock(user);if (rows == 0) {throw new RuntimeException("数据已被其他事务修改");}} else {throw new RuntimeException("数据不存在");}
}

4.Zookeeper 分布式锁

在 Spring Boot 中,可以使用 Curator 框架来实现 ZooKeeper 分布式锁,具体实现分为以下 3 步:

  1. 引入 Curator 和 ZooKeeper 客户端依赖;
  2. 配置 ZooKeeper 连接信息;
  3. 编写分布式锁实现类。

4.1 引入 Curator 和 ZooKeeper

<dependency><groupId>org.apache.curator</groupId><artifactId>curator-framework</artifactId><version>latest</version>
</dependency>
<dependency><groupId>org.apache.curator</groupId><artifactId>curator-recipes</artifactId><version>latest</version>
</dependency>
<dependency><groupId>org.apache.zookeeper</groupId><artifactId>zookeeper</artifactId><version>latest</version>
</dependency>

4.2 配置 ZooKeeper 连接

在 application.yml 中添加 ZooKeeper 连接配置:

spring:zookeeper:connect-string: localhost:2181namespace: demo

4.3 编写分布式锁实现类

@Component
public class DistributedLock {@Autowiredprivate CuratorFramework curatorFramework;/*** 获取分布式锁** @param lockPath   锁路径* @param waitTime   等待时间* @param leaseTime  锁持有时间* @param timeUnit   时间单位* @return 锁对象* @throws Exception 获取锁异常*/public InterProcessMutex acquire(String lockPath, long waitTime, long leaseTime, TimeUnit timeUnit) throws Exception {InterProcessMutex lock = new InterProcessMutex(curatorFramework, lockPath);if (!lock.acquire(waitTime, timeUnit)) {throw new RuntimeException("获取分布式锁失败");}if (leaseTime > 0) {lock.acquire(leaseTime, timeUnit);}return lock;}/*** 释放分布式锁** @param lock 锁对象* @throws Exception 释放锁异常*/public void release(InterProcessMutex lock) throws Exception {if (lock != null) {lock.release();}}
}

5.Redis 分布式锁

我们可以使用 Redis 客户端 Redisson 实现分布式锁,它的实现步骤如下:

  1. 添加 Redisson 依赖
  2. 配置 Redisson 连接信息
  3. 编写分布式锁代码类

5.1 添加 Redisson 依赖

在 pom.xml 中添加如下配置:

<!-- https://mvnrepository.com/artifact/org.redisson/redisson-spring-boot-starter -->
<dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.20.0</version>
</dependency>

5.2 配置 Redisson 连接

在 Spring Boot 项目的配置文件 application.yml 中添加 Redisson 配置:

spring:data:redis:host: localhostport: 6379database: 0
redisson:codec: org.redisson.codec.JsonJacksonCodecsingle-server-config:address: "redis://${spring.data.redis.host}:${spring.redis.port}"database: "${spring.data.redis.database}"password: "${spring.data.redis.password}"

5.3 编写分布式锁代码类

import jakarta.annotation.Resource;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.stereotype.Service;import java.util.concurrent.TimeUnit;@Service
public class RedissonLockService {@Resourceprivate Redisson redisson;/*** 加锁** @param key     分布式锁的 key* @param timeout 超时时间* @param unit    时间单位* @return*/public boolean tryLock(String key, long timeout, TimeUnit unit) {RLock lock = redisson.getLock(key);try {return lock.tryLock(timeout, unit);} catch (InterruptedException e) {Thread.currentThread().interrupt();return false;}}/*** 释放分布式锁** @param key 分布式锁的 key*/public void unlock(String key) {RLock lock = redisson.getLock(key);lock.unlock();}
}

6.Redis VS Zookeeper

Redis 和 ZooKeeper 都可以用来实现分布式锁,它们在实现分布式锁的机制和原理上有所不同,具体区别如下:

  1. 数据存储方式:Redis 将锁信息存储在内存中,而 ZooKeeper 将锁信息存储在 ZooKeeper 的节点上,因此 ZooKeeper 需要更多的磁盘空间。
  2. 锁的释放:Redis 的锁是通过设置锁的过期时间来自动释放的,而 ZooKeeper 的锁需要手动释放,如果锁的持有者出现宕机或网络中断等情况,需要等待锁的超时时间才能自动释放。
  3. 锁的竞争机制:Redis 使用的是单机锁,即所有请求都直接连接到同一台 Redis 服务器,容易发生单点故障;而 ZooKeeper 使用的是分布式锁,即所有请求都连接到 ZooKeeper 集群,具有较好的可用性和可扩展性。
  4. 一致性:Redis 的锁是非严格意义下的分布式锁,因为在多台机器上运行多个进程时,由于 Redis 的主从同步可能会存在数据不一致的问题;而 ZooKeeper 是强一致性的分布式系统,保证了数据的一致性。
  5. 性能:Redis 的性能比 ZooKeeper 更高,因为 Redis 将锁信息存储在内存中,而 ZooKeeper 需要进行磁盘读写操作。

总之,Redis 适合实现简单的分布式锁场景,而 ZooKeeper 适合实现复杂的分布式协调场景,也就是 ZooKeeper 适合强一致性的分布式系统。

强一致性是指系统中的所有节点在任何时刻看到的数据都是一致的。ZooKeeper 中的数据是有序的树形结构,每个节点都有唯一的路径标识符,所有节点都共享同一份数据,当任何一个节点对数据进行修改时,所有节点都会收到通知,更新数据,并确保数据的一致性。 在 ZooKeeper 中,强一致性体现在数据的读写操作上。ZooKeeper 使用 ZAB(ZooKeeper Atomic Broadcast)协议来保证数据的一致性,该协议确保了数据更新的顺序,所有的数据更新都需要经过集群中的大多数节点确认,保证了数据的一致性和可靠性。

小结

在 Java 中,使用数据库、ZooKeeper 和 Redis 都可以实现分布式锁。但数据库 IO 操作比较慢,不适合高并发场景;Redis 执行效率最高,但在主从切换时,可能会出现锁丢失的情况;ZooKeeper 是一个高可用性的分布式协调服务,可以保证数据的强一致性,但是使用 ZooKeeper 需要部署额外的服务,增加了系统复杂度。所以没有最好的解决方案,只有最合适自己的解决方案。

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

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

相关文章

如何将pdf文件变小?三招学会pdf文件压缩

在日常工作和生活中&#xff0c;我们常常需要处理大量的PDF文件&#xff0c;然而&#xff0c;有时候这些文件的大小会成为问题&#xff0c;比如文件太大无法通过邮件发送、在线上传&#xff0c;或者在手机上打开时读取缓慢等&#xff0c;为了解决这些问题&#xff0c;我们需要将…

PN结解释

基本原理 PN结由P和N组成 硅掺杂硼&#xff0c;缺少电子&#xff0c;显正电&#xff0c;就是P&#xff08;Positive&#xff09; 硅掺杂磷&#xff0c;多出电子&#xff0c;显负电&#xff0c;就是N&#xff08;Negative&#xff09; 将P和N拼接 左边代表游离的电子&#xf…

flex布局学习笔记

flex布局 推荐网址&#xff1a;弹性框完整指南 |CSS-Tricks - CSS-Tricks 基础知识和术语 由于flexbox是一个完整的模块&#xff0c;而不是一个单一的属性&#xff0c;它涉及很多事情&#xff0c;包括它的整套属性。其中一些应该在容器&#xff08;父元素&#xff0c;称为“…

这些嵌入式系统安全性的知识你需要了解

这可能是 工程师在面对嵌入式系统应用程序的安全性时可能提出的第一个问题。 不幸的是&#xff0c;有大量的“安全软件包”可用&#xff0c;并且对安全性不熟悉的嵌入式工程师可能只将安全性称为加密或病毒防护。尽管加密是安全性的一种工具&#xff0c;而病毒扫描程序从技术上…

Python爬虫实现(requests、BeautifulSoup和selenium)

Python爬虫实现&#xff08;requests、BeautifulSoup和selenium&#xff09; requests实现 Python requests 是一个常用的 HTTP 请求库&#xff0c;可以方便地向网站发送 HTTP 请求&#xff0c;并获取响应结果。 下载requests库 pip install requests 实例&#xff1a; # 导…

基于Android系统图书管理系统

摘要 随着移动终端使用率的快速增加&#xff0c;Android智能产品已日益成为越来越多的人们选择的移动终端产品。伴随着Android智能手机与平板电脑已经在我们生活大量的使用&#xff0c;越来越多的基于Android开发平台的应用也随之产生。 便捷的图书检索和借阅&#xff1a;用户可…

ACL(访问控制列表)

文章目录 一、ACL定义常见功能 二、基于ACL的包过滤定义包过滤的方向包过滤的工作流程注意事项 三、ACL分类四、常用命令 首先可以看下思维导图&#xff0c;以便更好的理解接下来的内容。 一、ACL 定义 ACL&#xff0c;也称为访问控制列表&#xff0c;是一种网络安全工具&…

【爬虫】8.1. 深度使用tesseract-OCR技术识别图形验证码

深度使用tesseract-OCR技术识别图形验证码 文章目录 深度使用tesseract-OCR技术识别图形验证码1. OCR技术2. 准备工作3. 简单作用了解3.1. 验证码图片爬取-screenshot_as_png3.2. 识别测试-image_to_string3.2.1. 正确识别3.2.2. 错误识别3.2.3. 灰度调节 3.3. 识别实战-使用im…

卫星地图-航拍影像-叠加配准套合(ArcGIS版)

卫星地图-航拍影像-叠加配准套合(ArcGIS版) 发布时间&#xff1a;2018-01-17 版权&#xff1a;BIGEMAP 第一步 工具准备 BIGEMAP地图下载器&#xff1a;Bigemap系列产品-GIS行业基础软件kml\shp 相关教程&#xff1a;CAD文件直接导入BIGEMAP进行套合配准&#xff08;推荐&am…

TC测试自动化Shell脚本

在使用TC测试的发现手动进行丢包延迟抖动等场景的组合以及TC命令的切换效率很低&#xff0c;写了一个脚本可以提升效率&#xff0c;也可以根据自己的需求进行脚本更改&#xff01; 使用方法&#xff1a; 1&#xff09;运行sh脚本 2&#xff09;输入TC想要限制的网卡名和服务器…

IDEA 快捷键大全

目录 一、文本编辑 二、光标操作 三、文本选择 四、代码折叠 五、辅助编码 六、上下文导航 七、查找操作 八、符号导航 九、代码分析 十、运行和调试 十一、代码重构 一、文本编辑 Ctrl Shift V&#xff1a;从历史选择粘贴 Ctrl D&#xff1a;复制光标所在行 …

数字信封技术概论

数字信封技术是一种通过加密手段实现信息保密性和验证的技术&#xff0c;它在保护敏感信息传输过程中得到了广泛应用。本文将详细介绍数字信封技术的原理、实现和应用场景。 一、数字信封技术的原理 数字信封技术是一种将对称密钥通过非对称加密手段分发的方法。在数字信封中…

【Fiddler】mac m1 机器上使用 fiddler 抓取接口

mac m1 机器上使用 fiddler 抓取接口&#xff08;非虚拟机模式&#xff09; author: jwensh date:2023.09.12 文章目录 mac m1 机器上使用 fiddler 抓取接口&#xff08;非虚拟机模式&#xff09;1. 环境准备2. 进行配置3. 使用情况 1. 环境准备 想要抓取 mac 上浏览器的接口&a…

JAVA8接口使用问题

JAVA8接口使用问题 文章目录 JAVA8接口使用问题1、默认方法冲突问题&#xff08;1&#xff09;亲爹优先原则&#xff08;2&#xff09;左右为难 2、常量冲突问题 1、默认方法冲突问题 &#xff08;1&#xff09;亲爹优先原则 当一个类&#xff0c;既继承一个父类&#xff0c;…

Android10 SystemUI系列 问题合集(一)开机后发现控制中心无法全部展开,控制中心tile长按无响应

一、前言 这个问题是在笔者偶然的一次情况下遇到的,前前后后花了几个小时才找到问题根源,优化了一些时间找到了哪个改动导致了原因。下面记录一下分析过程 二、准备工作 首先需要知道,在Android10 中有一个 StatusBar 类很重要 代码路径frameworks\base\packages\System…

C语言数组和指针笔试题(一)(一定要看)

目录 一维数组例题1例题2例题3例题4例题5例题6例题7例题8例题9例题10例题输出结果 字符数组例题1例题2例题3例题4例题5例题6例题7 一维数组 int a[] {1,2,3,4}; 1:printf("%d\n",sizeof(a)); 2:printf("%d\n",sizeof(a0)); 3:printf("%d\n",si…

为什么要选择客服外包团队?

在这个快节奏的商业时代&#xff0c;优质的客户服务已经成为企业赢得竞争的关键因素之一。然而&#xff0c;很多企业在客服方面的投入往往受到各种限制&#xff0c;例如人力、财力等。为了解决这些问题&#xff0c;一种新型的服务模式——客服外包团队逐渐走入了企业的视线。本…

如何在JavaScript中实现链式调用(chaining)?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ JavaScript中的链式调用⭐ 示例⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff01;这个专栏是为那些对Web开发感兴…

山东ITSS信息化资质

一、什么是ITSS认证&#xff1f; ITSS&#xff08;Information Technology Service Standards&#xff0c;信息技术服务标准&#xff0c;简称ITSS&#xff09;是一套成体系和综合配套的信息技术服务标准库&#xff0c;全面规范了信息技术服务产品及其组成要素&#xff0c;用于…