之前写过用 redis 实现的分布式锁,这次用etcd来实现一个。

原理

首先获取一个etcd的租约,拿着这个租约用etcd的事务操作去设置一个key,如果设置成功,就表示抢到了锁,否则抢索失败。租约的作用就是实现抢到锁之后的释放功能,防止长期占用。

etcd的txn事务比较特别,是一个IF-THEN-ELSE的形式,其中IF接收的参数是一个比较操作。多说无益,看代码吧。

代码
package main
import (
"context"
"fmt"
"go.etcd. io /etcd/clientv3"
"time"
)
func main() {
config := clientv3.Config{
Endpoints: []string{"127.0.0.1:2379"},
DialTimeout: 5 * time.Second,
}
client, err := clientv3.New(config)
if err !=  nil  {
fmt.Print(err)
}
lease := clientv3.NewLease(client)
leaseResp, err := lease.Grant(context.TODO(), 5)
if err != nil {
fmt.Println(err)
}
leaseID := leaseResp.ID
ctx, cancelFunc := context.WithCancel(context.TODO())
 //两个defer用于释放锁
defer cancelFunc()
defer lease.Revoke(context.TODO(), leaseID)
 
 //抢锁和占用期间,需要不停的续租,续租方法返回一个只读的 channel 
keepChan, err := lease.KeepAlive(ctx, leaseID)
if err != nil {
fmt.Println(err)
}
 //处理续租返回的信息
go func() {
for {
select {
case keepResp := <-keepChan:
if keepChan == nil {
fmt.Println("lease out")
goto END
} else {
fmt.Println("get resp", keepResp.ID)
}
}
}
END:
}()
kv := clientv3.NewKV(client)
txn := kv.Txn(context.TODO())
 //开始抢锁事务操作
txn.If(clientv3.Compare(clientv3.CreateRevision("/lock/9"), "=", 0)).Then(clientv3.OpPut("/lock/9", "", clientv3.WithLease(leaseID))).Else(clientv3.OpGet("/lock/9"))
 //提交事务
txnResp, err := txn.Commit()
 
if err != nil {
fmt.Println(err)
return
}
 //如果抢锁成功
if txnResp.Succeeded {
fmt.Println("success")
} else {
fmt.Println("fail")
}
time.Sleep(5 * time.Second)
}