Consul 的一致性读分析

背景

Consul 作为HashiCorp 出品的分布式注册中心和配置中心,是cp模型的,即强调一致性,通过raft协议实现

一致性

consul 一致性支持三种模式,即要强一致还是,最终一致, 可以交个用户选择,这才是一个优秀的分布式系统应该具备的,要了解一致性读,需要先了解consul的三种一致性模式,如下:

  • Default
    用consul 没有做任何改动的话,大部分都是这个模式工作的,default模式consul考虑了读的一致性还是很高的,读写都是通过leader来处理的,只是一种情况出现脑裂时,可能存在2个leader,在服务,但是老的leader肯定是不能写的,但是有可能服务读,读到过期的数据,但也不是一直会这样,leader有个租约,租约到期这个leader就下线了。
  • Consistent
    consistent 即强一致读,如果是这个模式,consul 每次读请求都要向集群的超过半数的server检查他是不是leader,就比defalut 模式多一次rtt开销,因为即使你是leader,还要请求server确认是否存在其他的leader,这样肯定不会读到过时的数据。

  • Stale
    stale是吞吐量最高的模式,但也是一致性最差的模式,所以一致性和吞吐量是矛盾的,因为stale 模式下,consul 集群的任何节点都能服务读请求,意外着即使集群没有leader,还是可以对外提供读请求。

我们了解了consul支持三种一致性模式,你是不是很好奇,consul是怎么实现的呢,我们平时部署一个consul集群也没有让我指定是那一种啥,consul既然是交给用户来选择,所以consul通过api的参数来确定,需要用那种读一致性。

在哪里指定一致性级别

有聪明的同学就会问,说了这么多,我到底在哪里指定这个一致性级别,别急,下面就开始说

consul 通过http 接口提供服务,就在http的api里可以指定,客户端sdk就不说了,有很多版本,这里只说consul agent端,因为线上一般都是直接请求localhost:8500 访问本地的consul agent的。下面是所有consul agent http接口都要执行的一个逻辑parseConsistency,就是解析一致性

func (s *HTTPServer) parseConsistency(resp http.ResponseWriter, req *http.Request, b structs.QueryOptionsCompat) bool {
    query := req.URL.Query()
    //这里默认就认为是default模式。
    defaults := true
    //解析http请求如果带了stale参数,则是允许读过期的数据,那就server不用转发给leader
    if _, ok := query["stale"]; ok {
        b.SetAllowStale(true)
        defaults = false
    }
    //解析http请求如果带了consistent参数,代表要读最新的数据。
    if _, ok := query["consistent"]; ok {
        b.SetRequireConsistent(true)
        defaults = false
    }
    //解析http请求如果带了consistent参数,代表要从leader读。
    if _, ok := query["leader"]; ok {
        defaults = false
    }
    //解析http请求如果带了cached参数,代表可以从agent读,不需要请求server
    if _, ok := query["cached"]; ok {
        b.SetUseCache(true)
        defaults = false
    }
    if maxStale := query.Get("max_stale"); maxStale != "" {
        dur, err := time.ParseDuration(maxStale)
        if err != nil {
            resp.WriteHeader(http.StatusBadRequest)
            fmt.Fprintf(resp, "Invalid max_stale value %q", maxStale)
            return true
        }
        b.SetMaxStaleDuration(dur)
        if dur.Nanoseconds() > 0 {
            b.SetAllowStale(true)
            defaults = false
        }
    }
...

上面解析了客户端的读模式,下面看怎么用的,随便看一个consul读的代码,比如查看健康的service node 的一段代码:

//如果可以用cache的数据,则直接从当前agent响应。
    if args.QueryOptions.UseCache {
        raw, m, err := s.agent.cache.Get(cachetype.HealthServicesName, &args)
        if err != nil {
            return nil, err
        }
        defer setCacheMeta(resp, &m)
        reply, ok := raw.(*structs.IndexedCheckServiceNodes)
        if !ok {
            // This should never happen, but we want to protect against panics
            return nil, fmt.Errorf("internal error: response type not correct")
        }
        out = *reply
    } else {
    //否则需要通过rpc请求server节点。  
    RETRY_ONCE:
        if err := s.agent.RPC("Health.ServiceNodes", &args, &out); err != nil {
            return nil, err
        }
        if args.QueryOptions.AllowStale && args.MaxStaleDuration > 0 && args.MaxStaleDuration < out.LastContact {
            args.AllowStale = false
            args.MaxStaleDuration = 0
            goto RETRY_ONCE
        }
    }

我们只有指定了cache参数,consul 才会从agent 本地直接响应数据,这里也可以看出,agent 是会缓存数据的,否则就需要请求server节点,这个时候问题又来了,server节点一般我们是一个集群,最少3个节点,那请求那一个呢,有负载均衡吗,带着这个问题,我们看下代码,怎么选server的, 代码如下:

// FindServer takes out an internal "read lock" and searches through the list
// of servers to find a "healthy" server.  If the server is actually
// unhealthy, we rely on Serf to detect this and remove the node from the
// server list.  If the server at the front of the list has failed or fails
// during an RPC call, it is rotated to the end of the list.  If there are no
// servers available, return nil.
func (m *Manager) FindServer() *metadata.Server {
    l := m.getServerList()
    numServers := len(l.servers)
    if numServers == 0 {
        m.logger.Warn("No servers available")
        return nil
    }

    // Return whatever is at the front of the list because it is
    // assumed to be the oldest in the server list (unless -
    // hypothetically - the server list was rotated right after a
    // server was added).
    return l.servers[0]
}

consul 这里是不是处理的很简单,每次都是取第一个,人家注释也说了,如果这个出现失败了,会移到最后。

Consul Server的逻辑

consul agent 发现不用本地cache的数据,那就要rpc请求server节点,server节点接受到任何请求,都会执行forward方法,来检查是否要转发请求还是就自己响应数据。

func (s *Server) forward(method string, info structs.RPCInfo, args interface{}, reply interface{}) (bool, error) {
    var firstCheck time.Time

    // Handle DC forwarding
    // 检查dc是否一致,不一致就要转发到正确的dc
    dc := info.RequestDatacenter()
    if dc != s.config.Datacenter {
        // Local tokens only work within the current datacenter. Check to see
        // if we are attempting to forward one to a remote datacenter and strip
        // it, falling back on the anonymous token on the other end.
        if token := info.TokenSecret(); token != "" {
            done, ident, err := s.ResolveIdentityFromToken(token)
            if done {
                if err != nil && !acl.IsErrNotFound(err) {
                    return false, err
                }
                if ident != nil && ident.IsLocal() {
                    // Strip it from the request.
                    info.SetTokenSecret("")
                    defer info.SetTokenSecret(token)
                }
            }
        }

        err := s.forwardDC(method, dc, args, reply)
        return true, err
    }

    // Check if we can allow a stale read, ensure our local DB is initialized
    // 这里server开始检查读一致性,如果允许读过期的数据,则直接用当前server的数据。
    // 不需要后面的检查是否为leader了。
    if info.IsRead() && info.AllowStaleRead() && !s.raft.LastContact().IsZero() {
        return false, nil
    }

CHECK_LEADER:
    // Fail fast if we are in the process of leaving
    select {
    case <-s.leaveCh:
        return true, structs.ErrNoLeader
    default:
    }

    // Find the leader
    // 到这里就是要default读或者consistent读,都需要从leader读数据。
    isLeader, leader := s.getLeader()

    // Handle the case we are the leader
    // 如果当前是leader,则不需要再转发到leader了。
    if isLeader {
        return false, nil
    }

    // Handle the case of a known leader
    // 不是leader,则需要再转发到leader节点,多一次网络请求。
    rpcErr := structs.ErrNoLeader
    if leader != nil {
        rpcErr = s.connPool.RPC(s.config.Datacenter, leader.ShortName, leader.Addr,
            leader.Version, method, leader.UseTLS, args, reply)
        if rpcErr != nil && canRetry(info, rpcErr) {
            goto RETRY
        }
        return true, rpcErr
    }

有同学看到这里,不是还有consistent 模式没有讲吗,这个就不在分析了, 不然文章太长了,没有人看

总结

一写就这么多,总算把consul的一致性读的特性,怎么用的,和背后的原理给说明了,我们默认情况都是default模式,即请求都是需要通过访问agent,agent再请求server,如果server不是leader,还要转发到leader节点。要1次http,2次rpc才能获取到数据,所以如果有consul server压力大的,可以通过cache来缓解server特别是leader的压力。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容

  • 最近一直在做consul的压测,在开启acl的模式下,模拟consul client和springcloud co...
    老虎1029阅读 855评论 0 0
  • 一致性分析 Raft算法解决的不仅仅是分布式集群环境下的一致性问题,还有一定限度内的容错性(半数节点工作正常则服务...
    黄靠谱阅读 983评论 0 0
  • 一致性的原因 数据存在多台机器上 数据在不同机器上需要保证相同 分类 强一致性保证提交改变后,马上改变集群的状态P...
    谭英智阅读 369评论 0 0
  • Consul简介 注册中心(不仅仅是注册中心) 一致性协议采用 Raft 算法,用来保证服务的高可用和一致性,实际...
    黄靠谱阅读 3,929评论 0 8
  • 本文主要对Consul中的几个概念进行了归纳和梳理,为方便后续使用。 1. RPC(远程过程调用) RPC是「服务...
    PlayerI阅读 1,808评论 0 1