學習etcd分散式鎖

charliecen發表於2021-11-15

之前使用redis分散式鎖,最近看了下etcd的分散式鎖,下面是測試demo

package main

import (
    "context"
    "fmt"
    "github.com/coreos/etcd/clientv3"
    "time"
)

func main() {
    conf = clientv3.Config{
        Endpoints:            []string{"localhost:2379"},
        DialTimeout:          5 * time.Second,
    }

    go tryLock1()
    go tryLock2()
    go tryLock3()
    go tryLock4()
    time.Sleep(10 * time.Second)

}

var conf clientv3.Config

type EtcdMutex struct {
    Ttl int64
    Conf clientv3.Config
    Key string
    cancel context.CancelFunc
    txn clientv3.Txn
    lease clientv3.Lease
    LeaseID clientv3.LeaseID
}

// 初始化鎖
func (em *EtcdMutex) init() error {
    var (
        err error
        ctx context.Context
    )
    client, err := clientv3.New(em.Conf)
    if err != nil {
        return err
    }
    em.txn = clientv3.NewKV(client).Txn(context.TODO())
    em.lease = clientv3.NewLease(client)
    leaseResp, err := em.lease.Grant(context.TODO(), em.Ttl)
    if err != nil {
        return err
    }

    ctx, em.cancel = context.WithCancel(context.TODO())
    em.LeaseID = leaseResp.ID
    _, err = em.lease.KeepAlive(ctx, em.LeaseID)
    return err
}

// 獲取鎖
func (em *EtcdMutex) Lock() error {
    err := em.init()
    if err != nil {
        return err
    }

    // lock, 當前key版本等於0,則建立空值,並賦上租約ID,並提交
    txnResp, err := em.txn.If(clientv3.Compare(clientv3.CreateRevision(em.Key), "=", 0)).
        Then(clientv3.OpPut(em.Key, "", clientv3.WithLease(em.LeaseID))).
        Else().Commit()

    if err != nil {
        return err
    }

    // 是否建立成功
    if !txnResp.Succeeded {
        return fmt.Errorf("獲取鎖失敗")
    }
    return nil
}

// 釋放鎖
func (em *EtcdMutex) Unlock(name string) {
    // 租約取消
    em.cancel()
    // 釋放租期
    _, _ = em.lease.Revoke(context.TODO(), em.LeaseID)
    fmt.Printf("%s 釋放鎖", name)
}

func tryLock1() {
    e := &EtcdMutex{
        Conf: conf,
        Ttl: 10,
        Key: "lock",
    }
    err := e.Lock()
    if err != nil {
        fmt.Println("go1 獲取鎖失敗")
        return
    }
    defer e.Unlock("go1")

    fmt.Println("go1 獲取鎖成功")
    time.Sleep(1 * time.Second)
}

func tryLock2() {
    e := &EtcdMutex{
        Conf: conf,
        Ttl: 10,
        Key: "lock",
    }
    err := e.Lock()
    if err != nil {
        fmt.Println("go2 獲取鎖失敗")
        return
    }
    defer e.Unlock("go2")

    fmt.Println("go2 獲取鎖成功")
    time.Sleep(1 * time.Second)
}

func tryLock3() {
    e := &EtcdMutex{
        Conf: conf,
        Ttl: 10,
        Key: "lock",
    }
    err := e.Lock()
    if err != nil {
        fmt.Println("go3 獲取鎖失敗")
        return
    }
    defer e.Unlock("go3")

    fmt.Println("go3 獲取鎖成功")
    time.Sleep(1 * time.Second)
}

func tryLock4() {
    e := &EtcdMutex{
        Conf: conf,
        Ttl: 10,
        Key: "lock",
    }
    err := e.Lock()
    if err != nil {
        fmt.Println("go4 獲取鎖失敗")
        return
    }
    defer e.Unlock("go4")

    fmt.Println("go4 獲取鎖成功")
    time.Sleep(1 * time.Second)
}

執行結果

go3 獲取鎖失敗
go2 獲取鎖成功
go4 獲取鎖失敗
go1 獲取鎖失敗
go2 釋放鎖
程式完成,並顯示退出程式碼 0
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章