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