golang 使用redis实现分布式锁

在微服务的docker容器中,多个pod抢占一套资源时,需要用到全局锁,一般使用redis可以很好的实现保护功能。

安装redis-server:

apt install redis-server

配置redis有访问密码:

vim /etc/redis/redis.conf

# 开启redis客户端鉴权
protected-mode yes

# 设置密码为jack123
requirepass jack123

启动redis

systemctl start redis

版本1

...

对于版本1的锁,假设某个goroutine获取到了锁,但是该goroute在执行过程中需要等待其它的资源未被满足,出现了死锁的情况。那么其它goroutine抢占不到锁,就不能执行业务,业务就会堆积,cpu空转。 解决该问题的方法:用timeout redis锁

版本2代码:

package main

import (
	"fmt"
	"sync"
	"time"

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

var redisclient = redis.NewClient(&redis.Options{
	Addr:     "192.168.1.243:6379",
	Password: "jack123",
	DB:       0,
})

var cnt int64
var key = "jack"
var wg sync.WaitGroup

func main() {
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func() {
			lock(func() {
				cnt++
				fmt.Printf("after incr is %d\n", cnt)
			})
		}()
	}
	wg.Wait()
	fmt.Printf("cnt = %d\n", cnt)
}

func lock(handler func()) {
	defer wg.Done()

	lockSuccess, err := redisclient.SetNX(key, 1, time.Second*3).Result()
	if err != nil || lockSuccess != true {
		fmt.Println("get lock fail", err)
		return
	} else {
		fmt.Println("get lock success")
	}

	handler()

	//unlock
	_, err = redisclient.Del(key).Result()
	if err != nil {
		fmt.Println("unlock fail", err)
	} else {
		fmt.Println("unlock success")
	}
}

编译:

go mod init test
go mod tidy
go build

测试:

root@ubuntu:/tmp/zz# ./tt 
get lock success
after incr is 1
get lock fail <nil>
get lock fail <nil>
get lock fail <nil>
unlock success
get lock success
after incr is 2
unlock success
get lock success
after incr is 3
get lock fail <nil>
get lock fail <nil>
get lock fail <nil>
get lock fail <nil>
unlock success
cnt = 3
root@ubuntu:/tmp/zz#

上述版本会出现一个问题:当某个goroutine1执行时间比较长,例如操作一个10GB的大文件。goroutine2去获取锁是发现goroutine1虽然有锁但是过期了,goroutine2就毫不客气的拿到了该锁,然后goroutine2去执行业务代码,goroutine2也执行了很久。 go调度器在goroutine2 执行期间,goroutine1调取取执行,这时候goroutine1并不知道自己因为超时而时去了该锁,而对该锁进行了删除。 这时goroutine3 去抢占锁成功了,就会出现goroutine2和goroutine3同时操作互斥资源的情况。那么怎么解决该问题呢? 每个goroutine 对锁设置不同的标签做为值,每个goroutine在删除锁之前读取一下锁的值,确保是自己持有的情况下,才会进行删除锁的操作。

版本3:

func lock(myfunc func()) {
    //lock
    uuid := getUuid()
    lockSuccess, err := redisclient .SetNX(key, uuid, time.Second*3).Result()
    if err != nil || !lockSuccess {
        fmt.Println("get lock fail", err)
        return
    } else {
        fmt.Println("get lock success")
    }   
    //run func
    myfunc()
    //unlock
    value, _ := redisclient .Get(key).Result()
    if value == uuid { //compare value,if equal then del
        _, err := redisclient .Del(key).Result()
        if err != nil {
            fmt.Println("unlock fail")
        }  else {
            fmt.Println("unlock success")
        }
    }
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 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
发出的红包

打赏作者

唐墨123

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值