分布式锁的实现

309 阅读2分钟

1、分布式锁的应用场景:

多个进程同时操作共享资源

实际用到的场景:

定时任务,在多台机器上对共享资源进行操作,比如:

1)每天的某一时刻,要生成对前一天数据的统计,多个定时任务只需要执行其中一个就可以了

2)定时告警任务,不加分布式锁去取数据库的数据,会重复通知

3)分表存储数据,每天会定时检查索引,多个进程去执行,虽然不会重复建索引,但会产生错误日志

2、分布式锁的实现方案

2.1 基于redis

//初始化redis连接池
type RedisPool struct {
	redis.Pool
}
type RedisMap map[string]*RedisPool
var RDS = RedisMap{}
func (rm *RedisMap) GetDefaultRedisPool() *RedisPool {
	return RDS["default"]
}
func init() {
	rd := redis.Pool{
		TestOnBorrow: nil,
		MaxIdle:      10,
		MaxActive:    10,
		IdleTimeout:  5 * time.Second,
		Wait:         false,
		Dial: func() (conn redis.Conn, err error) {
			conn, err = redis.Dial("tcp", "127.0.0.1:6379")
			if err != nil {
			}
			return
		},
	}
	RDS["default"] = &RedisPool{
		rd,
	}
}

//加锁
func TryLock(key, requestId string, expireSeconds int) (bool, error) {
	conn := RDS.GetDefaultRedisPool().Get()
	defer conn.Close()

	msg, err := redis.String(conn.Do("SET", key, requestId, "EX", expireSeconds, "NX"))
	if err == redis.ErrNil {
		//锁已经存在
		return false, err
	}
	if err != nil {
		return false, err
	}
	//已获取到锁
	if msg == "OK" {
		return true, nil
	}
	return false, errors.New("")
}

func GetLock(key string) string {
	conn := RDS.GetDefaultRedisPool().Get()
	defer conn.Close()

	msg, err := redis.String(conn.Do("GET", key))
	if err != nil {
		return ""
	}
	return msg
}
//解锁
func UnLock(key, requestId string) bool {
	if GetLock(key) == requestId {
		conn := RDS.GetDefaultRedisPool().Get()
		defer conn.Close()

		msg, _ := redis.Int64(conn.Do("DEL", key))
		//自动过期时再删除返回结果未0
		if msg == 1 || msg == 0 {
			fmt.Println("unlock successed...")
			return true
		}
		fmt.Println("unlock failed...")
		return false
	}
	fmt.Println("unlock failed...")
	return false
}

func AddTimeout(key, requestId string, expireSeconds int64) (bool, error) {
	conn := RDS.GetDefaultRedisPool().Get()
	defer conn.Close()

	ttlTime, err := redis.Int64(conn.Do("TTL", key))
	if err != nil {
		return false, err
	}
	fmt.Println("ttlTime:", ttlTime)
	if ttlTime > 0 {
		_, err := conn.Do("SETEX", key, int(ttlTime + expireSeconds), requestId)
		if err != nil {
			fmt.Println("22")
			return false, err
		}
		return true, nil
	}
	fmt.Println("33")
	return false, nil
}

func main() {
	key := "key1"
	localIp := "localIp"
	expireSeconds := 20
	fmt.Println("start...")
	ok, err := TryLock(key, localIp, expireSeconds)
	if err != nil || !ok {
		fmt.Println("the lock exists...", err, ok)
		return
	}
	defer UnLock(key, localIp)

	fmt.Println("lock successed...")

	ok, err = AddTimeout(key, localIp, 30)
	if err != nil || !ok {
		fmt.Println("add timeout failed...", err, ok)
		return
	}
	fmt.Println("add timeout successed...")

	time.Sleep(time.Second * 30)
	fmt.Println("end...")
}

总结:

通过setex设置超时时间,达到时间之后数据会自动删除,但是失效时间设置的太短,方法没执行完锁就自动释放了,会产生并发问题,如果设置的时间太长,其他获取锁的线程就要多等一段时间

解决:

redis锁的过期时间能够自动续期,每隔一段时间(10s)查看这个锁,如果还持有锁,就对过期时间进行续期

blog.csdn.net/yangxiaodon…

2.2 基于zookeeper

2.3 基于数据表