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 //關(guān)閉續(xù)約的func
lease clientv3.Lease //租約
leaseId clientv3.LeaseID //租約ID
txn clientv3.Txn //etcd事務(wù)
}
//初始化鎖
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
//自動續(xù)約
_, err = mutex.lease.KeepAlive(ctx, mutex.leaseId)
return err
}
//獲取鎖
func (mutex *EtcdMutex) Lock() error {
err := mutex.init()
if err != nil {
return err
}
//Lock,如果不存在key,那就創(chuàng)建一個key,并且提交事務(wù)
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,多運行幾次,會隨機出現(xiàn)goroutinne1和goroutine2其中一個搶到鎖另一個搶不到的情況
注意:此為不可重入鎖