在分布式系统中,分布式锁是一个重要的组件。etcd提供了一种简单而强大的方式来实现分布式锁。
以下是使用etcd实现分布式锁的基本流程:
- 创建一个租约
leaseResp, err := cli.Grant(context.Background(), 10)
if err != nil {
log.Fatal(err)
}
- 尝试获取锁
lockKey := "my-lock"
kv := clientv3.NewKV(cli)
txn := kv.Txn(context.Background())
txn.If(clientv3.Compare(clientv3.CreateRevision(lockKey), "=", 0)).Then(
clientv3.OpPut(lockKey, "", clientv3.WithLease(leaseResp.ID)),
).Else(
// 如果不能获取到锁,则返回错误
err = errors.New("failed to acquire lock")
)
resp, err := txn.Commit()
if err != nil {
log.Fatal(err)
}
if !resp.Succeeded {
log.Fatal("failed to acquire lock")
}
在上面的代码中,我们使用了etcd事务(transaction)API来实现分布式锁。如果没有其他节点已经持有该键,则可以通过创建一个空值来获得该键并与租约关联。
否则,我们会收到一个“compare failed”错误,并且需要重新尝试获取锁。
- 保持租约有效
keepAliveChan, err := cli.KeepAlive(context.Background(), leaseResp.ID)
if err != nil {
log.Fatal(err)
}
for {
select {
case <- keepAliveChan:
// 租约续期成功,不需要做任何处理
case <-time.After(time.Second * 15):
// 没有收到续期成功通知,说明失去了锁,重新获取
log.Println("Lost lock")
goto lock // 跳转到尝试获取锁的步骤
}
}
在上面的代码中,我们使用keep-alive机制来保持租约有效。如果我们丢失了租约(例如由于网络故障或etcd服务器崩溃),则会触发一个“lease expired”错误,并且我们需要重新获取该键以获得新的租约。
- 释放锁
_, err = kv.Delete(context.Background(), lockKey)
if err != nil {
log.Fatal(err)
}
最后,当我们完成了任务并准备释放锁时,只需删除与该键相关联的值即可。
注意,在实际使用中,我们还需要考虑更多的情况(例如死锁、锁竞争等)。因此,在编写真正可靠和高效的分布式应用程序时,请仔细考虑各种可能性。




