在微服务的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")
}
}
}