go实现redis分布式锁

package main

import (
	"context"
	"errors"
	"fmt"
	"log"
	"time"

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

type redisClient struct {
	client *redis.Client
	key    string
	value  string
	ctx    context.Context
}

// 集群redis
func connRedisCluster(address []string, password string) *redis.ClusterClient {
	conf := redis.ClusterOptions{
		Addrs:    address,
		Password: password,
	}
	return redis.NewClusterClient(&conf)
}

// 单机redis
func connRedisSingle(addr, password string) *redis.Client {
	conf := redis.Options{
		Addr:     addr,
		Password: password,
	}
	return redis.NewClient(&conf)
}

func (r *redisClient) lock() (bool, error) {
	ret := r.client.SetNX(r.ctx, r.key, r.value, time.Second*10)
	if err := ret.Err(); err != nil {
		return false, fmt.Errorf("redis: key: %s, value %s, set nx error: %v", r.key, r.value, err)
	}
	return ret.Val(), nil
}

func (r *redisClient) unlock() error {
	ret := r.client.Del(r.ctx, r.key)
	if err := ret.Err(); err != nil {
		return fmt.Errorf("redis: key: %s, value %s, unlock error: %v", r.key, r.value, err)
	}
	return nil
}

func (r *redisClient) retryLock() error {
	ok := false
	for !ok {
		t, err := r.getTTL()
		if err != nil {
			return fmt.Errorf("redis client: get ttl error: %v", err)
		}
		if t > 0 {
			log.Printf("lock occupied, retrying in %f seconds...\n", (t / 10).Seconds())
			time.Sleep(t / 10)
		}
		ok, err = r.lock()
		if err != nil {
			return fmt.Errorf("redis client: lock error: %v", err)
		}
	}
	return nil
}

func (r *redisClient) getLock() (string, error) {
	ret := r.client.Get(r.ctx, r.key)
	if err := ret.Err(); err != nil {
		return "", err
	}
	rt, _ := ret.Bytes()
	return string(rt), nil
}

// Get the remaining TTL of the lock
func (r *redisClient) getTTL() (time.Duration, error) {
	ret := r.client.TTL(r.ctx, r.key)
	if err := ret.Err(); err != nil {
		return 0, fmt.Errorf("redis: key: %s, value: %s, get ttl error: %s", r.key, r.value, err)
	}
	return ret.Val(), nil
}

func (r *redisClient) threadLock(threadId string) error {
	for {
		_, err := r.getLock()
		if err != nil && errors.Is(err, redis.Nil) {
			// No value retrieved, indicating no one holds the lock currently
			log.Printf("thread %s starting to lock", threadId)
			ok, err := r.lock()
			if err != nil { // 加锁出错
				return fmt.Errorf("redis client: lock error: %s", err)
			}
			if !ok { // 加锁失败
				err := r.retryLock() // 加锁重试
				if err != nil {
					return fmt.Errorf("threadId: %s, redis client: retry lock error: %s", threadId, err)
				}
			}
			log.Printf("thread %s has locked", threadId)
			// Perform actions after acquiring the lock
			time.Sleep(25 * time.Second)
			// Release the lock
			err = r.unlock()
			if err != nil {
				return fmt.Errorf("threadId: %s, redis client: unlock error: %s", threadId, err)
			}
			log.Printf("thread %s has released the lock", threadId)
			return nil
		} else if err != nil {
			return fmt.Errorf("redis client: get lock error: %s", err)
		}
		t, err := r.getTTL()
		if err != nil {
			return fmt.Errorf("redis client: get ttl error: %s", err)
		}
		if t > 0 {
			log.Printf("thread %s lock occupied, retrying in %f seconds\n", threadId, (t / 10).Seconds())
			time.Sleep(t / 10)
		}
	}
}

func main() {
	ctx := context.Background()
	address := "127.0.0.1:6379"
	cl := connRedisSingle(address, "123456")
	defer cl.Close()

	// Create redisClient instances, specifying the key and value
	r1 := redisClient{client: cl, key: "lock_key_1", value: "lock_value_1", ctx: ctx}

	// Thread 1 acquires the lock
	go func() {
		err := r1.threadLock("1")
		if err != nil {
			log.Fatal(err)
		}
	}()
	// Thread 2 acquires the lock
	go func() {
		err := r1.threadLock("2")
		if err != nil {
			log.Fatal(err)
		}
	}()
	// Thread 2 acquires the lock
	go func() {
		err := r1.threadLock("3")
		if err != nil {
			log.Fatal(err)
		}
	}()

	select {}
}

实现 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
发出的红包

打赏作者

赴前尘

喜欢我的文章?请我喝杯咖啡吧!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值