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

分布式锁的基本概念

在 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.")
    }
}
  • 26
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
基于Redis的Redisson分布式锁的RedisTemplate实现看门狗写法的分布式锁可以通过以下步骤实现: 1. 首先,创建一个RedissonClient对象,该对象用于与Redis进行通信和执行锁操作。可以使用RedisTemplate封装的Redis连接工厂来创建RedissonClient对象。 2. 然后,使用RedisTemplate实例化一个RLock对象,该对象用于实现分布式锁的功能。可以通过调用RedisTemplate的getLock方法来获取RLock对象。 3. 接下来,可以设置看门狗的超时时间。通过调用RLock对象的设置方法,如setLockWatchdogTimeout方法,可以修改看门狗的检查锁超时时间。 4. 在使用分布式锁时,可以调用RLock对象的加锁方法,如lock方法,来获取锁。在加锁后,可以执行需要保护的临界区代码。 5. 最后,在临界区代码执行完成后,需要调用RLock对象的解锁方法,如unlock方法,来释放锁。 以下是一个示例代码,展示了如何使用RedisTemplate实现看门狗写法的分布式锁: ```java // 创建Redis连接工厂 RedisConnectionFactory redisConnectionFactory = redisTemplate.getConnectionFactory(); // 创建RedissonClient对象 RedissonClient redissonClient = Redisson.create(redisConnectionFactory); // 使用RedisTemplate实例化RLock对象 RLock lock = redissonClient.getLock("my-lock"); // 设置看门狗的超时时间 lock.setLockWatchdogTimeout(30000); // 设置为30秒 // 加锁并执行临界区代码 lock.lock(); try { // 执行需要保护的临界区代码 // ... } finally { // 解锁 lock.unlock(); } ``` 通过以上步骤,可以使用RedisTemplate实现看门狗写法的分布式锁,确保在Redisson实例关闭前,锁会不断地延长有效期,避免锁死的情况发生。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [使用 Redis 实现分布式锁案例](https://blog.csdn.net/zl5186888/article/details/126704240)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值