分布式鎖之redis實(shí)現(xiàn)

前言

傳統(tǒng)的鎖(比如編程語言里的lock),都是對(duì)多線程進(jìn)行控制,但是對(duì)多進(jìn)程、或者多客戶端就無能為力了,為此,就誕生了分布式鎖。

通常,redis會(huì)被用作緩存功能,但是,它也可以有其他的一些用途。本文,就是利用redis實(shí)現(xiàn)分布式鎖功能。當(dāng)然,實(shí)現(xiàn)分布式鎖還有很多種其他方式,比如:

  • 基于數(shù)據(jù)庫樂觀鎖
  • 基于ZooKeeper的分布式鎖

不管哪種方式,他的基本原理是不變的:用一個(gè)狀態(tài)值表示鎖,對(duì)鎖的占用和釋放通過狀態(tài)值來標(biāo)識(shí)。

基本原理

redis為單進(jìn)程單線程模式,采用隊(duì)列模式將并發(fā)訪問變成串行訪問,且多客戶端對(duì)redis的連接并不存在競(jìng)爭(zhēng)關(guān)系。

關(guān)鍵命令

> SETNX key value
# SETNX: SET if Not eXists
# 將key的值設(shè)置為value
# 當(dāng)key存在時(shí),不做任何操作,返回   0
# 當(dāng)key不存在時(shí),進(jìn)行設(shè)置操作,返回 1

> GET key
# 返回key的值

> GETSET key value
# 返回key的舊值,同時(shí)將key的值設(shè)置成value

實(shí)現(xiàn)的基本思想

key為任意值,value代表過期時(shí)間(unix time = now + expire)

實(shí)現(xiàn)Lock函數(shù),此函數(shù)進(jìn)行一次加鎖嘗試。

首先調(diào)用SETNX設(shè)置,如果成功,則成功獲得鎖。否則,用GET獲取key值,和當(dāng)前時(shí)間比較,如果對(duì)比發(fā)現(xiàn)過期,放棄加鎖。否則進(jìn)行下一步。用GETSET獲取key值,如果發(fā)現(xiàn)未過期,則加鎖成功。 如果過期,則表示有其他客戶端已經(jīng)先于本客戶端設(shè)置,放棄加鎖。

Talk is cheap, show me the code.

下面,就用一個(gè)redis單實(shí)例實(shí)現(xiàn)分布式鎖。如果是多客戶端使用,確??蛻舳藭r(shí)間一致。

/*
 expire: N秒后鎖失效,允許其他客戶端競(jìng)爭(zhēng)
*/
func Lock(conn redis.Conn, key string, expire int) bool {
    var now int64 = time.Now().Unix()

    r1, err := conn.Do("SETNX", key, now+int64(expire))
    if err != nil {
        return false
    }

    v1, err := redis.Int(r1, err)
    if err != nil {
        return false
    }

    if v1 == 1 {
        return true
    }

    /*此時(shí)key存在,查看對(duì)應(yīng)的值*/
    r, err := conn.Do("GET", key)
    if err != nil {
        return false
    }

    v2, err := redis.Int64(r, err)
    if err != nil {
        return false
    }

    if now < v2 {
        /*值未過期,表示其他客戶端占用資源,放棄鎖*/
        return false
    } else {
        /*獲取舊值,設(shè)置新值*/
        r, err := conn.Do("GETSET", key, now+int64(expire))
        if err != nil {
            return false
        }

        v3, err := redis.Int64(r, err)
        if err != nil {
            return false
        }

        if now >= v3 {
            return true
        }
        /*
          else情況:表示其他redis客戶端搶先一步設(shè)置成功,此時(shí)放棄鎖
          return false
        */
    }

    return false
}

/*
 釋放鎖
*/
func Unlock(conn redis.Conn, key string) bool {
    var _, err = conn.Do("DEL", key)
    if err != nil {
        return false
    }

    return true
}

/*
 嘗試加鎖
*/
func TryLock(conn redis.Conn, key string, expire int, timeout int) bool {
    var b = Lock(conn, key, expire)
    if b {
        return b
    }

    if timeout == 0 {
        return false
    }
    var ticker = time.NewTicker(time.Duration(timeout) * time.Second)
    defer ticker.Stop()
    for {
        select {
        case <-time.After(100 * time.Millisecond):
            if Lock(conn, key, expire) {
                /*成功lock后返回,否則一直持續(xù)到超時(shí)*/
                return true
            }
        }

        select {
        case <-ticker.C:
            return false
        default:
            //DO NOTHING
        }
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容