etcd实现分布式锁分析
3篇关于分布式锁的文章,可以结合看:
consul实现分布式锁:https://www.cnblogs.com/jiujuan/p/10527786.html
redis实现分布式锁:https://www.cnblogs.com/jiujuan/p/10595838.html
etcd实现分布式锁:https://www.cnblogs.com/jiujuan/p/12147809.html
分布式锁简介
在单机情况下,锁的环境比较简单,因为都是在单机的环境里。
而在分布式情况下,多机环境里。由原来的单机系统变成了分布式系统。分布式系统的多线程、多进程分布在不同的机器上,在加上网络这个因素,要控制一个共享资源的使用就复杂得多。比如,网络超时怎么办?网络不可用怎么办?发生死锁怎么办? 等等... ... 一系列问题。
在分布式情况下,需要设计一种分布式锁,来解决这些问题。
分布式锁问题和特性
设想一下,如果是你来设计一个分布式锁,你会怎样考虑?锁应该具有哪些特性?获取锁过程中会出现什么问题?要解决哪些问题?
经过长时间google后 O(∩_∩)O!,一般会出现下面这些主要问题:
死锁:什么是死锁?就是资源抢占的各方,彼此都在等待对方释放资源,以便自己能获取系统资源,但是没有哪一方退出,这时候就死锁了
惊群效应:多线程/多进程等待同一个socket事件,当这个事件发生时,这些线程/进程被同时唤醒,就是惊群
脑裂:当集群中出现 脑裂 的时候,往往会出现多个 master 的情况,这样数据的一致性会无法得到保障,从而导致整个服务无法正常运行
下面这些特性的:
高可用:也就是可靠性。组成集群的分布式锁系统,某一台机器锁不能提供服务了,其他机器仍然可以提供锁服务。
互斥性:就像单机系统的锁特性一样具有互斥性。不过分布式系统是由多个机器节点组成的。如果有一个节点获取了锁,其他节点必须等待锁释放或者锁超时后,才可以去获取锁资源。
可重入:一个节点获取了锁之后,还可以再次获取整个锁资源。
高效和锁超时:高效是指获取和释放锁高效。 锁超时,防止死锁的发生。
公平锁:节点依次获取锁资源。
etcd如何实现分布式锁
etcd是怎么解决上面这些问题?它提供了哪些功能来解决上述的特性。
- 1.raft
raft,是工程上使用较为广泛,强一致性、去中心化、高可用的分布式协议。
raft提供了分布式系统的可靠性功能。
读者可以自行查阅raft相关的资料。比如这个 raft网站,它不仅介绍了raft算法,还在网站最下面提供了不同语言的raft实现。raft算法比paxos算法好理解一些。
- 2.lease功能
lease功能,就是租约机制(time to live)。
1、etcd可以对存储key-value的数据设置租约,也就是给key-value设置一个过期时间,当租约到期,key-value将会失效而被etcd删除。
2、etcd同时也支持续约租期,可以通过客户端在租约到期之间续约,以避免key-value失效;
3、etcd还支持解约,一旦解约,与该租约绑定的key-value将会失效而删除。
Lease 功能可以保证分布式锁的安全性,为锁对应的 key 配置租约,即使锁的持有者因故障而不能主动释放锁,锁也会因租约到期而自动释放。
- 3.watch功能
监听功能。watch 机制支持监听某个固定的key,它也支持watch一个范围(前缀机制),当被watch的key或范围发生变化时,客户端将收到通知。
在实现分布式锁时,如果抢锁失败,可通过 Prefix 机制返回的 KeyValue 列表获得 Revision 比自己小且相差最小的 key(称为 pre-key),对 pre-key 进行监听,因为只有它释放锁,自己才能获得锁,如果 Watch 到 pre-key 的 DELETE 事件,则说明pre-ke已经释放,自己已经持有锁。
- 4.prefix功能
前缀机制。也称目录机制,如两个 key 命名如下:key1=“/mykey/key1″ , key2=”/mykey/key2″,那么,可以通过前缀-“/mykey”查询,返回包含两个 key-value 对的列表。可以和前面的watch功能配合使用。
例如,一个名为 /mylock 的锁,两个争抢它的客户端进行写操作,实际写入的 key 分别为:key1=”/mylock/UUID1″,key2=”/mylock/UUID2″,其中,UUID 表示全局唯一的 ID,确保两个 key 的唯一性。很显然,写操作都会成功,但返回的 Revision 不一样,那么,如何判断谁获得了锁呢?通过前缀 /mylock 查询,返回包含两个 key-value 对的的 KeyValue 列表,同时也包含它们的 Revision,通过 Revision 大小,客户端可以判断自己是否获得锁,如果抢锁失败,则等待锁释放(对应的 key 被删除或者租约过期),然后再判断自己是否可以获得锁。
lease 功能和 prefix功能,能解决上面的死锁问题。
- 5.revision功能
每个 key 带有一个 Revision 号,每进行一次事务加一,因此它是全局唯一的,如初始值为 0,进行一次 put(key, value),key 的 Revision 变为 1;同样的操作,再进行一次,Revision 变为 2;换成 key1 进行 put(key1, value) 操作,Revision 将变为 3。
这种机制有一个作用:
通过 Revision 的大小就可以知道进行写操作的顺序。在实现分布式锁时,多个客户端同时抢锁,根据 Revision 号大小依次获得锁,可以避免 “羊群效应” (也称 “惊群效应”),实现公平锁。
etcd的V3版本分布式锁分析
在etcd的v3的client里有一个concurrency的包,里面实现了分布式锁。
源代码在mutex.go
/clientv3/concurrency/session.go
type Session struct {
client *v3.Client
opts *sessionOptions
id v3.LeaseID
cancel context.CancelFunc
donec <-chan struct{}
}
/clientv3/concurrency/mutex.go 分布锁实现分析
// Mutex implements the sync Locker interface with etcd
type Mutex struct {
s *Session //上面的Session struct
pfx string //前缀
myKey string //key
myRev int64 //Revision
hdr *pb.ResponseHeader
}
func NewMutex(s *Session, pfx string) *Mutex {
return &Mutex{s, pfx + "/", "", -1, nil}
}
// Lock locks the mutex with a cancelable context. If the context is canceled
// while trying to acquire the lock, the mutex tries to clean its stale lock entry.
func (m *Mutex) Lock(ctx context.Context) error {
s := m.s //上面的Session struct
client := m.s.Client()
//m.pfx是前缀,比如"myresource/lock/"
//s.Lease()是一个64位的整数值,etcd v3引入了lease(租约)的概念,concurrency包基于lease封装了session,
//每一个客户端都有自己的lease,也就是说每个客户端都有一个唯一的64位整形值
//m.myKey类似于"myresource/lock/12345"
m.myKey = fmt.Sprintf("%s%x", m.pfx, s.Lease())
//etcdv3新引入的多键条件事务,替代了v2中Compare-And-put操作。
//etcdv3的多键条件事务的语意是先做一个比较(compare)操作,
//如果比较成立则执行一系列操作,如果比较不成立则执行另外一系列操作。
//接下来的这部分实现了如果不存在这个key,则将这个key写入到etcd,如果存在则读取这个key的值这样的功能。
//下面这一句,是构建了一个compare的条件,比较的是key的createRevision(createRevision是表示这个key创建时被分配的这个序号。
//当key不存在时,createRevision是0。),如果revision是0,则存入一个key,如果revision不为0,则读取这个key。
//revision是etcd一个全局的序列号,全局唯一且递增,每一个对etcd存储进行改动都会分配一个这个序号,在v2中叫index
cmp := v3.Compare(v3.CreateRevision(m.myKey), "=", 0) //cmp 比较Revision, 当key不存在时,createRevision是0。
// put self in lock waiters via myKey; oldest waiter holds lock
put := v3.OpPut(m.myKey, "", v3.WithLease(s.Lease()))
// reuse key in case this session already holds the lock
get := v3.OpGet(m.myKey)
// 如果revision为0,则存入,否则获取
resp, err := client.Txn(ctx).If(cmp).Then(put).Else(get).Commit()
if err != nil {
return err
}
// 本次操作的revision
m.myRev = resp.Header.Revision
// 操作失败,则获取else返回的值,即已有的revision
if !resp.Succeeded {
m.myRev = resp.Responses[0].GetResponseRange().Kvs[0].CreateRevision
}
ownerKey := resp.Responses[1].GetResponseRange().Kvs
if len(ownerKey) == 0 || ownerKey[0].CreateRevision == myRev {
m.hdr = resp.Header
return nil
//成功获取锁
}
//如果上面的code操作成功了,则myRev是当前客户端创建的key的revision值。
//waitDeletes等待匹配m.pfx ("/myresource/lock/")这个前缀(可类比在这个目录下的)并且createRivision小于m.myRev-1所有key被删除
//如果没有比当前客户端创建的key的revision小的key,则当前客户端者获得锁
//如果有比它小的key则等待,比它小的被删除
hdr, werr = waitDeletes(ctx, client, m.pfx, m.myRev-1)
// release lock key if wait failed
if werr != nil {
m.Unlock(client.Ctx())
} else {
m.hdr = hdr
}
return werr
}
参考:
- https://blog.csdn.net/cadem/article/details/56495834
- https://github.com/coreos/etcd
- https://github.com/jiujuan/tech-collection/blob/master/Etcd/README.md
etcd实现分布式锁分析的更多相关文章
- 一次基于etcd的分布式锁自动延时失败问题的排查
今天在测试基于etcd的分布式锁过程中,在测试获取锁后,释放之前超出TTL时长的情况下自动延长TTL这部分功能,在延长指定key的TTL时总是返回404错误信息,在对目标KEY更新TTL时目标KEY已 ...
- golang基于etcd实现分布式锁(转)
下面描述使用 Etcd 实现分布式锁的业务流程,假设对某个共享资源设置的锁名为:/lock/mylock 步骤 1: 准备 客户端连接 Etcd,以 /lock/mylock 为前缀创建全局唯一的 k ...
- etcd实现分布式锁
转载自:etcd实现分布式锁 当并发的访问共享资源的时候,如果没有加锁的话,无法保证共享资源安全性和正确性.这个时候就需要用到锁 1.需要具备的特性 需要保证互斥访问(分布式环境需要保证不同节点.不同 ...
- golang使用etcd实现分布式锁
package main import ( "context" "fmt" "time" "go.etcd.io/etcd/cli ...
- 基于go+etcd实现分布式锁
原文链接:https://www.yii-china.com/topic/detail/113 package main import ( "context" "fmt& ...
- ETCD分布式锁实现选主机制(Golang实现)
ETCD分布式锁实现选主机制(Golang) 为什么要写这篇文章 做架构的时候,涉及到系统的一个功能,有一个服务必须在指定的节点执行,并且需要有个节点来做任务分发,想了半天,那就搞个主节点做这事呗,所 ...
- etcd分布式锁及事务
前言 分布式锁是控制分布式系统之间同步访问共享资源的一种方式.在分布式系统中,常常需要协调他们的动作.如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互 ...
- Redis 中的原子操作(3)-使用Redis实现分布式锁
Redis 中的分布式锁如何使用 分布式锁的使用场景 使用 Redis 来实现分布式锁 使用 set key value px milliseconds nx 实现 SETNX+Lua 实现 使用 R ...
- 利用多写Redis实现分布式锁原理与实现分析(转)
利用多写Redis实现分布式锁原理与实现分析 一.关于分布式锁 关于分布式锁,可能绝大部分人都会或多或少涉及到. 我举二个例子:场景一:从前端界面发起一笔支付请求,如果前端没有做防重处理,那么可能 ...
- RedissonLock分布式锁源码分析
最近碰到的一个问题,Java代码中写了一个定时器,分布式部署的时候,多台同时执行的话就会出现重复的数据,为了避免这种情况,之前是通过在配置文件里写上可以执行这段代码的IP,代码中判断如果跟这个IP相等 ...
随机推荐
- [转帖]Oracle中INITRANS和MAXTRANS参数
每个块都有一个块首部.这个块首部中有一个事务表.事务表中会建立一些条目来描述哪些事务将块上的哪些行/元素锁定.这个事务表的初始大小由对象的INITRANS 设置指定.对于表,这个值默认为2(索引的IN ...
- [转帖]Jmeter压力测试工具安装及使用教程
https://www.cnblogs.com/monjeo/p/9330464.html 一.Jmeter下载 进入官网:http://jmeter.apache.org/ 1.第一步进入官网如下图 ...
- Stream的简单学习
Stream的简单学习 前言 https://github.com/jeffhammond/STREAM unzip STREAM-master.zip cd /STREAM-master/ make ...
- [转帖]读Brendan Gregg - 谈性能分析
https://zhuanlan.zhihu.com/p/206743670 Brendan Gregg何许人 Brendan Gregg在性能分析工业界如雷贯耳, 相信看到这篇文章的人肯定知道他的大 ...
- adb驱动安装
学会adb,工资涨一千 win系统安装 1.安装adb首先需要去官网下载adb安装包,下载完成后解压会有一个adb目录以及目录下四个文件 2.然后将adb目录mv到C:\Windows下,配置环境变量 ...
- SHA加密在实际应用中的优势与局限
SHA加密算法简介 SHA(Secure Hash Algorithm)加密算法是一种单向加密算法,常用于加密数据的完整性校验和加密签名.它是由美国国家安全局(NSA)设计并广泛应用于各种安全场景.S ...
- ThreadLocal源码解析及实战应用
作者:京东物流 闫鹏勃 1 什么是ThreadLocal? ThreadLocal是一个关于创建线程局部变量的类. 通常情况下,我们创建的变量是可以被任何一个线程访问并修改的.而使用ThreadLoc ...
- 让你彻底理解Typescript中静态成员和抽象方法
1.Typescript中static详解 静态成员:在类中通过 static 修饰的属性或者方法 那么就是静态的属性静态方法 也称之为:静态成员(静态成员包含静态属性和静态方法) 静态成员在使用的时 ...
- 【代码片段】makefile 中通过 shell 函数执行 sed
作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢! cnblogs博客 zhihu Github 公众号:一本正经的瞎扯 先上代码:(在 macos 上调试通过) # define ...
- Vue基础系列文章10---单文件组件
1.单文件组件的结构 <template> <!--这里用于定义VUE组件的模块内容--> <dvi> <h1>这是 APP 根组件</h1> ...