当前位置 博文首页 > 魏小言的博客:Consul分布式锁原理详解及实例

    魏小言的博客:Consul分布式锁原理详解及实例

    作者:[db:作者] 时间:2021-06-24 12:41

    Consul分布式锁原理详解及实例

    现在大型系统为了保证服务高可用,都采用分布式架构提供服务。Consul是一款较zookeepr、etcd等年轻的纯Goland分布式服务架构。其包含多个功能模块服务发现、检查健康、K/V存储、多数据中心等,这里就其分布式锁进行介绍。

    Consul分布式锁实现和其K/V存储的特性密切相关。K/V存储在业务中最常见的之一是Redis「基于K/V设计的存储、中间件等等很多,相对Redis是业务开发中接触更普遍的其中一个」,其SetNx可以出色的完成分布式锁的功能。Consul与Redis机制不同,Consul依赖K/V与Session的绑定关系,进而完成互斥锁定。

    Session是什么呢/?在Consul中,session的概念和浏览器的session类似,标识本次操作owner,类似句柄。与Node/K-V关联,上游是一Node,下游多个K-V,相关系列函数包含renew、acquire、destroy、create…。
    「源码:github.com/hashicorp/consul/api@v1.4.0/session.go」

    K-V就是常规键值对,在Consul中,通过Api进行Get-Set。
    「源码:github.com/hashicorp/consul/api@v1.4.0/kv.go」

    Consul实现分布式锁,准确来讲,和K/V、Session各自功能没什么关联,主要应用的是两者之间的关系。
    简单来说,根据Key绑定的Session判断是否可抢占。流程见下图:
    在这里插入图片描述

    其详细源码见:github.com/hashicorp/consul/api@v1.4.0/lock.go

    其中含大量参数细节,着重注意:

    // Lock 分布式锁数据结构
    type Lock struct {
        c    *Client   // 提供访问consul的API客户端
        opts *LockOptions // 分布式锁的可选项
        isHeld       bool          // 该锁当前是否已经被持有
        sessionRenew chan struct{} // 通知锁持有者需要更新session
        locksession  string        // 锁持有者的session
        l            sync.Mutex    // 锁变量的互斥锁
    }
    // LockOptions 提供分布式锁的可选项参数
    type LockOptions struct {
        Key              string        // 锁的 Key,必填项,且必须有 KV 的写权限
        Value            []byte        // 锁的内容,以下皆为选填项
        Session          string        // 锁的session,用于判断锁是否被创建
        SessionOpt       *SessionEntry // 自定义创建session条目,用于创建session,避免惊群
        SessionName      string        // 自定义锁的session名称,默认为 "Consul API Lock"
        SessionTTL       string        // 自定义锁的TTL时间,默认为 "15s"
        MonitorRetries   int          // 自定义监控的重试次数,避免脑裂问题
        MonitorRetryTime time.Duration // 自定义监控的重试时长,避免脑裂问题
        LockWaitTime     time.Duration // 自定义锁的等待市场,避免死锁问题
        LockTryOnce      bool          // 是否只重试一次,默认为false,则为无限重试
    }
    

    注意:lock delay需要注意一下。 在使用etcd和redis
    redlock实现分布式锁的时候,一个节点释放锁,另一个节点可以立马拿到锁, 就算有延迟也只是网络上的调用开销。但consul的lock
    delay策略不是这样的,一个节点释放锁了,另一个节点不能立马拿到锁。需要等待lock delay时间后才可以拿到锁「脑裂现象」。

    Q&A
    1、Consul分布式锁最终会落地以Api形式到Consul,那么Consul如何保证两个Http请求的原子性/?
    Consul分布式强一致性算法Raft,及满足CAP的弱一致性方案Gossip协议。


    2、Consul分布式强一致性与Session提供的分布式锁关系/? Session分布式锁基于Consul强一致特性

    「要正确得实现了Paxos和Raft算法都不是一件特别容易的事,尤其是Paxos,Raft还好些。但如果想每一个服务都嵌入一个Paxos或者Raft,这对于维护将是一场灾难。Google的做法是使用Chubby作为一致性的基础服务提供者,在这之上提供分布式锁和Leader选举的服务(是不是有点PaaS的味道)。」

    下附:
    1、文档:
    https://learn.hashicorp.com/tutorials/consul/application-leader-elections#overview
    https://learn.hashicorp.com/tutorials/consul/application-leader-elections
    2、编码demo

    func DynamicCollectLog(arguments docopt.Opts) {
        lockCollect, err := lock.ConsulLock()
        if err != nil {
            log.Infof("DynamicCollectLog ConsulLock Lock fail")
            return
        }
        defer lockCollect.Unlock()
        _ = CmdCollect(arguments)
        sigCh := make(chan os.Signal, 1)
        signal.Notify(sigCh, syscall.SIGTERM, syscall.SIGINT)
        for {
            select {
            case sig, _ := <-sigCh:
                fmt.Printf("DynamicCollectLog ConsulLock(%s),  Unlock...\n", sig)
                log.Errorf("DynamicCollectLog ConsulLock(%s),  Unlock...", sig)
                err = lockCollect.Unlock()
                if err != nil {
                    log.Infof("DynamicCollectLog ConsulUnlock fail")
                }else{
                    log.Info("DynamicCollectLog ConsulLock Unlocked")
                }
            }
        }
    }
    
    
    func ConsulLock() (lock *api.Lock, err error) {
        client, err := api.NewClient(&api.Config{})
        if err != nil {
            log.Infof("ConsulLock failed to created client %v", err)
            return nil, err
        }
        lockKey := "FlowAnalysis_Collect_Consul_Lock_key"
        lock, err = client.LockOpts(&api.LockOptions{
            Key:        lockKey,
            Value:      []byte("sender 1"),
            SessionTTL: "10s",
        })
        if err != nil {
            log.Infof("ConsulLock failed to created lock %v", err)
            return nil, err
        }
        _, err = lock.Lock(nil)
        if err != nil {
            log.Infof("ConsulLock failed to accquired lock")
            return nil, err
        }
        return lock, nil
    }
    
    下一篇:没有了