go语言&etcd实现分布式乐观锁

399 阅读1分钟

基于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已经注册
}