什么是分布式锁?Redis的分布式锁又是什么?

什么是分布式锁?

分布式锁是一种用于解决分布式系统中多节点对共享资源并发访问问题的机制。在分布式系统中,多个服务器实例或服务进程可能同时操作某个共享资源(如数据库记录、缓存条目、文件等),导致数据不一致或竞争条件。分布式锁可以确保同一时刻只有一个节点可以访问或修改该资源,避免并发问题。

1. 分布式锁的概念和关键属性

分布式锁与传统单机环境下的锁类似,但它的难点在于分布式环境中多个节点之间如何协同工作。分布式锁的实现需要考虑网络延迟、节点失效、时钟不同步等问题,因此需要具备以下关键属性:

1.1 互斥性(Mutual Exclusion)

在分布式锁下,只有一个节点能够获取锁并访问共享资源。其他节点在获取锁之前不能操作该资源,确保不会发生并发冲突。

1.2 死锁避免(Deadlock Avoidance)

在分布式系统中,如果一个节点获取锁后未能及时释放(如系统崩溃、网络故障等),需要有机制防止锁被永久持有。为此,分布式锁通常设置超时时间(TTL),当锁超时后自动释放,其他节点可以重新竞争锁。

1.3 容错性(Fault Tolerance)

分布式系统中的节点可能随时崩溃或发生网络分区。分布式锁的设计需要确保即使在这些情况下,锁的状态也是一致的,且能够有效恢复。通常,分布式锁通过中心化协调机制(如 Zookeeper)或强一致性协议来保证一致性。

1.4 可重入性(Reentrancy)

有时一个节点可能需要多次获取同一把锁。可重入锁允许同一线程多次获取锁,而不会导致死锁或阻塞。如果不支持可重入性,每次都需要手动释放锁,使用起来会更加复杂。

1.5 公平性(Fairness)

某些场景需要确保锁是公平的,即按照请求的顺序,依次让各个节点获取锁,防止某些节点长时间无法获取锁。公平性通常通过队列化的方式实现。

2. 分布式锁的常见实现方式

2.1 基于数据库的分布式锁

这是最简单的分布式锁实现方式,通过数据库的记录来模拟锁。具体实现步骤如下:

  • 当某个节点要获取锁时,它会在数据库中插入一条唯一标识的记录(比如 lock_id)。
  • 如果插入成功,则表示该节点成功获取了锁;如果插入失败(记录已存在),则表示锁已被其他节点持有。
  • 完成任务后,该节点删除记录,释放锁。

优点

  • 实现简单,不需要额外的中间件。
  • 数据库通常是系统中已有的组件,容易集成。

缺点

  • 数据库性能较低,不适合高并发场景。
  • 容错性差,如果节点在持有锁时崩溃,锁可能无法及时释放,需要额外的超时机制来恢复。
2.2 基于 Redis 的分布式锁

Redis 是一种高性能的内存缓存数据库,支持简单的键值操作,因此在分布式锁的实现中广泛使用。Redis 分布式锁通常通过 SETNX 命令实现:

  • SETNX(“SET if Not Exists”)是一个原子操作,当某个节点想获取锁时,使用该命令在 Redis 中创建一条记录。如果键不存在(锁未被占用),则创建成功并返回获取锁成功的状态;如果键已存在(锁已被其他节点占用),则返回失败。
  • 同时通过 EXPIRE 命令给锁设置一个自动过期时间(TTL),以防止由于节点崩溃导致锁永久存在。
  • 节点完成任务后,使用 DEL 命令删除该键,释放锁。

优点

  • 性能高,适合高并发场景。
  • Redis 支持集群模式,具有较好的扩展性。

缺点

  • 在网络分区或 Redis 崩溃时,可能会发生锁丢失或锁状态不一致的问题。
  • 需要考虑原子性操作和锁过期时间,确保锁的准确性。比如,如果客户端在执行完任务之前崩溃,锁过期后可能被其他客户端获取,但任务还未真正完成,这时会导致并发问题。
2.3 基于 Zookeeper 的分布式锁

Zookeeper 是一个开源的分布式协调服务,使用强一致性协议(如 ZAB 协议)来确保分布式系统中的数据一致性。它可以通过创建临时顺序节点来实现分布式锁:

  • 每个节点想获取锁时,会在 Zookeeper 上创建一个临时顺序节点(Ephemeral Sequential Node)。
  • 这些节点会按照顺序排列,最小的节点表示锁的拥有者,其他节点则监听比自己小的节点是否被删除。如果最小节点被删除(即锁被释放),下一个节点会获得锁。
  • 临时节点的特点是如果客户端与 Zookeeper 的会话断开,节点会自动删除,确保锁的释放。

优点

  • 高一致性,适合强一致性要求的场景。
  • 具有天然的容错能力,Zookeeper 的会话断开时锁自动释放。

缺点

  • 性能相对较低,特别是在高并发环境下,由于 Zookeeper 的同步机制,可能导致响应延迟。
  • Zookeeper 部署较复杂,需要更多的系统资源。

3. 分布式锁的实际应用场景

3.1 库存扣减

在电商系统中,当用户同时购买商品时,多个服务实例会同时修改库存。如果没有分布式锁,可能会导致超卖的情况。通过分布式锁可以确保同时只有一个实例能够操作库存,避免数据不一致。

3.2 定时任务调度

在分布式环境中,多个服务实例可能同时调度同一个定时任务。为了避免任务重复执行,可以使用分布式锁保证同一时刻只有一个实例在执行该任务。

3.3 分布式事务控制

在某些分布式事务场景下,多个节点需要协调对共享资源的操作。分布式锁可以用来确保这些操作按照正确的顺序执行,从而保证事务的一致性。

4. 分布式锁的最佳实践和注意事项

4.1 锁的过期时间设置

锁的过期时间(TTL)应该设置得合理,既不能太短,避免任务还未完成锁就被释放,也不能太长,防止节点崩溃后锁无法及时释放。如果任务执行时间不确定,可以考虑动态调整锁的过期时间。

4.2 原子性操作

在释放锁时,确保解锁操作的原子性。如果客户端在释放锁之前发生了崩溃,可能会导致锁被意外释放,其他客户端错误地获取锁。可以通过 Redis 的 Lua 脚本或 Zookeeper 的原子操作来保证锁的正确释放。

4.3 锁的可见性

在某些复杂场景中,需要确保分布式锁的可见性。比如在 Redis 集群中,需要确保多个节点之间的锁状态是一致的。如果锁状态在节点之间不同步,可能导致多个节点同时获取锁,从而失去锁的作用。

通过合理设计分布式锁机制,可以有效避免分布式系统中常见的并发问题,提升系统的可靠性和一致性。在实际应用中,需要根据具体的业务场景和技术栈选择合适的分布式锁实现方式。

Redis的分布式锁又是什么?

Redis 分布式锁

Redis 分布式锁是基于 Redis 数据库的键值存储机制,通过原子操作确保多个客户端(或节点)之间可以安全地互斥访问共享资源。Redis 分布式锁常用在高并发场景下,能够确保在分布式环境中,某个时刻只有一个客户端可以对共享资源进行修改。

Redis 分布式锁的关键命令

Redis 提供了几个基础命令来实现分布式锁:

  1. SETNX(SET if Not Exists):用于实现互斥锁。
    • SETNX key value:如果 key 不存在,则设置 key=value 并返回 1;如果 key 已存在,则返回 0。这是一个原子操作,用于确保只有一个客户端能够成功设置该键。
  2. EXPIRE:用于设置键的过期时间。
    • EXPIRE key seconds:为 key 设置一个秒数级别的超时时间,防止客户端获取锁后崩溃导致锁永久无法释放。
  3. DEL:用于释放锁。
    • 当操作完成后,客户端可以通过 DEL key 删除锁,释放对共享资源的占用。

SETNX 和 EXPIRE 的组合:加锁的思路

  • SETNX 是 Redis 实现分布式锁的核心命令,它能够确保一个锁只能被一个客户端获取。因为是原子操作,多个客户端同时执行 SETNX 只有一个能够成功。
  • 由于客户端可能在持有锁期间崩溃,导致锁永远不释放,因此必须为锁设置一个过期时间(TTL)。这可以通过 EXPIRE 命令或 Redis 5.0 之后的 SET 命令选项 NXEX 组合来实现一次性操作。
加锁的步骤:
  1. 客户端执行 SETNX 创建锁,成功则继续执行任务。
  2. 同时通过 EXPIRE 命令为锁设置过期时间,避免死锁。
  3. 任务执行完毕后,客户端通过 DEL 命令释放锁。

Redis 分布式锁的完整实现思路

1. 加锁流程
  • 客户端尝试获取锁。
  • 如果锁已存在(SETNX 返回 0),则说明其他客户端持有锁,当前客户端需要等待或重试。
  • 如果成功获取锁,则继续进行任务操作,并为锁设置一个过期时间。
2. 解锁流程
  • 完成操作后,客户端释放锁,确保其他客户端可以获取锁。
3. 超时机制
  • 锁会自动在设置的 TTL(过期时间)后被释放,以防止某个客户端崩溃或网络问题导致锁永远无法释放。

相关命令行和代码实现

Redis 命令行示例:
  1. 加锁(使用 SETNX 设置锁):
SETNX my_lock "unique_client_id"

如果 my_lock 键不存在,返回 1,加锁成功;如果存在,返回 0,表示锁被其他客户端占用。

  1. 设置锁的超时(防止死锁):
EXPIRE my_lock 10

my_lock 设置 10 秒的超时,超过 10 秒后自动释放。

  1. 解锁(释放锁):
DEL my_lock

任务完成后,删除 my_lock 锁,释放资源。

Redis 分布式锁的完整代码实现(使用 Redis 客户端库)

下面是一个基于 Redis 客户端的 Java 示例,展示如何实现分布式锁:

import redis.clients.jedis.Jedis;public class RedisDistributedLock {private Jedis jedis;private String lockKey;private int lockTimeout; // 锁超时时间(秒)public RedisDistributedLock(Jedis jedis, String lockKey, int lockTimeout) {this.jedis = jedis;this.lockKey = lockKey;this.lockTimeout = lockTimeout;}// 尝试获取锁public boolean tryLock(String clientId) {// 通过SETNX命令尝试加锁Long result = jedis.setnx(lockKey, clientId);if (result == 1) {  // 加锁成功jedis.expire(lockKey, lockTimeout);  // 设置锁的超时时间return true;}return false;}// 解锁public void unlock(String clientId) {// 使用 Lua 脚本保证原子性操作,只有持有锁的客户端才能释放锁String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then " +"return redis.call('del', KEYS[1]) else return 0 end";jedis.eval(luaScript, 1, lockKey, clientId);}public static void main(String[] args) {Jedis jedis = new Jedis("localhost", 6379);  // 连接到本地 RedisRedisDistributedLock lock = new RedisDistributedLock(jedis, "my_lock", 10);String clientId = "unique_client_id";  // 当前客户端的唯一标识if (lock.tryLock(clientId)) {try {// 执行临界区代码System.out.println("锁已获取,执行任务...");Thread.sleep(5000);  // 模拟任务执行} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock(clientId);  // 释放锁System.out.println("任务完成,锁已释放");}} else {System.out.println("锁已被其他客户端占用,稍后重试");}jedis.close();}
}

代码说明:

  • tryLock():尝试通过 SETNX 加锁,如果成功,则通过 EXPIRE 设置锁的过期时间。
  • unlock():使用 Lua 脚本保证只有持有锁的客户端才能释放锁,避免其他客户端错误释放。

Lua 脚本解释:

通过 Redis 的 eval 命令执行 Lua 脚本,在解锁时验证持有锁的客户端是否与释放锁的客户端匹配。这个操作是原子的,可以避免并发问题。

Redis 分布式锁的图示:

1. 加锁流程:
  • 客户端 A 尝试获取锁 my_lock,通过 SETNX 成功设置锁。
  • Redis 设置该锁,同时通过 EXPIRE 设置锁的过期时间,避免死锁。
  • 其他客户端(如客户端 B)尝试获取锁时发现锁已存在,必须等待锁释放或重试。
2. 解锁流程:
  • 客户端 A 完成任务后,通过 DEL 释放锁。
  • 客户端 B 尝试重新获取锁,成功获取并继续任务。

图示(加锁和解锁过程):

上面图示展示了 Redis 分布式锁的加锁和解锁过程:

  1. 加锁
    • 客户端 A 通过 SETNX 成功获取锁,Redis 设置 my_lock 键,并给该锁设置超时时间(TTL)。
    • 客户端 B 尝试获取锁,但因锁已存在而失败,需要等待或重试。
  2. 解锁
    • 客户端 A 完成任务后通过 DEL 释放锁。
    • 客户端 B 再次尝试获取锁,成功获取并继续任务。

这种机制确保了在分布式环境下,同一时刻只有一个客户端能够操作共享资源。

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

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

相关文章

千鹿 AI:强大的抠图神器,让你的工作效率飙升99%

宝子们,今天一定要给大家分享一款超厉害的抠图工具 —— 千鹿 AI。千鹿 AI 用起来真的是极其方便,仅仅上传一张图片,短短几秒钟的时间,就能够获得一张边缘超级清晰的抠图成品,实在是令人惊叹。 鹿 AI 的厉害之处有很多…

【Linux系统编程】环境基础开发工具使用

目录 1、Linux软件包管理器yum 1.1 什么是软件包 1.2 安装软件 1.3 查看软件包 1.4 卸载软件 2、Linux编辑器-vim 2.1 vim的概念 2.2 vim的基本操作 2.3 vim的配置 3、Linux编译器-gcc/g 3.1 gcc编译的过程​编辑​编辑​编辑 3.2 详解链接 动态链接 静态链接 4…

二百六十九、Kettle——ClickHouse清洗ODS层原始数据增量导入到DWD层表中

一、目的 清洗ClickHouse的ODS层原始数据,增量导入到DWD层表中 二、实施步骤 2.1 newtime select( select create_time from hurys_jw.dwd_statistics order by create_time desc limit 1) as create_time 2.2 替换NULL值 2.3 clickhouse输入 2.4 字段选择 2.5 …

UDP反射放大攻击防范手册

UDP反射放大攻击是一种极具破坏力的恶意攻击手段。 一、UDP反射放大攻击的原理 UDP反射放大攻击主要利用了UDP协议的特性。攻击者会向互联网上大量的开放UDP服务的服务器发送伪造的请求数据包。这些请求数据包的源IP地址被篡改为目标受害者的IP地址。当服务器收到这些请求后&…

『网络游戏』服务器启动逻辑【16】

新建Visual Studio工程命名为NetGameServer 重命名为ServerStart.cs 创建脚本: 编写脚本:ServerRoot.cs 编写脚本:ServerStart.cs 新建文件夹 调整脚本位置 新建文件夹 新建文件夹网络服务 创建脚本:NetSvc.cs 编写脚本&#xff1…

【word】文章里的表格边框是双杠

日常小伙伴们遇到word里插入的表格,边框是双杠的,直接在边框和底纹里修改边框的样式就可以,但我今天遇到的这个有点特殊,先看看表格在word里的样式是怎么样,然后我们聊聊如何解决。 这个双杠不是边框和底纹的设置原因…

linux线程 | 一点通你的互斥锁 | 同步与互斥

前言:本篇文章主要讲述linux线程的互斥的知识。 讲解流程为先讲解锁的工作原理, 再自己封装一下锁并且使用一下。 做完这些就要输出一堆理论性的东西, 但博主会总结两条结论!!最后就是讲一下死锁。 那么, 废…

《量子之歌》

第一章:曙光 在2045年的未来,人工智能不再是科幻作品中的虚构,而是成为了日常生活的一部分。在这个时代,深度学习模型已经变得如此庞大和复杂,以至于即使是最快的超级计算机也需要数小时才能完成一次完整的训练。 陈欣…

大华智能云网关注册管理平台 SQL注入漏洞复现(CNVD-2024-38747)

0x01 产品简介 大华智能云网关注册管理平台是一款专为解决社会面视频资源接入问题而设计的高效、便捷的管理工具,平台凭借其高效接入、灵活部署、安全保障、兼容性和便捷管理等特点,成为了解决社会面视频资源接入问题的优选方案。该平台不仅提高了联网效率,降低了联网成本,…

【前端】制作一个自己的网页(4)

刚才我们完成了网页中标题与段落元素的学习。在实际开发时,一个网页通常会包含多个相同元素,比如多个标题与段落。 对于相同标签的元素,我们又该如何区分定位呢? 对多个相同的标签分类 比如右图设置了七个段落元素,它…

DS堆的实际应用(10)

文章目录 前言一、堆排序建堆排序 二、TopK问题原理实战创建一个有一万个数的文件读取文件并将前k个数据创建小堆用剩余的N-K个元素依次与堆顶元素来比较将前k个数据打印出来并关闭文件 测试 三、堆的相关习题总结 前言 学完了堆这个数据结构的概念和特性后,我们来看…

react18中实现简易增删改查useReducer搭配useContext的高级用法

useReducer和useContext前面有单独介绍过,上手不难,现在我们把这两个api结合起来使用,该怎么用?还是结合之前的简易增删改查的demo,熟悉vue的应该可以看出,useReducer类似于vuex,useContext类似…

wxml 模板语法-数据绑定

mustache 语法的应用场景: 动态绑定内容: 动态绑定属性: 三元运算: 算数运算:

MobileNet v3(相比于MobileNet v2)

概述: 更新Block(bneck) 使用NAS搜索参数 (Neural Architecture Search) 重新设计耗时层结构 更准确,更高效 以及表中数据展示 更新Block 1.加入SE模块 2.更新了激活函数 首先通过一个1*1的卷积层来进行一个升维处理&#…

深入了解vcruntime140.dll:为什么会出现vcruntime140.dll丢失及修复

“找不到vcruntime140.dll”或“vcruntime140.dll丢失”是什么情况?出现这样的情况有什么方法可以解决?今天这篇文章将和大家聊聊vcruntime140.dll丢失错误的详细解决办法,一步步教大家修复错误vcruntime140.dll文件。 一步步修复错误vcrunti…

[含文档+PPT+源码等]精品基于springboot实现的原生微信小程序酒店管理系统[包运行成功+永久免费答疑辅导]

基于Spring Boot实现的原生微信小程序酒店管理系统,其背景主要源于酒店行业的信息化需求和移动互联网技术的快速发展。以下是对该背景的具体阐述: 一、酒店行业的信息化需求 信息量剧增: 随着旅游业的蓬勃发展,酒店数量不断增加&…

AI金融攻防赛:金融场景凭证篡改检测(DataWhale组队学习)

引言 大家好,我是GISer Liu😁,一名热爱AI技术的GIS开发者。本系列文章是我跟随DataWhale 2024年10月学习赛的AI金融攻防赛学习总结文档。本文主要讲解如何解决 金融场景凭证篡改检测的核心问题,以及解决思路和代码实现过程。希望…

【图解版】力扣第1题:两数之和

Golang代码实现 func twoSum(nums []int, target int) []int {m : make(map[int]int)for i : range nums {if _, ok : m[target - nums[i]]; ok {return []int{i, m[target - nums[i]]}} m[nums[i]] i}return nil }

Java语言-抽象类

目录 1.抽象类概念 2.抽象类语法 3.抽象类特性 4.抽象类作用 1.抽象类概念 在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的, 如果 一个类中没有包含足够的信息来描绘一个具体…

本地群晖NAS安装phpMyAdmin管理MySQ数据库实战指南

文章目录 前言1. 安装MySQL2. 安装phpMyAdmin3. 修改User表4. 本地测试连接MySQL5. 安装cpolar内网穿透6. 配置MySQL公网访问地址7. 配置MySQL固定公网地址8. 配置phpMyAdmin公网地址9. 配置phpmyadmin固定公网地址 前言 本文主要介绍如何在群晖NAS安装MySQL与数据库管理软件p…