基于golang etcd sdk实现分布式乐观锁
乐观锁是一种锁的思想主要是基于版本号来控制。基于本文了解一些golang基础&etcd客户端操作即可时间分布式锁
mysql乐观锁
1.开启事物,查询当前版本号
2.执行业务流程,修改数据,更改同时判断版本号是否和前面查询的一致
3.如果版本号一致完成修改,不一致重复1.2步骤
基于mysql写分布式锁细节逻辑
1.构建etcd客户端
2.创建kv,带上lease(防止宕机后死锁)
3.lease自动续约,防止业务执行的过程中lease被释放
4.业务完成后,释放锁
基于上面四点我们开始编写golang代码
package main
import (
"context"
"fmt"
clientv3 "go.etcd.io/etcd/client/v3"
"time"
)
var (
conf clientv3.Config
err error
client *clientv3.Client
lease clientv3.Lease
ctx context.Context
leaseResp *clientv3.LeaseGrantResponse
LKARespChan <-chan *clientv3.LeaseKeepAliveResponse
LKAResp *clientv3.LeaseKeepAliveResponse
cancelFunc context.CancelFunc
txn clientv3.Txn
kv clientv3.KV
txResp *clientv3.TxnResponse
)
func main() {
conf = clientv3.Config{
Endpoints: []string{"127.0.0.1:2379"},
DialTimeout: 1 * time.Second,
}
if client, err = clientv3.New(conf); err != nil {
fmt.Println(err)
}
ctx = context.TODO()
// 创建租约,续租,防止服务宕机一直锁
lease = clientv3.NewLease(client)
if leaseResp, err = lease.Grant(ctx, 5); err != nil {
fmt.Println(err)
}
ctx, cancelFunc = context.WithCancel(ctx)
if LKARespChan, err = lease.KeepAlive(ctx, leaseResp.ID); err != nil {
fmt.Println(err)
}
// 协程 监控续约状态
go func() {
for {
select {
case LKAResp = <-LKARespChan:
if LKAResp != nil {
fmt.Println(LKAResp.ID, "续约成功")
} else {
fmt.Println(LKAResp.ID, "续约失败")
goto END
}
}
}
END:
}()
// 提前注册结束函数
defer cancelFunc()
// 立即释放租约
defer lease.Revoke(context.TODO(), leaseResp.ID)
// 上锁 + 事物
kv = clientv3.NewKV(client)
txn = kv.Txn(ctx)
// 创建版本号为0说明没有人获取锁
txn.If(clientv3.Compare(clientv3.CreateRevision("cron/lock"), "=", 0)).
Then(clientv3.OpPut("cron/lock", "y", clientv3.WithLease(leaseResp.ID))).Else(clientv3.OpGet("cron/lock"))
if txResp, err = txn.Commit(); err != nil {
fmt.Println(err)
}
// 判断锁是否抢占成功
if !txResp.Succeeded {
fmt.Println("抢锁失败")
return
}
// 业务
time.Sleep(3 * time.Second)
// 释放租约,释放锁
// defer已经注册
}