golang 使用redis设置分布式锁 demo

内容来自对 chatgpt 的咨询

分布式锁是在多个节点上运行的应用程序中协调工作的一种常用方法,而Redis是实现分布式锁的流行选择。以下是使用Go语言和github.com/go-redis/redis库来设置Redis分布式锁的一个简单示例:

首先,确保你已经安装了该库:

go get -u github.com/go-redis/redis/v8

demo

然后,这是一个简单的Go代码,用于设置和释放Redis分布式锁:

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/go-redis/redis/v8"
)

var ctx = context.Background()

func main() {
	rdb := redis.NewClient(&redis.Options{
		Addr: "localhost:6379", // 你的Redis地址
		DB:   0,                 // 使用默认DB
	})

	lockKey := "my-lock-key"
	lockValue := "my-unique-identifier" // 这应该是一个每次都唯一的值,例如,可以使用UUID
	lockDuration := 5 * time.Second     // 锁持续时间

	// 尝试设置锁
	ok, err := rdb.SetNX(ctx, lockKey, lockValue, lockDuration).Result()
	if err != nil {
		panic(err)
	}

	if ok {
		fmt.Println("Lock acquired!")

		// 你的业务逻辑

		// 释放锁
		val, err := rdb.Get(ctx, lockKey).Result()
		if err == nil && val == lockValue {
			_, err := rdb.Del(ctx, lockKey).Result()
			if err != nil {
				panic(err)
			}
			fmt.Println("Lock released!")
		}
	} else {
		fmt.Println("Could not acquire lock!")
	}
}

注意:

1、这是一个简单的实现,而在实际生产环境中,你可能需要添加一些额外的功能,如重试机制、锁超时检查等。

2、使用SetNX(Set if Not eXists)确保只有一个客户端可以获得锁。

3、为了确保安全性,释放锁之前要检查锁的值确实是你设置的值。这样可以防止错误地释放其他客户端的锁。

4、在大型分布式系统中,你可能还需要考虑使用像Redlock这样的更复杂的算法来确保锁的安全性和稳定性。

问:Redis 分布式锁过期时间怎么合理设置,长了可能影响性能,短了可能任务没执行完

方案一:保证过期时间足够长,大部分的任务都能执行完,任务执行完主动释放锁

方案二:设置一个PT99任务能执行完的过期时间,没执行完则使用守护对锁进行续期,

对于方案二的代码实现

设置锁成功后,初始化一个 channel,随后启用一个协程作为守护进程监听定时器和channel,如果定时器到期则进行续期,如果从channel中取到数据,立刻return返回终止程序。由于任务执行完毕会主动释放锁,释放锁时会关闭守护进程的chan,此时 select 的case 从channel中读取到一个零值,立刻返回,结束锁续期程序。
守护进程定期检测锁是否还在,只要 chan没有被关闭,说明锁还在。
只要锁还在就说明任务还没执行完,那么重置过期时间,等待任务执行完主动释放锁。
问题:该程序的守护进程怎么判断任务是否执行完毕,答:任务执行完毕会主动释放锁,释放锁时会关闭守护进程的chan,这时守护线程就会关闭,就不再续期了。

type Lock struct {
	daemon chan interface{} // 看门狗
	client   *redis.Client  // redis 客户端
	ttl      time.Duration    // 过期时间
	key      string           // 锁key
}

func (l *Lock) Lock(ctx context.Context) error {
	// 尝试获取锁
	var value = "1"
	success, err := l.client.SetNX(l.key, value, l.ttl).Result()
	fmt.Printf("success is %+v err is %+v", success, err)
	if err != nil {
		return errors.New("获取分布式锁失败")
	}
	if !success {
		return errors.New("获取分布式锁失败")
	}
    
	// 加锁成功,启动守护进程进行锁续期
	go l.DaemonLock()
	return nil
}

func (l *Lock) DaemonLock() { // 锁续期
    ticker := time.NewTicker(l.ttl / 2)
    defer ticker.Stop()
    for {
        select {
            case <-ticker.C:
            // 延长锁的过期时间
            ok, err := l.client.Expire(l.key, l.ttl).Result()
            // 异常或锁已经不存在则不再续期
            if err != nil || !ok {
                return
            }
            case <-l.daemon:
            // 已经解锁
            return
        }
    }
}

func (l *Lock) Unlock(ctx context.Context) error {
	// 关闭看门狗
	close(l.daemon)
	err := l.client.Del(l.key).Err()
	if err != nil {
		fmt.Printf("Unlock err=%s", err.Error())
		return err
	}
	return err
}

DaemonLock 会在 l.daemon 这个通道被关闭或者在续约锁失败时退出。

这是因为当一个 Go 语言的通道被关闭后,尝试从这个通道中读取数据将会立即返回一个与通道类型相对应的零值,并且第二个返回值(一个布尔值)会被设为 false。select 语句会不断地尝试从 l.daemon 通道中读取值,如果通道被关闭,select 就会立即从对应的 case 中返回。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现 Redis 分布式锁的基本思路是利用 Redis 的 SETNX 命令(SET if Not eXists)实现。SETNX 命令会在 key 不存在的情况下,将 key 的值设为 value,如果 key 已经存在,则不做任何操作。 以下是一个简单的 Golang 实现 Redis 分布式锁的代码示例: ```go package redislock import ( "fmt" "time" "github.com/go-redis/redis/v7" ) type RedisLock struct { redisClient *redis.Client key string value string expiration time.Duration } func NewRedisLock(redisClient *redis.Client, key, value string, expiration time.Duration) *RedisLock { return &RedisLock{ redisClient: redisClient, key: key, value: value, expiration: expiration, } } func (r *RedisLock) Lock() (bool, error) { success, err := r.redisClient.SetNX(r.key, r.value, r.expiration).Result() if err != nil { return false, err } return success, nil } func (r *RedisLock) Unlock() error { err := r.redisClient.Del(r.key).Err() if err != nil { return err } return nil } ``` 在上面的代码中,NewRedisLock 函数用于创建一个 RedisLock 实例,需要传入 Redis 客户端、锁的 key、锁的值、锁的过期时间。Lock 方法用于尝试获取锁,如果获取成功,返回 true,否则返回 false。Unlock 方法用于释放锁。 以下是一个简单的使用示例: ```go package main import ( "fmt" "time" "github.com/go-redis/redis/v7" "github.com/yourusername/redislock" ) func main() { redisClient := redis.NewClient(&redis.Options{ Addr: "localhost:6379", DB: 0, }) lock := redislock.NewRedisLock(redisClient, "my-lock", "my-value", 10*time.Second) success, err := lock.Lock() if err != nil { fmt.Println("failed to acquire lock:", err) return } if !success { fmt.Println("lock is already held by another process") return } defer lock.Unlock() // Do some work } ``` 在上面的示例中,我们创建了一个 Redis 客户端,并且创建了一个 RedisLock 实例。然后,我们调用 Lock 方法尝试获取锁,如果获取成功,就可以进行一些需要加锁的操作。最后,我们调用 Unlock 方法释放锁。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值