对于一个进程来说,内部的资源锁有很多的实现方式,也都很高效,对于同一个机器的不同进程做同步的话,也有很多的方式去实现,对于不同机器上部署的程序来说,假如要进行同步,则比较麻烦。

最简单的方式可能就是使用redis了,redis的实现是所有的操作放入单线程中处理的,所以对于并发的请求,都有先后顺序,那么对于实现分布式锁来说有着得天独到的优势。

其实所谓的分布式锁,就是在某个共享节点上做一个资源的标记,当标记不存在的时候,则表示没有其它的请求在操作这个资源,那么就可以获取这个资源,并进行操作,当其余的请求发现这个标记存在的时候,则等待,直到前一个请求将这个标记清楚,则整个流程就走通了。

redis中的SET命令有一个NX支持,就是在不存在的时候,该操作才会成功,于是我们就可以利用这一点去实现上述的想法。

当一个请求想要获取某个锁的时候,我们可以

//    to generate the unique key
u, err := uuid.NewUUID()
if nil != err {
    return err
}
s.lockValue = u.String()
s.lockKey = keyName

//    try to lock
rpl, err := redis.String(conn.Do("SET", keyName, s.lockValue, "NX", "PX", timeout))
if nil != err {
    return err
}
if rpl != "OK" {
    return ErrSingleLockOperationFailed
}

return nil

我们为每个locker分配了一个uuid,并使用SET NX命令来获取锁,当成功后,则该locker就获得了该锁。

解锁也很简单

//    try to unlock
//    avoid to unlock a lock not belongs to the locker
lockValue, err := redis.String(conn.Do("GET", s.lockKey))
if nil != err {
    return err
}
if lockValue != s.lockValue {
    return ErrSingleLockInvalidLockValue
}

rpl, err := redis.Int(conn.Do("DEL", s.lockKey))
if nil != err {
    return err
}

if rpl != 1 {
    return ErrSingleLockLockIsUnlocked
}

return nil

这些就是大概的代码实现。

然而实现其实是很简单的,但是其实里面做了点小工作来避免一个问题。

假如某个locker获得了锁,然而在timeout的时间内没有DEL该锁,那么这个锁就过期自动释放了,也就是其它的请求可以获得这个锁,问题就出现了,假设第二个获取的锁还没有释放,当第一个请求在timeout的事件后释放了该锁,那么第三个请求就可以获得新的锁了,这是个很大的问题,这样就有3个连接获得了锁,这是个很大的问题。

于是在上述代码中,每一个locker都有一个uuid,并且当uuid相等的时候才会去删除该锁,避免了锁被另外一个locker释放的问题。

github

共 0 条回复
暂时没有人回复哦,赶紧抢沙发
发表新回复

作者

sryan
today is a good day