手把手教媳妇 -ETCD实现分布式锁

package etcdmux

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

//分布式锁
type EtcdMutex struct {
    Ttl int64 //租约时间

    Conf clientv3.Config

    Key string //etcd的key

    cancel context.CancelFunc //关闭续约的func

    lease clientv3.Lease //租约

    leaseId clientv3.LeaseID //租约ID

    txn clientv3.Txn //etcd事务

}

//初始化锁
func (mutex *EtcdMutex) init() error {

    client, err := clientv3.New(mutex.Conf)

    if err != nil {
        return err
    }
    //初始化txn
    mutex.txn = clientv3.NewKV(client).Txn(context.TODO())
    //初始化租约
    mutex.lease = clientv3.NewLease(client)
    //申请一个租约
    leaseResp, err := mutex.lease.Grant(context.TODO(), mutex.Ttl)

    if err != nil {
        return err
    }

    var ctx context.Context

    //初始化mutex的cancelFunc
    ctx, mutex.cancel = context.WithCancel(context.TODO())
    //初始化leaseId
    mutex.leaseId = leaseResp.ID
    //自动续约
    _, err = mutex.lease.KeepAlive(ctx, mutex.leaseId)

    return err

}

//获取锁
func (mutex *EtcdMutex) Lock() error {

    err := mutex.init()

    if err != nil {
        return err
    }
    //Lock,如果不存在key,那就创建一个key,并且提交事务
    mutex.txn.If(clientv3.Compare(clientv3.CreateRevision(mutex.Key), "=", 0)).
        Then(clientv3.OpPut(mutex.Key, "", clientv3.WithLease(mutex.leaseId))).
        Else()
    txnResp, err := mutex.txn.Commit()
    if err != nil {
        return err
    }
    if !txnResp.Succeeded { //判断txn.if条件是否成立
        return fmt.Errorf("枪锁失败")
    }
    return nil

}

//释放锁
func (mutex *EtcdMutex) UnLock() {
    mutex.cancel()
    //撤销租约
    mutex.lease.Revoke(context.TODO(), mutex.leaseId)
    fmt.Println("释放了锁")
}

测试代码A-(利用两个goroutine来测试抢夺锁)

//测试锁
func  TestMutex(t *testing.T) {
    var conf = clientv3.Config{
        Endpoints:   []string{"127.0.0.1:2379"},
        DialTimeout: 5 * time.Second,
    }

    //两把锁
    eMutex1 := &EtcdMutex{
        Conf: conf,
        Ttl:  10,
        Key:  "lock",
    }
    eMutex2 := &EtcdMutex{
        Conf: conf,
        Ttl:  10,
        Key:  "lock",
    }
    //groutine1
    go func() {
        err := eMutex1.Lock()
        defer eMutex1.UnLock()
        if err != nil {
            fmt.Println("groutine1抢锁失败")
            fmt.Println(err)
            return
        }
        fmt.Println("groutine1抢锁成功")
        time.Sleep(10 * time.Second)

    }()

    //groutine2
    go func() {
        err := eMutex2.Lock()
        defer eMutex2.UnLock()
        if err != nil {
            fmt.Println("groutine2抢锁失败")
            fmt.Println(err)
            return
        }
        fmt.Println("groutine2抢锁成功")

    }()
    time.Sleep(30 * time.Second)
}

启动etcd,多运行几次,会随机出现goroutinne1和goroutine2其中一个抢到锁另一个抢不到的情况

注意:此为不可重入锁

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。