golang 使用redis实现分布式锁
原创
©著作权归作者所有:来自51CTO博客作者冰糖豆豆的原创作品,请联系作者获取转载授权,否则将追究法律责任
在微服务的docker容器中,多个pod抢占一套资源时,需要用到全局锁,一般使用redis可以很好的实现保护功能。
安装redis-server:
配置redis有访问密码:
vim /etc/redis/redis.conf
# 开启redis客户端鉴权
protected-mode yes
# 设置密码为jack123
requirepass jack123
启动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")
}
}
}