[分布式锁]基于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
            }
        }
    }
}