概述

etcd提供了线性一致性。在线性一致性的根底上。etcd提供了

  • Campaign 进入期待队列,当后面所有其它候选人的value都delete后返回。
  • Proclaim 不失落leadership的状况下,从新申明一个新的值。
  • Resign 放弃leadership
  • Leader 取得以后的的leader value
  • Observe 开始watch leader value 的变更

这篇blog探讨了etcd选举机制的实现细节,以及应该如何利用etcd选举来防止脑裂。
如果仅仅是须要晓得本人是否主节点,那么只须要Campaign指令,当Campaign返回时,本人便成为主节点。
在一个分布式系统中,是没有同时这个概念的,假如Campaign指令的返回tcp包因丢包多次重试,晚了1分钟才达到Campaigner,那么Campaign返回的同时,这个后果就曾经生效。
所以,要害是:心跳距离肯定要短于value的ttl,且心跳失败的工夫不能长于ttl,心跳失败数次时就应从新加入选举。这个时候,其它节点因value的ttl没过不能成为leader,而leader会因心跳失败放弃成为leader,从而躲避Campaign指令滞后问题,或其它起因导致的,leader campaigner的value过期后,该campaigner还认为本人是leader的问题,即避免出现脑裂。

Campaign

代码

见 https://github.com/etcd-io/et... 8ee1dd9e23bce4d9770816edf5816b13767ac51d

流程简述

  1. put value to prefix/lease_id (排队)
  2. waitDeletes (期待prefix下所有早于本人的value,即本人排到第一)

    Campaign 代码含正文

    type Election struct { session *Session keyPrefix string leaderKey     string leaderRev     int64 leaderSession *Session hdr           *pb.ResponseHeader}

    election.go: 59

    // Campaign puts a value as eligible for the election on the prefix// key.// Multiple sessions can participate in the election for the// same prefix, but only one can be the leader at a time.//// If the context is 'context.TODO()/context.Background()', the Campaign// will continue to be blocked for other keys to be deleted, unless server// returns a non-recoverable error (e.g. ErrCompacted).// Otherwise, until the context is not cancelled or timed-out, Campaign will// continue to be blocked until it becomes the leader.func (e *Election) Campaign(ctx context.Context, val string) error { s := e.session client := e.session.Client() k := fmt.Sprintf("%s%x", e.keyPrefix, s.Lease()) // put 一个 key value,一般而言,不会有抵触。 // 如果同一个session 反复put,导致key的 revision 不为0,txn才会失败。 txn := client.Txn(ctx).If(v3.Compare(v3.CreateRevision(k), "=", 0)) txn = txn.Then(v3.OpPut(k, val, v3.WithLease(s.Lease()))) txn = txn.Else(v3.OpGet(k)) resp, err := txn.Commit() if err != nil {     return err } e.leaderKey, e.leaderRev, e.leaderSession = k, resp.Header.Revision, s // 如果 put 时发现 key 的revision 不为 0 if !resp.Succeeded {     kv := resp.Responses[0].GetResponseRange().Kvs[0]     // 更新 leaderRev     e.leaderRev = kv.CreateRevision     if string(kv.Value) != val {         // value 不相等,更新value         if err = e.Proclaim(ctx, val); err != nil {             // 失败则通过删除本人的key (辞职)             // 从新开始选举, 返回谬误             // 如果从新开始选举谬误?有心跳超时。             e.Resign(ctx)             return err         }     } } _, err = waitDeletes(ctx, client, e.keyPrefix, e.leaderRev-1) if err != nil {     // clean up in case of context cancel     select {     case <-ctx.Done():         // 产生谬误,删除本人的key,防止被误认为leader.         e.Resign(client.Ctx())     default:         e.leaderSession = nil     }     return err } // 成为leader e.hdr = resp.Header return nil}

    一个典型的选主过程

    @startumlautonumberparticipant "actor1" as actor1participant "actor2" as actor2participant "etcd" as etcdactivate actor1activate actor2activate etcdactor1 -> etcd: put "prefix/lease_id_a" value1actor2 -> etcd: put "prefix/lease_id_b" value2etcd -> actor2: key_revision:1 total_revision: 10000etcd -> actor1: key_revision:1 total_revision: 10002actor1 -> etcd: wait for "prefix" delete with revision 10001actor2 -> actor2: i'm leadernote right: 以后队列的状态是[actor2_lease_id_b, actor1_lease_id_a], actor2 排在最后面,所以返回actor2,actor2成为leader,actor1须要期待deactivate actor2actor2 -> etcd: put "prefix/lease_id_c" value2etcd -> actor1: 10000 deleteactor1 -> actor1: i'm leadernote right: 以后队列的状态是[actor1_lease_id_a, actor2_lease_id_c], actor1 排在最后面,所以返回actor1,actor1成为leader, actor2须要期待etcd -> actor2: key_revision:1 total_revision: 10003actor2 -> etcd: wait for "prefix" delete with revision 10002@enduml

    选举封装

    形象

    能够创立campaigner,即选举人去参选,选举人本人负责参选,维持心跳。后面剖析过,零碎里相对不会同时存在两个节点认为本人是leader。选举过程被封装,用户只须要实现上面的事件:

  3. become_leader 这个时候能够开启leader服务。
  4. leader_change 这个时候要更新leader node。
  5. get_out_of_leadership 这个时候要敞开leader服务。(不论是ETCD分区,还是和ETCD网络中断,参选人process因为程序BUG退出,都意味着本人不是leader了,这个时候要敞开leader服务)