前言

分布式锁是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中,常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁。

etcd分布式锁设计

  1. 排他性:任意时刻,只能有一个机器的一个线程能获取到锁。

通过在etcd中存入key值来实现上锁,删除key实现解锁,参考下面伪代码:

func Lock(key string, cli *clientv3.Client) error {
//获取key,判断是否存在锁
resp, err := cli.Get(context.Background(), key)
if err != nil {
return err
}
//锁存在,返回上锁失败
if len(resp.Kvs) > 0 {
return errors.New("lock fail")
}
_, err = cli.Put(context.Background(), key, "lock")
if err != nil {
return err
}
return nil
}
//删除key,解锁
func UnLock(key string, cli *clientv3.Client) error {
_, err := cli.Delete(context.Background(), key)
return err
}

当发现已上锁时,直接返回lock fail。也可以处理成等待解锁,解锁后竞争锁。

//等待key删除后再竞争锁
func waitDelete(key string, cli *clientv3.Client) {
rch := cli.Watch(context.Background(), key)
for wresp := range rch {
for _, ev := range wresp.Events {
switch ev.Type {
case mvccpb.DELETE: //删除
return
}
}
}
}
  1. 容错性:只要分布式锁服务集群节点大部分存活,client就可以进行加锁解锁操作。

    etcd基于Raft算法,确保集群中数据一致性。

  2. 避免死锁:分布式锁一定能得到释放,即使client在释放之前崩溃。

    上面分布式锁设计有缺陷,假如client获取到锁后程序直接崩了,没有解锁,那其他线程也无法拿到锁,导致死锁出现。

    通过给key设定leases来避免死锁,但是leases过期时间设多长呢?假如设了30秒,而上锁后的操作比30秒大,会导致以下问题:

  • 操作没完成,锁被别人占用了,不安全

  • 操作完成后,进行解锁,这时候把别人占用的锁解开了

解决方案:给key添加过期时间后,以Keep leases alive方式延续leases,当client正常持有锁时,锁不会过期;当client程序崩掉后,程序不能执行Keep leases alive,从而让锁过期,避免死锁。看以下伪代码:

//上锁
func Lock(key string, cli *clientv3.Client) error {
//获取key,判断是否存在锁
resp, err := cli.Get(context.Background(), key)
if err != nil {
return err
}
//锁存在,等待解锁后再竞争锁
if len(resp.Kvs) > 0 {
waitDelete(key, cli)
return Lock(key)
}
//设置key过期时间
resp, err := cli.Grant(context.TODO(), 30)
if err != nil {
return err
}
//设置key并绑定过期时间
_, err = cli.Put(context.Background(), key, "lock", clientv3.WithLease(resp.ID))
if err != nil {
return err
}
//延续key的过期时间
_, err = cli.KeepAlive(context.TODO(), resp.ID)
if err != nil {
return err
}
return nil
}
//通过让key值过期来解锁
func UnLock(resp *clientv3.LeaseGrantResponse, cli *clientv3.Client) error {
_, err := cli.Revoke(context.TODO(), resp.ID)
return err
}

经过以上步骤,我们初步完成了分布式锁设计。其实官方已经实现了分布式锁,它大致原理和上述有出入,接下来我们看下如何使用官方的分布式锁。

etcd分布式锁使用

func ExampleMutex_Lock() {
cli, err := clientv3.New(clientv3.Config{Endpoints: endpoints})
if err != nil {
log.Fatal(err)
}
defer cli.Close() // create two separate sessions for lock competition
s1, err := concurrency.NewSession(cli)
if err != nil {
log.Fatal(err)
}
defer s1.Close()
m1 := concurrency.NewMutex(s1, "/my-lock/") s2, err := concurrency.NewSession(cli)
if err != nil {
log.Fatal(err)
}
defer s2.Close()
m2 := concurrency.NewMutex(s2, "/my-lock/") // acquire lock for s1
if err := m1.Lock(context.TODO()); err != nil {
log.Fatal(err)
}
fmt.Println("acquired lock for s1") m2Locked := make(chan struct{})
go func() {
defer close(m2Locked)
// wait until s1 is locks /my-lock/
if err := m2.Lock(context.TODO()); err != nil {
log.Fatal(err)
}
}() if err := m1.Unlock(context.TODO()); err != nil {
log.Fatal(err)
}
fmt.Println("released lock for s1") <-m2Locked
fmt.Println("acquired lock for s2") // Output:
// acquired lock for s1
// released lock for s1
// acquired lock for s2
}

此代码来源于官方文档,etcd分布式锁使用起来很方便。

etcd事务

顺便介绍一下etcd事务,先看这段伪代码:

Txn(context.TODO()).If(//如果以下判断条件成立
Compare(Value(k1), "<", v1),
Compare(Version(k1), "=", 2)
).Then(//则执行Then代码段
OpPut(k2,v2), OpPut(k3,v3)
).Else(//否则执行Else代码段
OpPut(k4,v4), OpPut(k5,v5)
).Commit()//最后提交事务

使用例子,代码来自官方文档

func ExampleKV_txn() {
cli, err := clientv3.New(clientv3.Config{
Endpoints: endpoints,
DialTimeout: dialTimeout,
})
if err != nil {
log.Fatal(err)
}
defer cli.Close() kvc := clientv3.NewKV(cli) _, err = kvc.Put(context.TODO(), "key", "xyz")
if err != nil {
log.Fatal(err)
} ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
_, err = kvc.Txn(ctx).
// txn value comparisons are lexical
If(clientv3.Compare(clientv3.Value("key"), ">", "abc")).
// the "Then" runs, since "xyz" > "abc"
Then(clientv3.OpPut("key", "XYZ")).
// the "Else" does not run
Else(clientv3.OpPut("key", "ABC")).
Commit()
cancel()
if err != nil {
log.Fatal(err)
} gresp, err := kvc.Get(context.TODO(), "key")
cancel()
if err != nil {
log.Fatal(err)
}
for _, ev := range gresp.Kvs {
fmt.Printf("%s : %s\n", ev.Key, ev.Value)
}
// Output: key : XYZ
}

总结

如果发展到分布式服务阶段,且对数据的可靠性要求很高,选etcd实现分布式锁不会错。介于对ZooKeeper好感度不强,这里就不介绍ZooKeeper分布式锁了。一般的Redis分布式锁,可能出现锁丢失的情况(如果你是Java开发者,可以使用Redisson客户端实现分布式锁,据说不会出现锁丢失的情况)。

etcd分布式锁及事务的更多相关文章

  1. ETCD分布式锁实现选主机制(Golang实现)

    ETCD分布式锁实现选主机制(Golang) 为什么要写这篇文章 做架构的时候,涉及到系统的一个功能,有一个服务必须在指定的节点执行,并且需要有个节点来做任务分发,想了半天,那就搞个主节点做这事呗,所 ...

  2. 分布式ID生成器及redis,etcd分布式锁

    分布式id生成器 有时我们需要能够生成类似MySQL自增ID这样不断增大,同时又不会重复的id.以支持业务中的高并发场景.比较典型的,电商促销时,短时间内会有大量的订单涌入到系统,比如每秒10w+.明 ...

  3. golang基于etcd实现分布式锁(转)

    下面描述使用 Etcd 实现分布式锁的业务流程,假设对某个共享资源设置的锁名为:/lock/mylock 步骤 1: 准备 客户端连接 Etcd,以 /lock/mylock 为前缀创建全局唯一的 k ...

  4. etcd实现分布式锁

    转载自:etcd实现分布式锁 当并发的访问共享资源的时候,如果没有加锁的话,无法保证共享资源安全性和正确性.这个时候就需要用到锁 1.需要具备的特性 需要保证互斥访问(分布式环境需要保证不同节点.不同 ...

  5. python使用redis实现协同控制的分布式锁

    python使用redis实现协同控制的分布式锁 上午的时候,有个腾讯的朋友问我,关于用zookeeper分布式锁的设计,他的需求其实很简单,就是节点之间的协同合作. 我以前用redis写过一个网络锁 ...

  6. Etcd 使用场景:通过分布式锁思路实现自动选主

    分布式锁?选主? 分布式锁可以保证当有多台实例同时竞争一把锁时,只有一个人会成功,其他的都是失败.诸如共享资源修改.幂等.频控等场景都可以通过分布式锁来实现. 还有一种场景,也可以通过分布式锁来实现, ...

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

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

  8. Redis事务和分布式锁

    Redis事务 Redis中的事务(transaction)是一组命令的集合.事务同命令一样都是Redis最小的执行单位,一个事务中的命令要么都执行,要么都不执行.Redis事务的实现需要用到 MUL ...

  9. redis事务,分布式锁

    事务:一组命令集合 主要命令multi 和exec multi set a 1 sadd s1 a ...... exec 错误处理 (1)语法错误 127.0.0.1:6379> multi ...

随机推荐

  1. React Native 在 Airbnb 的起起落落

    写在前面 Airbnb 早在 2016 年就上了 React Native 大船,是很具代表性的先驱布道者: In 2016, we took a big bet on React Native. T ...

  2. Libra教程之:Libra协议的关键概念

    文章目录 Libra协议 交易和状态 交易详解 账本状态详解 版本数据库 账户 账户地址 Proof 验证节点 存储 Libra协议 Libra协议是Libra区块链的基础,本文主要讲解Libra协议 ...

  3. 【Linux常见问题】CentOS 6 root用户密码忘记,找回密码方法

    1.Linux的root密码修改不像Windows的密码修改找回,Windows的登录密码忘记需要介入工具进行解决.CentOS6和CentOS7的密码方法也是不一样的,具体如下: 2.centos ...

  4. js中的filter

    filter是常说的增删改查中的'查',当对一个数组进行筛选时,经常会使用indexOf 和es6中的includes()方法.filter是es5中的一种迭代方法,其定义为:对数组中的每一项运行给定 ...

  5. POJ 2054 Color a Tree解题报告

    题干 Bob is very interested in the data structure of a tree. A tree is a directed graph in which a spe ...

  6. React Hooks总结

    Hook 前言 什么是Hook 自从 16.8 版本开始,hooks 的出现使得你可以在不编写 class 的情况下使用状态管理以及其它 React 的特性. 那么在 React Hooks 出现之前 ...

  7. redis系列之4----redis高级应用(集群搭建、集群分区原理、集群操作)

    文章主目录 Redis集群简介 Redis集群搭建 Redis集群分区原理 集群操作 参考文档 本文是redis学习系列的第四篇,前面我们学习了redis的数据结构和一些高级特性,点击下面链接可回看 ...

  8. 微信小程序-swiper(轮播图)抖动问题

    ps:问题 组件swiper(轮播图)真机上不自动滚动 一直卡在那里抖动 以前遇到这个问题,官方一直没有正面回复.就搁置了,不过有大半年没写小程序了也没去关注,今天就去看了下官方文档,发觉更新了点好东 ...

  9. OpenCV 经纬法将鱼眼图像展开

    文章目录 前言 理论部分 鱼眼展开流程 鱼眼标准坐标计算 标准坐标系与球坐标的转换 代码实现 测试效果如下图 总结 this demo on github 前言 鱼眼镜头相比传统的镜头,视角更广,采集 ...

  10. 想要年薪百万,阿里Sentinel支持RESTful接口都搞不定?

    最近正准备用阿里Sentinel,发现RESTful接口支持的不是很好.有些童鞋可能对Sentinel不是很了解,我们先简单介绍一下. Sentinel简介 Sentinel是一套阿里巴巴开源的流量防 ...