golang基于etcd实现分布式锁(转)
下面描述使用 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实现分布式锁(转)的更多相关文章
- 一次基于etcd的分布式锁自动延时失败问题的排查
今天在测试基于etcd的分布式锁过程中,在测试获取锁后,释放之前超出TTL时长的情况下自动延长TTL这部分功能,在延长指定key的TTL时总是返回404错误信息,在对目标KEY更新TTL时目标KEY已 ...
- golang使用etcd实现分布式锁
package main import ( "context" "fmt" "time" "go.etcd.io/etcd/cli ...
- 基于 Redis 实现分布式锁
1.主流分布式锁实现方案 基于数据库实现分布式锁 基于缓存(redis 等) 基于 Zookeeper 2.根据实现方式分类 类 CAS 自旋式分布式锁:询问的方式,类似 java 并发编程中的线程获 ...
- 基于redis 实现分布式锁的方案
在电商项目中,经常有秒杀这样的活动促销,在并发访问下,很容易出现上述问题.如果在库存操作上,加锁就可以避免库存卖超的问题.分布式锁使分布式系统之间同步访问共享资源的一种方式 基于redis实现分布式锁 ...
- 基于redis的分布式锁
<?php /** * 基于redis的分布式锁 * * 参考开源代码: * http://nleach.com/post/31299575840/redis-mutex-in-php * * ...
- 基于Redis的分布式锁真的安全吗?
说明: 我前段时间写了一篇用consul实现分布式锁,感觉理解的也不是很好,直到我看到了这2篇写分布式锁的讨论,真的是很佩服作者严谨的态度, 把这种分布式锁研究的这么透彻,作者这种技术态度真的值得我好 ...
- 基于 Redis 的分布式锁
前言 分布式锁在分布式应用中应用广泛,想要搞懂一个新事物首先得了解它的由来,这样才能更加的理解甚至可以举一反三. 首先谈到分布式锁自然也就联想到分布式应用. 在我们将应用拆分为分布式应用之前的单机系统 ...
- 基于 Zookeeper 的分布式锁实现
1. 背景 最近在学习 Zookeeper,在刚开始接触 Zookeeper 的时候,完全不知道 Zookeeper 有什么用.且很多资料都是将 Zookeeper 描述成一个“类 Unix/Linu ...
- 基于redis的分布式锁(转)
基于redis的分布式锁 1 介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分 ...
随机推荐
- 为什么以sys无法远程登录数据库
今天通过pl/sql连接一个数据库,普通用户能连接上,但是sys就连接不上,告诉我用户名密码错误.之前其实遇到过这个问题,然后就开始查找原因.整个过程记录如下,结果是扎心的,过程也不是完全都懂.记下来 ...
- SQL Server Service Broker 示例(转)
1.定义数据类型.协议和服务(发送服务和接收服务) USE master; GO ALTER DATABASE 目标数据库 SET ENABLE_BROKER; GO -- 如果上面的操作执行后,长时 ...
- 给jumpserver双机配置glusterfs共享复制卷
为什么要使用glusterfs呢. 本身Haproxy+Keepalived对jumpserver进行了负载均衡和反向代理.但是真实的视频只会存储在一个节点上 否则播放视频的时候会出现找不到的情况 为 ...
- 查看当前mysql使用的配置文件是哪个
my.cnf是mysql启动时加载的配置文件,一般会放在mysql的安装目录中,用户也可以放在其他目录加载.安装mysql后,系统中会有多个my.cnf文件,有些是用于测试的.使用locate my. ...
- Java高级特性 第10节 IDEA和Eclipse整合JUnit测试框架
一.IDEA整合Junit测试框架 1.安装插件 打开File菜单的下拉菜单settings[设置] : 点击左侧Plugins[插件]菜单 在输入框中输入JUnitGenerator 2.0,点击I ...
- Linux printf命令详解
Linux printf命令 printf命令模仿了C语言中的printf()函数.主要作用是输出文本,按照我们指定的格式输出文本.还有一个输出文本的命令echo,在输出文本时,echo会换行.pri ...
- 群晖NAS同步文件,防止Mac OS X自动休眠的办法
背景: NAS drive同步文件到移动硬盘,需要消耗很长时间.但长时间不动电脑,mac又会自动关闭所有application,进入休眠模式,导致同步任务被终止. 使用系统的节能设置配置也没能成功关闭 ...
- promise 基础知识
promise 基础知识 proise:1.Promise是异步编程的一种解决方案,它有三种状态,分别是pending-进行中.resolved-已完成.rejected-已失败2.创建实例//met ...
- centos7卸载旧jdk安装新jdk1.8
卸载旧JDK版本 需卸载centos7自带的JDK1.7 rpm -qa|grep jdk 列出已安装jdk版本 rpm -e --nodeps java-1.7.0-openjdk-1.7. ...
- PVID和VID彻底研究(上) ——PVID的作用及和VID的区别
http://blog.csdn.net/cybertan/article/details/8348752 另外一篇 netgear的官方文档: http://club.netgear.cn/Know ...