下面描述使用 Etcd 实现分布式锁的业务流程,假设对某个共享资源设置的锁名为:/lock/mylock

步骤 1: 准备

客户端连接 Etcd,以 /lock/mylock 为前缀创建全局唯一的 key,假设第一个客户端对应的 key="/lock/mylock/UUID1",第二个为 key="/lock/mylock/UUID2";客户端分别为自己的 key 创建租约 - Lease,租约的长度根据业务耗时确定,假设为 15s;

步骤 2: 创建定时任务作为租约的“心跳”

当一个客户端持有锁期间,其它客户端只能等待,为了避免等待期间租约失效,客户端需创建一个定时任务作为“心跳”进行续约。此外,如果持有锁期间客户端崩溃,心跳停止,key 将因租约到期而被删除,从而锁释放,避免死锁。

步骤 3: 客户端将自己全局唯一的 key 写入 Etcd

进行 put 操作,将步骤 1 中创建的 key 绑定租约写入 Etcd,根据 Etcd 的 Revision 机制,假设两个客户端 put 操作返回的 Revision 分别为 1、2,客户端需记录 Revision 用以接下来判断自己是否获得锁。

步骤 4: 客户端判断是否获得锁

客户端以前缀 /lock/mylock 读取 keyValue 列表(keyValue 中带有 key 对应的 Revision),判断自己 key 的 Revision 是否为当前列表中最小的,如果是则认为获得锁;否则监听列表中前一个 Revision 比自己小的 key 的删除事件,一旦监听到删除事件或者因租约失效而删除的事件,则自己获得锁。

步骤 5: 执行业务

获得锁后,操作共享资源,执行业务代码。

步骤 6: 释放锁

完成业务流程后,删除对应的key释放锁。

我们希望同一时间只有一个线程能够访问到资源,但是分布式资源点之间的协调会非常麻烦,这个时候我们就需要一个分布式锁。

etcd分布式锁实现原理:

1.利用租约在etcd集群中创建一个key,这个key有两种形态,存在和不存在,而这两种形态就是互斥量。
2.如果这个key不存在,那么线程创建key,成功则获取到锁,该key就为存在状态。
3.如果该key已经存在,那么线程就不能创建key,则获取锁失败。

锁结构体:

在使用该锁时,需要传入Ttl,Conf,Key字段来初始化锁

type EtcdMutex struct {
Ttl int64 //租约时间
Conf clientv3.Config //etcd集群配置
Key string //etcd的key
cancel context.CancelFunc //关闭续租的func
lease clientv3.Lease
leaseID clientv3.LeaseID
txn clientv3.Txn
}

初始化锁:

func(em *EtcdMutex)init()error{
var err error
var ctx context.Context
client,err := clientv3.New(em.Conf)
if err != nil{
return err
}
em.txn = clientv3.NewKV(client).Txn(context.TODO())
em.lease = clientv3.NewLease(client)
leaseResp,err := em.lease.Grant(context.TODO(),em.Ttl)
if err != nil{
return err
}
ctx,em.cancel = context.WithCancel(context.TODO())
em.leaseID = leaseResp.ID
_,err = em.lease.KeepAlive(ctx,em.leaseID)
return err
}

获取锁:


func(em *EtcdMutex)Lock()error{
err := em.init()
if err != nil{
return err
}
//LOCK:
em.txn.If(clientv3.Compare(clientv3.CreateRevision(em.Key),"=",0)).
Then(clientv3.OpPut(em.Key,"",clientv3.WithLease(em.leaseID))).
Else()
txnResp,err := em.txn.Commit()
if err != nil{
return err
}
if !txnResp.Succeeded{ //判断txn.if条件是否成立
return fmt.Errof("抢锁失败")
}
return nil
}

释放锁:

func(em *EtcdMutex)UnLock(){
em.cancel()
em.lease.Revoke(context.TODO(),em.leaseID)
fmt.Println("释放了锁")
}

调用锁:

func main(){
var conf = clientv3.Config{
Endpoints: []string{"172.16.196.129:2380", "192.168.50.250:2380"},
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()
if err != nil{
fmt.Println("groutine1抢锁失败")
fmt.Println(err)
return
}
fmt.Println("groutine1抢锁成功")
time.Sleep(10*time.Second)
defer eMutex.UnLock()
}() //groutine2
go func() {
err := eMutex2.Lock()
if err != nil{
fmt.Println("groutine2抢锁失败")
fmt.Println(err)
return
}
fmt.Println("groutine2抢锁成功")
defer eMutex.UnLock()
}()
time.Sleep(30*time.Second)
}

golang基于etcd实现分布式锁(转)的更多相关文章

  1. 一次基于etcd的分布式锁自动延时失败问题的排查

    今天在测试基于etcd的分布式锁过程中,在测试获取锁后,释放之前超出TTL时长的情况下自动延长TTL这部分功能,在延长指定key的TTL时总是返回404错误信息,在对目标KEY更新TTL时目标KEY已 ...

  2. golang使用etcd实现分布式锁

    package main import ( "context" "fmt" "time" "go.etcd.io/etcd/cli ...

  3. 基于 Redis 实现分布式锁

    1.主流分布式锁实现方案 基于数据库实现分布式锁 基于缓存(redis 等) 基于 Zookeeper 2.根据实现方式分类 类 CAS 自旋式分布式锁:询问的方式,类似 java 并发编程中的线程获 ...

  4. 基于redis 实现分布式锁的方案

    在电商项目中,经常有秒杀这样的活动促销,在并发访问下,很容易出现上述问题.如果在库存操作上,加锁就可以避免库存卖超的问题.分布式锁使分布式系统之间同步访问共享资源的一种方式 基于redis实现分布式锁 ...

  5. 基于redis的分布式锁

    <?php /** * 基于redis的分布式锁 * * 参考开源代码: * http://nleach.com/post/31299575840/redis-mutex-in-php * * ...

  6. 基于Redis的分布式锁真的安全吗?

    说明: 我前段时间写了一篇用consul实现分布式锁,感觉理解的也不是很好,直到我看到了这2篇写分布式锁的讨论,真的是很佩服作者严谨的态度, 把这种分布式锁研究的这么透彻,作者这种技术态度真的值得我好 ...

  7. 基于 Redis 的分布式锁

    前言 分布式锁在分布式应用中应用广泛,想要搞懂一个新事物首先得了解它的由来,这样才能更加的理解甚至可以举一反三. 首先谈到分布式锁自然也就联想到分布式应用. 在我们将应用拆分为分布式应用之前的单机系统 ...

  8. 基于 Zookeeper 的分布式锁实现

    1. 背景 最近在学习 Zookeeper,在刚开始接触 Zookeeper 的时候,完全不知道 Zookeeper 有什么用.且很多资料都是将 Zookeeper 描述成一个“类 Unix/Linu ...

  9. 基于redis的分布式锁(转)

    基于redis的分布式锁 1 介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分 ...

随机推荐

  1. php使用fastdfs

    php的服务器地址:10.10.1.2 fastdfs tracker地址:10.15.1.2 fastdfs storage地址:10.16.1.2 将fastdfs的源码上传到php所在服务器,进 ...

  2. TensorFlow机器学习实战指南之第二章

    一.计算图中的操作 在这个例子中,我们将结合前面所学的知识,传入一个列表到计算图中的操作,并打印返回值: 声明张量和占位符.这里,创建一个numpy数组,传入计算图操作: import tensorf ...

  3. 正则替换HTML里的style属性

    一个网友问: <p class="a" style="font-size: 12pt;  font-family: ""; color: red ...

  4. 用寄快递模拟数据在网络中的传送过程,发送一个ip数据报到接受这个ip数据报经历的过程

    发送一个ip数据报到接受这个ip数据报经历的过程 好比寄一个快递的过程 这个包裹一共有三层, 这个包裹的最里面是数据,数据 第二层写着目的ip地址和源ip地址,即收件人和发件人的姓名 第三层也就是最外 ...

  5. 【java】抽象类和接口区别

    1.语法层面上的区别 1)抽象类可以提供成员方法的实现细节,而接口中只能存在public abstract 方法: 2)抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public sta ...

  6. [转]自建Syncthing中继服务器(私密传输或造福大众)

    自建Syncthing中继服务器(私密传输或造福大众) 一.介绍 我之前介绍了Syncthing,作为一款开源的文件同步程序,它的功能还是非常强大的,我也很高兴能看到它被越来越多的人知道和使用,前几天 ...

  7. fatal error: No such file or directory

    在fase_ws中编译ground_car包,遇如下报错: /home/gordon/fase_ws/src/fsae/ground_car/plugins/gazebo_ros_block_lase ...

  8. ThinkPHP3.2 中空方法、空控制器和空模块的设置

    ThinkPHP3.2 中空方法.空控制器和空模块的设置 1.空方法设置 问题: 当你访问一个不存在的方法的时候: 如: http://localhost/test/index.php/Home/Us ...

  9. symfony generate bundle autoload failed的解决办法

    I also encountered this problem,I add new bundle namespace in composer.json"autoload": { & ...

  10. 搭建Mock Server

    1.为什么要搭建mock-server? 为了更好的分工合作,让前端能在不依赖后端环境的情况下进行开发,其中一种手段就是为前端开发者提供一个 web 容器,这个本地环境就是 mock-server. ...