前言

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

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. Excel导入异常Cannot get a text value from a numeric cell解决及poi导入时注意事项

    POI操作Excel时偶尔会出现Cannot get a text value from a numeric cell的异常错误. 异常原因:Excel数据Cell有不同的类型,当我们试图从一个数字类 ...

  2. python- 函数高级

    函数高级 一.默认参数 1.默认参数概念 默认参数指函数/方法在定义时为形参赋值,对应的形参称为默认参数. 默认参数是一个参数定义期的概念,与调用无关. 2.默认参数的作用 如果参数定义默认参数,在调 ...

  3. 《Java 开发从入门到精通》—— 2.3 使用IDE工具序

    本节书摘来异步社区<Java 开发从入门到精通>一书中的第2章,第2.3节,作者: 扶松柏 , 陈小玉,更多章节内容可以访问云栖社区"异步社区"公众号查看. 2.3 使 ...

  4. Vue中的父子传值问题

    个人网站 https://iiter.cn 程序员导航站 开业啦,欢迎各位观众姥爷赏脸参观,如有意见或建议希望能够不吝赐教! 好久没更博了,感觉下班后的时间莫名其妙就没有了,有了,了... 趁着端午放 ...

  5. keep-alive的深入理解与使用(配合router-view缓存整个路由页面)

    原文链接: 点我 在搭建 vue 项目时,有某些组件没必要多次渲染,所以需要将组件在内存中进行‘持久化’,此时 <keep-alive> 便可以派上用场了. <keep-alive& ...

  6. 数学--数论--HDU 6063 RXD and math (跟莫比乌斯没有半毛钱关系的打表)

    RXD is a good mathematician. One day he wants to calculate: output the answer module 109+7. p1,p2,p3 ...

  7. HDU - 6187 (最大生成树) 最小生成树

    Destroy Walls Time Limit: 8000/4000 MS (Java/Others)    Memory Limit: 132768/132768 K (Java/Others) ...

  8. 数据库SQL---实体-联系模型(E-R模型)

    1.E-R模型的基本概念与表示 1)实体与实体集 (1)实体:可以是有形的.实在的事物(如每个员工),可以是抽象的.概念上存在的事物(如一个部门). (2)实体的特征:独立存在.可区别于其他实体. ( ...

  9. 获取Wi-Fi的SSID

    前几天做项目的时候,碰到一个问题,获取wifi的SSID,其实就是获取Wi-Fi的名字 iOS12以前 在iOS13之前获取wifi的SSID很简单,苹果提供了接口CNCopyCurrentNetwo ...

  10. Hello World的五十种不同实现方法!!!!!

    我们作为一名程序员,职业生涯中至少完成了一个“Hello, World!“程序.当我们学习一门新的语言时,“Hello, World!“通常是我们所写的第一个程序.程序员一般也都会使用多门语言,甚至有 ...