前言

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

etcd分布式锁设计

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

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

  1. func Lock(key string, cli *clientv3.Client) error {
  2. //获取key,判断是否存在锁
  3. resp, err := cli.Get(context.Background(), key)
  4. if err != nil {
  5. return err
  6. }
  7. //锁存在,返回上锁失败
  8. if len(resp.Kvs) > 0 {
  9. return errors.New("lock fail")
  10. }
  11. _, err = cli.Put(context.Background(), key, "lock")
  12. if err != nil {
  13. return err
  14. }
  15. return nil
  16. }
  17. //删除key,解锁
  18. func UnLock(key string, cli *clientv3.Client) error {
  19. _, err := cli.Delete(context.Background(), key)
  20. return err
  21. }

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

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

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

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

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

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

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

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

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

  1. //上锁
  2. func Lock(key string, cli *clientv3.Client) error {
  3. //获取key,判断是否存在锁
  4. resp, err := cli.Get(context.Background(), key)
  5. if err != nil {
  6. return err
  7. }
  8. //锁存在,等待解锁后再竞争锁
  9. if len(resp.Kvs) > 0 {
  10. waitDelete(key, cli)
  11. return Lock(key)
  12. }
  13. //设置key过期时间
  14. resp, err := cli.Grant(context.TODO(), 30)
  15. if err != nil {
  16. return err
  17. }
  18. //设置key并绑定过期时间
  19. _, err = cli.Put(context.Background(), key, "lock", clientv3.WithLease(resp.ID))
  20. if err != nil {
  21. return err
  22. }
  23. //延续key的过期时间
  24. _, err = cli.KeepAlive(context.TODO(), resp.ID)
  25. if err != nil {
  26. return err
  27. }
  28. return nil
  29. }
  30. //通过让key值过期来解锁
  31. func UnLock(resp *clientv3.LeaseGrantResponse, cli *clientv3.Client) error {
  32. _, err := cli.Revoke(context.TODO(), resp.ID)
  33. return err
  34. }

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

etcd分布式锁使用

  1. func ExampleMutex_Lock() {
  2. cli, err := clientv3.New(clientv3.Config{Endpoints: endpoints})
  3. if err != nil {
  4. log.Fatal(err)
  5. }
  6. defer cli.Close()
  7. // create two separate sessions for lock competition
  8. s1, err := concurrency.NewSession(cli)
  9. if err != nil {
  10. log.Fatal(err)
  11. }
  12. defer s1.Close()
  13. m1 := concurrency.NewMutex(s1, "/my-lock/")
  14. s2, err := concurrency.NewSession(cli)
  15. if err != nil {
  16. log.Fatal(err)
  17. }
  18. defer s2.Close()
  19. m2 := concurrency.NewMutex(s2, "/my-lock/")
  20. // acquire lock for s1
  21. if err := m1.Lock(context.TODO()); err != nil {
  22. log.Fatal(err)
  23. }
  24. fmt.Println("acquired lock for s1")
  25. m2Locked := make(chan struct{})
  26. go func() {
  27. defer close(m2Locked)
  28. // wait until s1 is locks /my-lock/
  29. if err := m2.Lock(context.TODO()); err != nil {
  30. log.Fatal(err)
  31. }
  32. }()
  33. if err := m1.Unlock(context.TODO()); err != nil {
  34. log.Fatal(err)
  35. }
  36. fmt.Println("released lock for s1")
  37. <-m2Locked
  38. fmt.Println("acquired lock for s2")
  39. // Output:
  40. // acquired lock for s1
  41. // released lock for s1
  42. // acquired lock for s2
  43. }

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

etcd事务

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

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

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

  1. func ExampleKV_txn() {
  2. cli, err := clientv3.New(clientv3.Config{
  3. Endpoints: endpoints,
  4. DialTimeout: dialTimeout,
  5. })
  6. if err != nil {
  7. log.Fatal(err)
  8. }
  9. defer cli.Close()
  10. kvc := clientv3.NewKV(cli)
  11. _, err = kvc.Put(context.TODO(), "key", "xyz")
  12. if err != nil {
  13. log.Fatal(err)
  14. }
  15. ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
  16. _, err = kvc.Txn(ctx).
  17. // txn value comparisons are lexical
  18. If(clientv3.Compare(clientv3.Value("key"), ">", "abc")).
  19. // the "Then" runs, since "xyz" > "abc"
  20. Then(clientv3.OpPut("key", "XYZ")).
  21. // the "Else" does not run
  22. Else(clientv3.OpPut("key", "ABC")).
  23. Commit()
  24. cancel()
  25. if err != nil {
  26. log.Fatal(err)
  27. }
  28. gresp, err := kvc.Get(context.TODO(), "key")
  29. cancel()
  30. if err != nil {
  31. log.Fatal(err)
  32. }
  33. for _, ev := range gresp.Kvs {
  34. fmt.Printf("%s : %s\n", ev.Key, ev.Value)
  35. }
  36. // Output: key : XYZ
  37. }

总结

如果发展到分布式服务阶段,且对数据的可靠性要求很高,选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. 点击Qtableview表头,触发事件

    connect(horizontalHeader(), SIGNAL(sectionClicked(int)), this, SLOT(onHeaderClicked(int))); refer to ...

  2. VR全景视图 Google VrPanoramaView

    2019独角兽企业重金招聘Python工程师标准>>> 一.背景简介 Welcome to VR at Google 进入Google VR主页,发现官方给我们提供了两套解决观看VR ...

  3. web前端开发中的各种居中

    居中是我们使用css来布局时常遇到的情况.使用css来进行居中时,有时一个属性就能搞定,有时则需要一定的技巧才能兼容到所有浏览器,本文就居中的一些常用方法做个简单的介绍. 注:本文所讲方法除了特别说明 ...

  4. 与IBM的Lin Sun关于Istio 1.0和微服务的问答

    北京时间 7 月 31 日,Istio 正式发布了 1.0 版本,并表示已经可用于生产环境.该版本的主要新特性包括跨集群 mesh 支持.细粒度流量控制以及在一个 mesh 中增量推出 mutual ...

  5. 老男孩Linux运维50期 --于海科--决心书

    1.我叫于海科,来自于甘肃省天水市,之前就读于兰州石化职业技术学院,我是听之前的学长说老男孩教育出来就业不错,我特此来这培训希望出来能够找到一份不错的工作.2.五个月学完,目标薪资是11k.3.达到目 ...

  6. elementUI字体图标不显示问题

    原文链接: 点我 自己搭建的Vue项目,没有使用vue-cli,引入elementUI时提示字体图标404,找不到文件,如下错误: GET http://localhost:9090/WEB-INF/ ...

  7. Java_Web--JDBC 增加记录操作模板

    如果不能成功链接数据库,我的博客JAVA中有详细的介绍,可以看一下 import java.sql.Connection; import java.sql.DriverManager; import ...

  8. Docker配置TLS认证,修复因暴露2375端口引发漏洞

    1.环境准备 # 查看Docker服务器主机名hostnamectl 这里记住我的主机名s130就好 # 静态主机名修改vi /etc/hostname# 临时主机名修改(重启失效)hostname  ...

  9. 写给Android 混淆小白的快速混淆方法

    为啥子要混淆 简单来说,Android 进行ProGuard,可以起到压缩,混淆,预检,优化的功能,虽然不能说更安全但还是一个不容忽视的环节. 开始混淆第一步 首先在build.gradle 中将混淆 ...

  10. POJ 2777——线段树Lazy的重要性

    POJ 2777 Count Color --线段树Lazy的重要性 原题 链接:http://poj.org/problem?id=2777 Count Color Time Limit: 1000 ...