你真正了解过Consul吗——掌握Consul分布式锁一篇就够了

Posted 魏小言

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了你真正了解过Consul吗——掌握Consul分布式锁一篇就够了相关的知识,希望对你有一定的参考价值。

Consul

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

Consul分布式锁

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

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

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

Lock流程图

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,则为无限重试
}

编码实例

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")
            }
        }
    }
    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
}

Q&A

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

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

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

附录

架构图需要抽象,抽象不等于罗列!

欢迎关注收藏,带你了解Consul分布式架构设计原理!

以上是关于你真正了解过Consul吗——掌握Consul分布式锁一篇就够了的主要内容,如果未能解决你的问题,请参考以下文章

JAVA面试总结

spring cloud 之 -- eureka vs consul,该选择谁?

Consul 介绍

使用 Consul 作为 Python 微服务的配置中心

渣渣菜鸡的蚂蚁金服面试经历

图解Janusgraph系列-并发安全,建议收藏