Golang中實(shí)現(xiàn)分布式鎖的可靠方案

golang中實(shí)現(xiàn)分布式鎖需考慮安全性、可靠性與性能,主要方案包括:1. 基于redis的分布式鎖,使用setnx命令和過(guò)期時(shí)間實(shí)現(xiàn),優(yōu)點(diǎn)是實(shí)現(xiàn)簡(jiǎn)單、性能高,缺點(diǎn)是可能存在鎖過(guò)期或續(xù)租機(jī)制復(fù)雜;2. 基于zookeeper的分布式鎖,利用臨時(shí)順序節(jié)點(diǎn)實(shí)現(xiàn),可靠性高但性能較低且實(shí)現(xiàn)較復(fù)雜;3. 基于etcd的分布式鎖,采用lease和watch機(jī)制,性能較高且易于部署,但實(shí)現(xiàn)也較復(fù)雜。選擇方案時(shí)應(yīng)根據(jù)業(yè)務(wù)需求權(quán)衡性能與可靠性,redis適合高性能場(chǎng)景,zookeeper和etcd適合高可靠性場(chǎng)景。續(xù)租機(jī)制用于延長(zhǎng)鎖的有效期,防止任務(wù)執(zhí)行期間鎖被釋放;避免誤刪除可通過(guò)設(shè)置唯一value并在釋放鎖時(shí)進(jìn)行校驗(yàn)實(shí)現(xiàn),確保僅刪除自身持有的鎖。

Golang中實(shí)現(xiàn)分布式鎖的可靠方案

golang中實(shí)現(xiàn)分布式鎖,需要考慮鎖的安全性、可靠性和性能。簡(jiǎn)單來(lái)說(shuō),就是確保在分布式環(huán)境下,只有一個(gè)客戶(hù)端能獲得鎖,并且即使發(fā)生故障,鎖也能被正確釋放。

Golang中實(shí)現(xiàn)分布式鎖的可靠方案

解決方案

實(shí)現(xiàn)分布式鎖,通常有以下幾種方案,各有優(yōu)劣:

Golang中實(shí)現(xiàn)分布式鎖的可靠方案

  1. 基于redis的分布式鎖: 利用redis的SETNX (SET if Not Exists) 命令和過(guò)期時(shí)間來(lái)實(shí)現(xiàn)。SETNX保證只有一個(gè)客戶(hù)端能成功設(shè)置鎖,過(guò)期時(shí)間防止死鎖。

    立即學(xué)習(xí)go語(yǔ)言免費(fèi)學(xué)習(xí)筆記(深入)”;

    Golang中實(shí)現(xiàn)分布式鎖的可靠方案

    • 優(yōu)點(diǎn): 實(shí)現(xiàn)簡(jiǎn)單,性能高。
    • 缺點(diǎn): 可能存在鎖過(guò)期時(shí)間不足導(dǎo)致鎖被提前釋放,或鎖續(xù)租機(jī)制復(fù)雜。需要考慮Redlock算法來(lái)提高可靠性,但Redlock本身也存在爭(zhēng)議。
    import (     "github.com/go-redis/redis/v8"     "context"     "time" )  func acquireLock(ctx context.Context, rdb *redis.Client, lockKey string, lockValue string, expireTime time.Duration) (bool, error) {     ok, err := rdb.SetNX(ctx, lockKey, lockValue, expireTime).Result()     if err != nil {         return false, err     }     return ok, nil }  func releaseLock(ctx context.Context, rdb *redis.Client, lockKey string, lockValue string) error {     // 使用lua腳本保證原子性     script := `         if redis.call("GET", KEYS[1]) == ARGV[1] then             return redis.call("DEL", KEYS[1])         else             return 0         end     `     result, err := rdb.Eval(ctx, script, []string{lockKey}, lockValue).Result()     if err != nil {         return err     }     if result == int64(1) {         return nil     }     return nil // 鎖已被其他客戶(hù)端釋放 }
  2. 基于ZooKeeper的分布式鎖: 利用ZooKeeper的臨時(shí)順序節(jié)點(diǎn)來(lái)實(shí)現(xiàn)??蛻?hù)端創(chuàng)建一個(gè)臨時(shí)順序節(jié)點(diǎn),序號(hào)最小的節(jié)點(diǎn)獲得鎖。當(dāng)客戶(hù)端崩潰時(shí),臨時(shí)節(jié)點(diǎn)自動(dòng)刪除,釋放鎖。

    • 優(yōu)點(diǎn): 可靠性高,鎖的釋放由ZooKeeper保證。
    • 缺點(diǎn): 性能相對(duì)較低,實(shí)現(xiàn)復(fù)雜。需要引入ZooKeeper客戶(hù)端庫(kù)。
    import (     "github.com/go-zookeeper/zk"     "time"     "sort"     "strconv" )  func acquireLockZk(conn *zk.Conn, lockPath string, data []byte) (string, error) {     // 創(chuàng)建臨時(shí)順序節(jié)點(diǎn)     path, err := conn.Create(lockPath+"/", data, 0, zk.WorldACL(zk.PermAll))     if err != nil {         return "", err     }      // 獲取所有子節(jié)點(diǎn)     children, _, err := conn.Children(lockPath)     if err != nil {         return "", err     }      sort.Strings(children)      // 判斷當(dāng)前節(jié)點(diǎn)是否是最小的節(jié)點(diǎn)     if path == lockPath+"/"+children[0] {         return path, nil // 獲得鎖     }      // 監(jiān)聽(tīng)前一個(gè)節(jié)點(diǎn)     index, _ := strconv.Atoi(path[len(lockPath)+1:])     prevIndex := index - 1     if prevIndex < 0 {         prevIndex = 0     }     prevNode := lockPath + "/" + children[prevIndex]      _, _, ch, err := conn.GetW(prevNode)     if err != nil {         return "", err     }      <-ch // 阻塞等待前一個(gè)節(jié)點(diǎn)釋放      return acquireLockZk(conn, lockPath, data) // 遞歸嘗試獲取鎖 }  func releaseLockZk(conn *zk.Conn, lockPath string) error {     err := conn.Delete(lockPath, -1)     return err }
  3. 基于Etcd的分布式鎖: 類(lèi)似于ZooKeeper,利用Etcd的lease機(jī)制和watch機(jī)制來(lái)實(shí)現(xiàn)。

    • 優(yōu)點(diǎn): 相比ZooKeeper,Etcd性能更高,更易于部署。
    • 缺點(diǎn): 實(shí)現(xiàn)復(fù)雜。需要引入Etcd客戶(hù)端庫(kù)。
    import (     "context"     "time"     "github.com/coreos/etcd/clientv3" )  func acquireLockEtcd(cli *clientv3.Client, lockKey string, leaseTTL int64) (clientv3.LeaseID, error) {     // 創(chuàng)建 lease     resp, err := cli.Grant(context.TODO(), leaseTTL)     if err != nil {         return clientv3.LeaseID(0), err     }      // 嘗試獲取鎖     _, err = cli.Put(context.TODO(), lockKey, "locked", clientv3.WithLease(resp.ID))     if err != nil {         return clientv3.LeaseID(0), err     }      // 續(xù)租,保持鎖     keepAliveChan, err := cli.KeepAlive(context.TODO(), resp.ID)     if err != nil {         return clientv3.LeaseID(0), err     }      go func() {         for range keepAliveChan {         }     }()      return resp.ID, nil }  func releaseLockEtcd(cli *clientv3.Client, lockKey string, leaseID clientv3.LeaseID) error {     _, err := cli.Revoke(context.TODO(), leaseID)     return err }

如何選擇合適的分布式鎖方案?

選擇哪種方案取決于具體的業(yè)務(wù)場(chǎng)景。如果對(duì)性能要求較高,且可以容忍一定的錯(cuò)誤率,Redis是一個(gè)不錯(cuò)的選擇。如果對(duì)可靠性要求極高,ZooKeeper或Etcd更合適。

分布式鎖的續(xù)租機(jī)制是什么?為什么需要續(xù)租?

續(xù)租機(jī)制是為了防止鎖在任務(wù)執(zhí)行期間過(guò)期而導(dǎo)致鎖被其他客戶(hù)端獲取。當(dāng)客戶(hù)端獲得鎖后,會(huì)定期向鎖服務(wù)發(fā)送續(xù)租請(qǐng)求,延長(zhǎng)鎖的過(guò)期時(shí)間。

如何避免分布式鎖的誤刪除?

在使用Redis時(shí),為了避免客戶(hù)端A刪除了客戶(hù)端B的鎖,需要在設(shè)置鎖時(shí),設(shè)置一個(gè)唯一的value(例如UUID),釋放鎖時(shí),先判斷鎖的value是否與客戶(hù)端持有的value一致,只有一致時(shí)才刪除鎖??梢允褂肔ua腳本來(lái)保證判斷和刪除操作的原子性。

? 版權(quán)聲明
THE END
喜歡就支持一下吧
點(diǎn)贊5 分享
站長(zhǎng)的頭像-小浪學(xué)習(xí)網(wǎng)月度會(huì)員