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其中一个抢到锁另一个抢不到的情况
注意:此为不可重入锁