基于redis的分布式锁

1. redis分布式锁原理

redis实现分布式锁其实就是对key进行操作

  • 加锁:给对应的lockkey赋值,就进行了加锁操作,其他进程发现该lockkey已经值了有值了,就说明锁已经被别人获取
  • 解锁:获取到锁的进程删除这个lockkey
  • 锁超时:避免客户端宕机导致无法正常释放锁

 

1.1 加锁

加锁实际上就是在redis中,给lockkey设置一个值,为了避免死锁,给定一个过期时间:

SET lock_key uuid NX PX 5000

上面的操作就是给lock_key设置了一个uuid,并且过期时间为5000ms,如果执行成功就说明获取到了锁

 

1.2 解锁

解锁的过程就是将lock_key删除。但也不能乱删,也就是不能删掉其他客户端获取到的锁。这时候uuid的作用就体现出来

为了保证解锁操作的原子性,可以用LUA脚本完成这一操作。先判断当前锁的字符串是否与传入的值相等,是的话就删除Key,解锁成功

if redis.call('get',KEYS[1])==ARGV[1] then
    return redis.call('del',KEYS[1])
else
    return 0
end

 

2. go实现redis分布式锁

首先需要引入第三方依赖:

go get github.com/gomodule/redigo/redis
go get github.com/satori/go.uuid

 

获取到redis连接池完成初始化

var redisPool *redis.Pool

func init() {
	redisPool = getRedisPool()
}

// getRedisPool 获取redis连接池
func getRedisPool() *redis.Pool {
	return &redis.Pool{
		MaxIdle:     1,
		MaxActive:   10,
		IdleTimeout: 180 * time.Second,
		Dial: func() (redis.Conn, error) {
			c, err := redis.Dial("tcp", "xx.xx.xx.xx:6379")
			if err != nil {
				return nil, err
			}

			// 选择db
			c.Do("SELECT", 0)
			return c, nil
		},
	}
}

 

定义我们分布式锁的结构体

type Locker struct {
	Key   string
	Uuid  string
	Error error
}

 

尝试获取到分布式锁,带有超时机制

// TryLock: 尝试锁,带尝试超时机制
func TryLock(key string, timeout time.Duration) (locker *Locker) {

	// 获取uuid
	response, err := uuid.NewV4()
	if err != nil {
		fmt.Println("get uuid failed")
		return
	}
	uid := response.String()

	locker = &Locker{
		Key:  key,
		Uuid: uid,
	}

	// 开始尝试获取锁
	start := time.Now()
	for time.Now().Sub(start) < timeout {
		redisConn := redisPool.Get()
		reply, _ := redis.String(redisConn.Do("SET", locker.Key, locker.Uuid, "EX", 60, "NX"))
		redisConn.Close()

		if reply == "OK" {
			return
		}
		time.Sleep(time.Duration(200) * time.Millisecond)
	}

	locker.Error = errors.New("try to lock timeout")
	return
}

 

业务代码结束之后可以释放锁

func (locker *Locker) Unlock() {
	// 只有获取到锁的时候才可以释放锁
	if lock.Error == nil {
		redisConn := redisPool.Get()
		defer redisConn.Close()
		reply, err := redisConn.Do("GET", locker.Key)
		if err != nil {
			fmt.Println("redis connect failed")
			return
		}
		redisUUID := reply.([]byte)

		// 确定这是自己的锁
		if string(redisUUID) == locker.Uuid {
			reply, _ := redisConn.Do("DEL", locker.Key)
			if reply != "OK" {
				fmt.Println("delete key failed")
				return
			}
		}
	}
}

  

posted @ 2022-03-21 20:01  aganippe  阅读(162)  评论(0编辑  收藏  举报