分布式锁 分布式锁解决了什么问题 如何实现 看门狗机制是什么

news2024/9/22 21:25:19

分布式锁的基本概念

在 Redis 中实现分布式锁的常用方式是通过 SETNX 命令(SET with NX option)来设置一个键(key),这个键代表锁。如果 key 不存在,SETNX 会设置成功,并返回 1,表示成功获取锁。如果 key 已经存在,SETNX 会返回 0,表示获取锁失败。

线程号在 Redis 锁中的作用

通常,为了确保在释放锁时只有锁的持有者能够删除这个锁,会在锁的 keyvalue 中保存线程标识符或节点标识符(例如,线程 ID 或唯一的随机值)。这样,在解锁时,可以验证当前操作的线程是否为持有该锁的线程。

你提到的问题分析

1. 关于 key 和线程号

如果你理解的是在锁的 key 中添加线程号,那确实会导致每个线程设置的 key 不一样。例如,假设线程 A 设置的 keylock:threadA,线程 B 设置的 keylock:threadB,那么它们各自的 key 是独立的,互不影响。这种情况下,确实每个线程都会成功设置自己的 key,但这实际上并不是我们想要的分布式锁效果,因为每个线程都能创建自己的锁,而不会有竞争关系。

2. 正确的做法:在 value 中保存线程标识符

通常情况下,锁的 key 是固定的,比如 lock:resource_id,表示对某个资源的锁,而不同线程或节点通过在 value 中存储唯一的标识符来区分持有锁的线程。

示例:

go复制代码// 线程 A 尝试获取锁
SET lock:resource_id "threadA" NX PX 30000

// 线程 B 尝试获取锁
SET lock:resource_id "threadB" NX PX 30000
  • 如果线程 A 成功获取锁,那么 lock:resource_idvalue 就是 "threadA"
  • 如果线程 B 尝试获取锁,因为 key 已经存在,操作会失败。
3. 释放锁

当线程 A 需要释放锁时,会先检查 keyvalue 是否等于 "threadA",如果是,才能删除锁:

go复制代码if GET lock:resource_id == "threadA" {
    DEL lock:resource_id
}

这样,只有持有锁的线程才能释放锁,避免其他线程误删。

总结

  • 锁的 key:应固定,表示要锁定的资源。
  • 锁的 value:保存线程或节点的唯一标识符,用于区分持有锁的线程或节点。

在 Redis 分布式锁中,所有线程都应该尝试设置相同的 key,而区分不同线程的是 value 中的标识符。你提到的问题可能是在理解时将线程标识符放在了 key 中,这样会导致每个线程创建的 key 不一样,从而失去了锁的作用。

在 Go 中结合 Redis 实现一个简单的分布式锁,通常可以使用 Redis 的 SET 命令和一些关键选项,如 NX(表示仅在键不存在时才设置键)和 PX(设置键的过期时间,以毫秒为单位)。以下是一个示例代码,演示如何使用 Redis 实现一个分布式锁。

1. 环境准备

首先,确保你已经安装了 Redis,并在 Go 项目中引入了 Redis 的 Go 客户端库 go-redis

bash
复制代码
go get github.com/redis/go-redis/v9

2. 实现分布式锁

以下代码展示了如何在 Go 中使用 Redis 实现一个简单的分布式锁。

go复制代码package main

import (
    "context"
    "fmt"
    "github.com/redis/go-redis/v9"
    "time"
)

var ctx = context.Background()

// 尝试获取分布式锁
func acquireLock(client *redis.Client, key string, value string, expiration time.Duration) (bool, error) {
    // SET key value NX PX expiration
    result, err := client.SetNX(ctx, key, value, expiration).Result()
    if err != nil {
        return false, err
    }
    return result, nil
}

// 释放分布式锁
func releaseLock(client *redis.Client, key string, value string) (bool, error) {
    // Lua脚本保证原子性
    luaScript := `
        if redis.call("GET", KEYS[1]) == ARGV[1] then
            return redis.call("DEL", KEYS[1])
        else
            return 0
        end
    `
    result, err := client.Eval(ctx, luaScript, []string{key}, value).Int()
    if err != nil {
        return false, err
    }
    return result == 1, nil
}

func main() {
    // 创建Redis客户端
    client := redis.NewClient(&redis.Options{
        Addr: "localhost:6379", // Redis服务器地址
    })

    // 锁的key和value
    lockKey := "my_lock"
    lockValue := "unique_value" // 每个线程应生成唯一的值,通常是UUID或线程ID

    // 尝试获取锁,设置10秒过期
    locked, err := acquireLock(client, lockKey, lockValue, 10*time.Second)
    if err != nil {
        fmt.Println("Error acquiring lock:", err)
        return
    }

    if locked {
        fmt.Println("Lock acquired successfully!")
        // 执行需要保护的操作...

        // 操作完成后释放锁
        unlocked, err := releaseLock(client, lockKey, lockValue)
        if err != nil {
            fmt.Println("Error releasing lock:", err)
            return
        }
        if unlocked {
            fmt.Println("Lock released successfully!")
        } else {
            fmt.Println("Failed to release lock!")
        }
    } else {
        fmt.Println("Failed to acquire lock, another process might hold it.")
    }
}

在这里插入图片描述

3. 代码说明

  1. 获取锁 (acquireLock):
    • 使用 SETNX 命令尝试获取锁。如果 key 不存在,则设置该 key,同时指定过期时间,确保锁在超时后会自动释放。
    • 如果 key 已经存在,SETNX 返回 false,表示获取锁失败。
  2. 释放锁 (releaseLock):
    • 为了防止误释放锁(例如:锁已过期并被其他线程重新获取),我们使用 Lua 脚本保证删除操作的原子性。
    • 只有当 keyvalue 与当前线程持有的锁的 value 一致时,才删除锁。
  3. 主函数 (main):
    • 创建 Redis 客户端,连接到 Redis 服务器。
    • 尝试获取锁并进行保护的操作。
    • 完成操作后释放锁。

4. 扩展

在实际应用中,分布式锁可能需要更多功能,例如自动续期、死锁检测等。这些功能可以根据具体需求进行扩展。还可以使用现成的库,如 Redlock 实现更复杂的分布式锁机制。

看门狗机制

分布式锁中的“开门狗机制”是用来解决锁过期时间不足而导致的锁提前释放的问题。开门狗机制可以自动延长锁的有效期,防止在锁持有者还在执行任务时锁被释放,从而避免其他客户端意外获得锁。

为什么需要开门狗机制?

当一个客户端获取分布式锁时,它通常会设置一个锁的过期时间(TTL),以防止锁因为某些意外原因(例如客户端崩溃)而永远不被释放。TTL 确保了即使客户端没有主动释放锁,锁也会在一定时间后自动释放。

然而,任务执行的时间有时可能比预期的要长。如果没有合适的机制,锁的 TTL 到期后,锁会被自动释放,导致其他客户端可能在任务尚未完成时获得锁,进而引发数据一致性问题。 看门狗可以更新所得到期时间

看门狗机制的工作原理

开门狗机制主要包括以下步骤:

  1. 获取锁并设置初始TTL:客户端获取锁时,设置一个初始的 TTL(例如 10 秒)。
  2. 定期续约:在锁持有期间,客户端启动一个后台任务(开门狗),定期检查锁的状态。如果客户端依然持有锁,并且任务还在执行,开门狗会延长锁的TTL。例如,每隔一半的TTL时间(例如5秒),将锁的TTL重置为原来的TTL时间(例如10秒)。
  3. 释放锁:一旦任务完成,客户端主动释放锁,同时停止开门狗。

示例代码

以下是一个简单的示例,演示如何在 Go 中实现带开门狗机制的分布式锁。

go复制代码package main

import (
    "context"
    "fmt"
    "github.com/redis/go-redis/v9"
    "time"
)

var ctx = context.Background()

// 获取分布式锁,带初始TTL
func acquireLock(client *redis.Client, key string, value string, expiration time.Duration) (bool, error) {
    result, err := client.SetNX(ctx, key, value, expiration).Result()
    if err != nil {
        return false, err
    }
    return result, nil
}

// 续约锁的TTL
func renewLock(client *redis.Client, key string, value string, expiration time.Duration) error {
    luaScript := `
        if redis.call("GET", KEYS[1]) == ARGV[1] then
            return redis.call("PEXPIRE", KEYS[1], ARGV[2])
        else
            return 0
        end
    `
    _, err := client.Eval(ctx, luaScript, []string{key}, value, int(expiration.Milliseconds())).Result()
    return err
}

// 释放分布式锁
func releaseLock(client *redis.Client, key string, value string) (bool, error) {
    luaScript := `
        if redis.call("GET", KEYS[1]) == ARGV[1] then
            return redis.call("DEL", KEYS[1])
        else
            return 0
        end
    `
    result, err := client.Eval(ctx, luaScript, []string{key}, value).Int()
    if err != nil {
        return false, err
    }
    return result == 1, nil
}

// 开门狗机制,定期续约锁的TTL
func startWatchdog(client *redis.Client, key string, value string, expiration time.Duration, interval time.Duration, stopChan chan bool) {
    ticker := time.NewTicker(interval)
    defer ticker.Stop()

    for {
        select {
        case <-ticker.C:
            // 续约锁的TTL
            err := renewLock(client, key, value, expiration)
            if err != nil {
                fmt.Println("Error renewing lock:", err)
                return
            }
            fmt.Println("Lock renewed for another", expiration)
        case <-stopChan:
            fmt.Println("Watchdog stopped")
            return
        }
    }
}

func main() {
    // 创建Redis客户端
    client := redis.NewClient(&redis.Options{
        Addr: "localhost:6379",
    })

    // 锁的key和value
    lockKey := "my_lock"
    lockValue := "unique_value"

    // 尝试获取锁,设置初始TTL为10秒
    locked, err := acquireLock(client, lockKey, lockValue, 10*time.Second)
    if err != nil {
        fmt.Println("Error acquiring lock:", err)
        return
    }

    if locked {
        fmt.Println("Lock acquired successfully!")

        // 启动开门狗机制,间隔5秒续约,TTL为10秒
        stopChan := make(chan bool)
        go startWatchdog(client, lockKey, lockValue, 10*time.Second, 5*time.Second, stopChan)

        // 模拟执行任务
        time.Sleep(15 * time.Second)

        // 任务完成后释放锁
        unlocked, err := releaseLock(client, lockKey, lockValue)
        if err != nil {
            fmt.Println("Error releasing lock:", err)
            return
        }
        if unlocked {
            fmt.Println("Lock released successfully!")
        } else {
            fmt.Println("Failed to release lock!")
        }

        // 停止开门狗
        stopChan <- true
    } else {
        fmt.Println("Failed to acquire lock, another process might hold it.")
    }
}

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

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

相关文章

【LinuxPython】linux中通过源码方式安装python环境

python环境安装直接看第二部分即可。 文章目录 1.背景2.python安装3.包环境复制 1.背景 部署一个线上任务时&#xff0c;相同的代码本地开发机正常产出数据&#xff0c;线上产出数据为0&#xff0c;排查到原因是&#xff1a; ...File "/home/disk1/wangdeyong/venv/pyth…

linux搭建zabbix

zabbix简介 Zabbix是一个监控系统&#xff0c;它可以帮助我们实时检查设备的状态&#xff0c;比如服务器、网络设备等。当设备出现问题时&#xff0c;它会及时通知我们&#xff0c;让我们可以采取措施来解决。同时&#xff0c;它还可以把收集到的数据转化成图表和报告&#xf…

ITL-Internet Technology Letters

文章目录 一、期刊简介二、征稿信息三、投稿须知四、咨询 一、期刊简介 Internet Technology Letters本期旨在涵盖所有用于提高物联网性能的新兴或现代学习算法。在此背景下&#xff0c;我们打算收集有关物联网学习进展的研究论文。强烈鼓励与机器学习、计算智能、概率学习、统…

树和图()

预备知识&#xff08;可以不看&#xff09;&#xff1a; 无向图可以理解为是特殊的有向图 1. 图的遍历&#xff08;因为树可以理解为是特殊的图&#xff0c;因此这里不考虑树的遍历&#xff0c;只考虑图的遍历&#xff09; 给定一个具体的图&#xff0c;便于分析 下面是树的结构…

Servlet——个人笔记

Servlet——个人笔记 文章目录 [toc]Servlet简介Servlet命名Servlet由来实现过程 Servlet 相对 CGI 的优势简要说说什么是CGI Servlet 在IDEA中开发流程Servlet注解方式配置WebServlet注解源码WebServlet注解使用 Servlet常见容器Servlet 生命周期简介测试 Servlet 方法init()…

交叉编译util-linux

参考文章&#xff1a;https://www.cnblogs.com/wanglouxiaozi/p/17836701.html 1、下载源码 https://mirrors.edge.kernel.org/pub/linux/utils/util-linux/v2.39/2、编译 解压压缩包&#xff1a; sudo tar xvf util-linux-2.39.2.tar.gz执行autogen.sh生成configure ./aut…

解锁眼部舒压新境界:WT2605C-AT-L009眼部按摩仪蓝牙语音方案,手机APP控制,让护眼更轻松!

一、开发背景&#xff1a; 随着科技的飞速发展和人们生活品质的提升&#xff0c;眼部按摩仪作为一种能够缓解眼部疲劳、改善眼部血液循环的健康产品&#xff0c;越来越受到消费者的青睐。在众多眼部按摩仪中&#xff0c;采用WT2605C-AT-L009蓝牙模块的应用方案&#xff0c;不仅…

优思学院|PDCA和DMAIC之间如何选择?

在现代组织中&#xff0c;提升方法、质量和效率是企业追求卓越、保持竞争力的核心目标。在这条道路上&#xff0c;DMAIC&#xff08;定义、测量、分析、改进、控制&#xff09;和PDCA&#xff08;计划、执行、检查、行动&#xff09;被广泛应用于持续改进和问题解决。这两者虽然…

基于Java的大学新生入学系统设计与实现----附源码17610

摘要 随着高校规模的不断扩大和新生人数的增加&#xff0c;传统的手工登记和管理方式已经无法满足高效、准确的需求。为了提升大学新生入学迎新工作的效率和质量&#xff0c;本研究设计开发了一套基于Java的大学新生入学系统。系统通过信息技术的应用&#xff0c;集成了首页、校…

GIT IDEA 远程仓库操作

1、配置远程仓库地址 &#xff08;点击推送后如果没有配置远程仓库会让配置远程仓库&#xff09; 2、从远程仓库中下载项目到本地 3、提交->推送 更新代码&#xff08;拉取&#xff09;

PSTX250-600-70软启动器PSTX25060070面价

PSTX250-600-70软启动器PSTX25060070面价 PSTX250-600-70软启动器PSTX25060070面价 PSTX250-600-70软启动器PSTX25060070面价 PSTX250-600-70软启动器PSTX25060070说明书 PSTX250-600-70软启动器PSTX25060070接线图 PSTX250-600-70软启动器PSTX25060070引脚线 PSTX250-60…

配置错误和 IAM 弱点是云安全的主要隐患

根据云安全联盟发布的《2024 年云计算最大威胁》报告&#xff0c;通常与云服务提供商 (CSP) 相关的传统云安全问题的重要性正在持续下降。 配置错误、IAM 弱点和 API 风险仍然至关重要 这些发现延续了 2022 年报告中首次发现的轨迹&#xff0c;同时&#xff0c;诸如错误配置的…

第100+21步 ChatGPT学习:概率校准 Isotonic Regression

基于Python 3.9版本演示 一、写在前面 最近看了一篇在Lancet子刊《eClinicalMedicine》上发表的机器学习分类的文章&#xff1a;《Development of a novel dementia risk prediction model in the general population: A large, longitudinal, population-based machine-learn…

C语言中的结构体和位移段

在C语言中&#xff0c;结构体&#xff08;struct&#xff09;是一种用户自定义的数据类型&#xff0c;允许我们将不同类型的变量组合在一起&#xff0c;形成一个复合数据类型。结构体可以包含整型、浮点型、字符型等多种数据类型的成员。例如&#xff0c;我们可以定义一个表示人…

使用C语言构建Lua库

Lua 本身是用 C 语言编写的&#xff0c;因此使用 C 编写扩展可以更好地与 Lua 引擎集成&#xff0c;减少性能瓶颈&#xff0c;同时C 语言提供了对底层硬件和操作系统功能的直接访问能力&#xff0c;让 Lua 可以通过 C 扩展来实现对文件系统、网络等高级功能的支持。因为C 语言非…

The First项目报告:Web3人生模拟器,DegenReborn带你重开币圈

2023年6月14日&#xff0c;ReadON APP的首页上&#xff0c;一篇引人注目的文章《黑客马拉松奖&#xff1a;‘Degenreborn’——Meme与GameFi的梦幻交汇》跃然眼前&#xff0c;该文章巧妙融合了NFT、GameFi及Ethereum等热门话题&#xff0c;为读者带来了一场科技与娱乐的盛宴。 …

万字详述haproxy

目录 写在前面 1、Haproxy简介 2、Haproxy的安装和基本配置信息 2.1、haproxy的安装 2.2haproxy的基本配置信息 2.2.1基本配置文件global参数 2.2.2基本配置文件proxys的相关参数 2.2.2.1 default的相关参数 2.2.2.2 frontend的相关配置 2.2.2.3 backend的相关配置 …

24年下半年软考只剩下3个月时间,来得及准备吗?

过来人告诉你来得及&#xff0c;但是选对科目很重要&#xff01; 一般来说&#xff0c;自学备考软考的时间为4-5个月&#xff0c;如果大家现在才开始备考的话&#xff0c;时间就有点紧张了&#xff0c;需要加倍努力才行&#xff0c;推荐大家可以报考一些相对简单的科目&#x…

Windows Server 2012 R2服务器安装CVE-2024-38077补丁KB5040456的安装及问题解决

Windows 远程桌面授权服务远程代码执行漏洞CVE-2024-38077&#xff0c;该漏洞影响: 远程执行代码&#xff0c;漏洞最高严重性: 严重。本文记录了Windows Server 2012 R2服务器补丁KB5040456的安装及报错“此更新不适用于你的计算机”的问题解决过程。 一、漏洞相关信息 1.影响…

具有 SAM2 分段的 NDVI 无人机

在我们之前的博客文章《OAK相机扩展NDVI功能检测植物健康情况》中&#xff0c;我们探讨了 NDVI 方法以及如何使用多光谱相机计算它。 今天&#xff0c;我们通过使用带有多光谱相机的无人机并使用 SAM2 模型进行场分割和健康比较&#xff0c;将 NDVI 感知提升到一个新的水平。 …