etcd campaign

2024-04-20 14:44
文章标签 etcd campaign

本文主要是介绍etcd campaign,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1. 引言

本文主要讲解使用etcd进行选举的流程,以及对应的缺陷和使用场景

2. etcd选举流程

流程如以代码所示,流程为:

  • clientv3.New

    创建client与etcd server建立连接

  • concurrency.NewSession

    创建选举的session,一般会配置session的TTL(内部会创建一个lease并进行保活)

  • concurrency.NewElection

    创建选举,并指定prefix key

    func NewElection(s *Session, pfx string) *Election {return &Election{session: s, keyPrefix: pfx + "/"}
    }
    
  • e.Campaign

    开始选举,并配置选举key的val,一般配置节点名

代码:

	cli, err := clientv3.New(clientv3.Config{Endpoints:            []string{"172.20.20.55:2379"},DialTimeout:          5 * time.Second,DialKeepAliveTime:    3 * time.Second,DialKeepAliveTimeout: 3 * time.Second,})if err != nil {log.Fatal(err)}defer cli.Close()for {s, err := concurrency.NewSession(cli, concurrency.WithTTL(2))if err != nil {log.Fatal(err)}defer s.Close()e := concurrency.NewElection(s, "/test/election")log.Println("Start campaign", e.Key())if err := e.Campaign(cli.Ctx(), etcdServerIpAndPort); err != nil {log.Fatal(err)}// TODO: send a message indicating that the current node has become the leaderlog.Println("Campaign success, become leader")// determine whether the campaign session is doneselect {case <-s.Done():log.Println("Campaign session done")}}

2.1. 创建Session流程

concurrency.NewSession里的具体实现,参考以下源码,流程:

  • 根据参数使用传入的lease,或根据TTL创建lease

    	ops := &sessionOptions{ttl: defaultSessionTTL, ctx: client.Ctx()}for _, opt := range opts {opt(ops)}id := ops.leaseIDif id == v3.NoLease {resp, err := client.Grant(ops.ctx, int64(ops.ttl))if err != nil {return nil, err}id = v3.LeaseID(resp.ID)}
    
  • client.KeepAlive

    对创建的lease进行保活(lease过期,也意味着session失效)

    	ctx, cancel := context.WithCancel(ops.ctx)keepAlive, err := client.KeepAlive(ctx, id)if err != nil || keepAlive == nil {cancel()return nil, err}
    

    client.KeepAlive会返回一个keepAlive channel,如果保活失败,lease过期,此channel会关闭,从而通知调用方Session已失效(如果当前节点为lease,意味着leader失效),参考代码:

    	donec := make(chan struct{})s := &Session{client: client, opts: ops, id: id, cancel: cancel, donec: donec}// keep the lease alive until client error or cancelled contextgo func() {defer close(donec)for range keepAlive {// eat messages until keep alive channel closes}}()
    

完整代码:

func NewSession(client *v3.Client, opts ...SessionOption) (*Session, error) {ops := &sessionOptions{ttl: defaultSessionTTL, ctx: client.Ctx()}for _, opt := range opts {opt(ops)}id := ops.leaseIDif id == v3.NoLease {resp, err := client.Grant(ops.ctx, int64(ops.ttl))if err != nil {return nil, err}id = v3.LeaseID(resp.ID)}ctx, cancel := context.WithCancel(ops.ctx)keepAlive, err := client.KeepAlive(ctx, id)if err != nil || keepAlive == nil {cancel()return nil, err}donec := make(chan struct{})s := &Session{client: client, opts: ops, id: id, cancel: cancel, donec: donec}// keep the lease alive until client error or cancelled contextgo func() {defer close(donec)for range keepAlive {// eat messages until keep alive channel closes}}()return s, nil
}
2.1.1. 保活流程

client.KeepAlive内部流程:

  • 判断id是否在保活的队列中,参考上一部分,创建session是可以传入一个已存在的lease

    • 不存在则创建并加入到l.keepAlives保活队列中
    • 存在则将当前创建的channel和ctx加入到keepAlive结构体中
    	ka, ok := l.keepAlives[id]if !ok {// create fresh keep aliveka = &keepAlive{chs:           []chan<- *LeaseKeepAliveResponse{ch},ctxs:          []context.Context{ctx},deadline:      time.Now().Add(l.firstKeepAliveTimeout),nextKeepAlive: time.Now(),donec:         make(chan struct{}),}l.keepAlives[id] = ka} else {// add channel and context to existing keep aliveka.ctxs = append(ka.ctxs, ctx)ka.chs = append(ka.chs, ch)}
    

    keepAlive结构体参数描述:

    • chs:当前lease关联的ch列表,若保活失败,则都会关闭,以此通知调用KeepAlive处,进行相应的逻辑处理,如需要处理Session失效。

    • ctxs:保存调用KeepAlive时传入的ctx,若ctx失效,意味着调用方不再需要进行lease保活

    • deadline:当前lease的失效时间,默认值为l.firstKeepAliveTimeout,此值默认为client.cfg.DialTimeout+time.Second,初始化代码如下:

      func NewLease(c *Client) Lease {return NewLeaseFromLeaseClient(RetryLeaseClient(c), c, c.cfg.DialTimeout+time.Second)
      }func NewLeaseFromLeaseClient(remote pb.LeaseClient, c *Client, keepAliveTimeout time.Duration) Lease {l := &lessor{donec:                 make(chan struct{}),keepAlives:            make(map[LeaseID]*keepAlive),remote:                remote,firstKeepAliveTimeout: keepAliveTimeout,}if l.firstKeepAliveTimeout == time.Second {l.firstKeepAliveTimeout = defaultTTL}if c != nil {l.callOpts = c.callOpts}reqLeaderCtx := WithRequireLeader(context.Background())l.stopCtx, l.stopCancel = context.WithCancel(reqLeaderCtx)return l
      }
      
    • donec:lease失效后,用于通知清理l.keepAlives中对应的数据

  • 开启协程清理ctx

    仅清理ctx对应keepAlive中的ch和ctx

    go l.keepAliveCtxCloser(id, ctx, ka.donec)
    
    func (l *lessor) keepAliveCtxCloser(id LeaseID, ctx context.Context, donec <-chan struct{}) {select {case <-donec:returncase <-l.donec:returncase <-ctx.Done():}l.mu.Lock()defer l.mu.Unlock()ka, ok := l.keepAlives[id]if !ok {return}// close channel and remove context if still associated with keep alivefor i, c := range ka.ctxs {if c == ctx {close(ka.chs[i])ka.ctxs = append(ka.ctxs[:i], ka.ctxs[i+1:]...)ka.chs = append(ka.chs[:i], ka.chs[i+1:]...)break}}// remove if no one more listenersif len(ka.chs) == 0 {delete(l.keepAlives, id)}
    }
    
  • 开启协程发送保活信息,以及确认lease是否过期

    firstKeepAliveOnce为sync.Once类型,多次调用仅会执行一次

    	l.firstKeepAliveOnce.Do(func() {go l.recvKeepAliveLoop()go l.deadlineLoop()})
    
    • 发送以及接收保活信息

      func (l *lessor) recvKeepAliveLoop() (gerr error) {defer func() {l.mu.Lock()close(l.donec)l.loopErr = gerrfor _, ka := range l.keepAlives {ka.close()}l.keepAlives = make(map[LeaseID]*keepAlive)l.mu.Unlock()}()for {stream, err := l.resetRecv()if err != nil {if canceledByCaller(l.stopCtx, err) {return err}} else {for {resp, err := stream.Recv()if err != nil {if canceledByCaller(l.stopCtx, err) {return err}if toErr(l.stopCtx, err) == rpctypes.ErrNoLeader {l.closeRequireLeader()}break}l.recvKeepAlive(resp)}}log.Println("resetRecv")select {case <-time.After(retryConnWait):continuecase <-l.stopCtx.Done():return l.stopCtx.Err()}}
      }
      • 发送

        resetRecv函数中获取一个grpc的stream,并通过此发送保活信息

        // resetRecv opens a new lease stream and starts sending keep alive requests.
        func (l *lessor) resetRecv() (pb.Lease_LeaseKeepAliveClient, error) {sctx, cancel := context.WithCancel(l.stopCtx)stream, err := l.remote.LeaseKeepAlive(sctx, l.callOpts...)if err != nil {cancel()return nil, err}l.mu.Lock()defer l.mu.Unlock()if l.stream != nil && l.streamCancel != nil {l.streamCancel()}l.streamCancel = cancell.stream = streamgo l.sendKeepAliveLoop(stream)return stream, nil
        }
        

        通过sendKeepAliveLoop函数进行保活信息的发送,关键逻辑:

        1. 遍历l.keepAlives,通过每个keepAlive结构体中的nextKeepAlive来判断是否要发送保活信息(nextKeepAlive数据参考之前讲的初始化和接收保活回复处)
        2. 每隔0.5秒运行一次,出现错误时直接退出执行
        // sendKeepAliveLoop sends keep alive requests for the lifetime of the given stream.
        func (l *lessor) sendKeepAliveLoop(stream pb.Lease_LeaseKeepAliveClient) {for {var tosend []LeaseIDnow := time.Now()l.mu.Lock()for id, ka := range l.keepAlives {if ka.nextKeepAlive.Before(now) {tosend = append(tosend, id)}}l.mu.Unlock()for _, id := range tosend {r := &pb.LeaseKeepAliveRequest{ID: int64(id)}if err := stream.Send(r); err != nil {// TODO do something with this error?return}}select {case <-time.After(500 * time.Millisecond):case <-stream.Context().Done():log.Println("stream context done")returncase <-l.donec:returncase <-l.stopCtx.Done():return}}
        }
        
      • 接收信息

        接收lease保活信息,并进行处理,主要更新nextKeepAlive(下一次发送时间)和deadline

        关键逻辑:

        1. nextKeepAlive为time.Now().Add((time.Duration(karesp.TTL) * time.Second) / 3.0),其中TTL为NewSession时传入的TTL。
        2. 如果回复中TTL为0,表明lease过期

        处理函数如下:

        // recvKeepAlive updates a lease based on its LeaseKeepAliveResponse
        func (l *lessor) recvKeepAlive(resp *pb.LeaseKeepAliveResponse) {karesp := &LeaseKeepAliveResponse{ResponseHeader: resp.GetHeader(),ID:             LeaseID(resp.ID),TTL:            resp.TTL,}l.mu.Lock()defer l.mu.Unlock()ka, ok := l.keepAlives[karesp.ID]if !ok {return}if karesp.TTL <= 0 {// lease expired; close all keep alive channelsdelete(l.keepAlives, karesp.ID)ka.close()return}// send update to all channelsnextKeepAlive := time.Now().Add((time.Duration(karesp.TTL) * time.Second) / 3.0)ka.deadline = time.Now().Add(time.Duration(karesp.TTL) * time.Second)for _, ch := range ka.chs {select {case ch <- karesp:default:}// still advance in order to rate-limit keep-alive sendska.nextKeepAlive = nextKeepAlive}
        }
        
    • 判断lease是否过期

      主要通过deadline进行判断,是否会实时更新。

      func (l *lessor) deadlineLoop() {for {select {case <-time.After(time.Second):case <-l.donec:return}now := time.Now()l.mu.Lock()for id, ka := range l.keepAlives {if ka.deadline.Before(now) {// waited too long for response; lease may be expiredka.close()delete(l.keepAlives, id)}}l.mu.Unlock()}
      }
      

KeepAlive完整代码:

func (l *lessor) KeepAlive(ctx context.Context, id LeaseID) (<-chan *LeaseKeepAliveResponse, error) {ch := make(chan *LeaseKeepAliveResponse, LeaseResponseChSize)l.mu.Lock()// ensure that recvKeepAliveLoop is still runningselect {case <-l.donec:err := l.loopErrl.mu.Unlock()close(ch)return ch, ErrKeepAliveHalted{Reason: err}default:}ka, ok := l.keepAlives[id]if !ok {// create fresh keep aliveka = &keepAlive{chs:           []chan<- *LeaseKeepAliveResponse{ch},ctxs:          []context.Context{ctx},deadline:      time.Now().Add(l.firstKeepAliveTimeout),nextKeepAlive: time.Now(),donec:         make(chan struct{}),}l.keepAlives[id] = ka} else {// add channel and context to existing keep aliveka.ctxs = append(ka.ctxs, ctx)ka.chs = append(ka.chs, ch)}l.mu.Unlock()go l.keepAliveCtxCloser(id, ctx, ka.donec)l.firstKeepAliveOnce.Do(func() {go l.recvKeepAliveLoop()go l.deadlineLoop()})return ch, nil
}

keepAlive.close()函数:

关闭所有调用KeepAlive函数返回的channel,此处为通知对应的Session

func (ka *keepAlive) close() {close(ka.donec)for _, ch := range ka.chs {close(ch)}
}
2.1.2. 保活流程总结
  1. 保活消息发送的间隔为创建Session时传入的TTL或者lease的TTL除以3,如TTL为3,则每隔1s发送一次;但是如果TTL为2,并不是每0.667s发送一次,因为执行保活的函数是固定每0.5s执行一次。所以间隔只能是0.5的整数倍,即如果TTL为2,则为1s发送一次保活信息。
    在这里插入图片描述

  2. lease过期也就意味着Session失效

2.2 选举流程

在这里插入图片描述

流程:

  1. 创建一个选举对象

    func NewElection(s *Session, pfx string) *Election {return &Election{session: s, keyPrefix: pfx + "/"}
    }
    
  2. 进行选举

主要介绍选举的步骤和逻辑:

  • 根据keyPrefix(NewElection时传入的)和lease id,组成代表当前节点的key

    k := fmt.Sprintf("%s%x", e.keyPrefix, s.Lease())
    
  • 通过事务判断key是否存在

    • 存在则获取值
      • 如果val与获取值不同,更新val,参考e.Proclaim
    • 不存在则插入key和val数据,并绑定对应的Session lease,如果lease过期后,对应的key和val也会被删除
    	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, sif !resp.Succeeded {kv := resp.Responses[0].GetResponseRange().Kvs[0]e.leaderRev = kv.CreateRevisionif string(kv.Value) != val {if err = e.Proclaim(ctx, val); err != nil {e.Resign(ctx)return err}}}
    

    e.Proclaim代码:

    func (e *Election) Proclaim(ctx context.Context, val string) error {if e.leaderSession == nil {return ErrElectionNotLeader}client := e.session.Client()cmp := v3.Compare(v3.CreateRevision(e.leaderKey), "=", e.leaderRev)txn := client.Txn(ctx).If(cmp)txn = txn.Then(v3.OpPut(e.leaderKey, val, v3.WithLease(e.leaderSession.Lease())))tresp, terr := txn.Commit()if terr != nil {return terr}if !tresp.Succeeded {e.leaderKey = ""return ErrElectionNotLeader}e.hdr = tresp.Headerreturn nil
    }
    

    如果e.Proclaim更新值失败则删除key,然后Campaign返回错误,下次调用Campaign时继续执行

    e.Resign功能为删除相应的选举key,代码:

    func (e *Election) Resign(ctx context.Context) (err error) {if e.leaderSession == nil {return nil}client := e.session.Client()cmp := v3.Compare(v3.CreateRevision(e.leaderKey), "=", e.leaderRev)resp, err := client.Txn(ctx).If(cmp).Then(v3.OpDelete(e.leaderKey)).Commit()if err == nil {e.hdr = resp.Header}e.leaderKey = ""e.leaderSession = nilreturn err
    }
    
  • 根据e.keyPrefix和e.leaderRev(上一步骤中key存入etcd server时的Revision),等待在此Revision之前创建的,具有相同prefix的key被删除

    	_, err = waitDeletes(ctx, client, e.keyPrefix, e.leaderRev-1)if err != nil {// clean up in case of context cancelselect {case <-ctx.Done():e.Resign(client.Ctx())default:e.leaderSession = nil}return err}
    

    waitDeletes逻辑:

    • 通过client.Get()获取指定前缀、指定最大创建Revision的最后一条key。即与当前选举key含有相同的prefix的,上一条数据,也可以理解为获取比当前节点先插入选举key、val的其它节点的key和val
      • 获取到数据,表明其它节点先创建了key,需要等待其过期,通过waitDelete watch keyPrefix的每个删除操作;watch到相应的删除事件,则重新调用client.Get(),判断是否需要继续等待
      • 没有获取到,表明没有其它节点先创建了key,自身可以成为leader,直接返回

    waitDeletes代码:

    func waitDeletes(ctx context.Context, client *v3.Client, pfx string, maxCreateRev int64) (*pb.ResponseHeader, error) {getOpts := append(v3.WithLastCreate(), v3.WithMaxCreateRev(maxCreateRev))for {resp, err := client.Get(ctx, pfx, getOpts...)if err != nil {return nil, err}if len(resp.Kvs) == 0 {return resp.Header, nil}lastKey := string(resp.Kvs[0].Key)if err = waitDelete(ctx, client, lastKey, resp.Header.Revision); err != nil {return nil, err}}
    }
    

    waitDelete代码:

    func waitDelete(ctx context.Context, client *v3.Client, key string, rev int64) error {cctx, cancel := context.WithCancel(ctx)defer cancel()var wr v3.WatchResponsewch := client.Watch(cctx, key, v3.WithRev(rev))for wr = range wch {for _, ev := range wr.Events {if ev.Type == mvccpb.DELETE {return nil}}}if err := wr.Err(); err != nil {return err}if err := ctx.Err(); err != nil {return err}return fmt.Errorf("lost watcher waiting for delete")
    }
    

Campaign完整代码:

func (e *Election) Campaign(ctx context.Context, val string) error {s := e.sessionclient := e.session.Client()k := fmt.Sprintf("%s%x", e.keyPrefix, s.Lease())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, sif !resp.Succeeded {kv := resp.Responses[0].GetResponseRange().Kvs[0]e.leaderRev = kv.CreateRevisionif string(kv.Value) != val {if err = e.Proclaim(ctx, val); err != nil {e.Resign(ctx)return err}}}_, err = waitDeletes(ctx, client, e.keyPrefix, e.leaderRev-1)if err != nil {// clean up in case of context cancelselect {case <-ctx.Done():e.Resign(client.Ctx())default:e.leaderSession = nil}return err}e.hdr = resp.Headerreturn nil
}
2.3.1. 选举流程总结
  1. 选举本质上为先到先得,是一个FIFO的队列,后来的需要等待前边的释放,而前边的释放时间则取决于设置的Session TTL,在lease过期,由etcd server删除对应的key后,下一个才可成为leader

3. 缺陷和使用场景

由上一章节描述的,当前节点要成为leder,需要等etcd server删除比当前节点先写入的其它节点的key和val。

如此意味着如果上一个节点故障后,需要等待上一个节点的Session TTL时间,下一个节点才会变为leader。而在此期间,如果etcd server发生故障,则这个时间还会延长。

3.1. etcd lease TTL测试

测试1:

测试流程:设置一个300s后超时的lease,关闭节点(etcd停止运行,etcd为单节点),300s后重启,发现该lease没有过期

结论:etcd停止服务后,lease的TTL会重置,且lease不会过期

在这里插入图片描述

测试2:

测试步骤:生成一个300s的lease,20s之后,kill掉etcd的leader,使etcd切主,然后查询该lease的剩余时间,结果为295s

结论:etcd切主后会重置lease的TTL

在这里插入图片描述

3.2 缺陷总结

通过上一部分中的测试,可以发现当etcd发生切主或重启(单节点)后,TTL会重置,也就是说当使用etcd进行选举的客户端发生故障后,在切主的过程中,etcd server也发生故障,则此时间会延长,因为故障节点的lease TTL被重置了,需要重新计算过期时间,这会导致切主时间延长。

使用场景:对切主的时间没有严苛的要求

3.3 使用的注意事项

根据前边的内容介绍,在选举的过程中,如果Session lease超时,Campaign处是感觉不到的,所以当Campaign返回后,需要额外判断Session是否Done了:

	for {s, err := concurrency.NewSession(cli, concurrency.WithTTL(2))if err != nil {log.Fatal(err)}defer s.Close()e := concurrency.NewElection(s, "/test/election")log.Println("Start campaign", e.Key())if err := e.Campaign(cli.Ctx(), etcdServerIpAndPort); err != nil {log.Fatal(err)}select {case <-s.Done():log.Println("Campaign session done")continue}// TODO: send a message indicating that the current node has become the leaderlog.Println("Campaign success, become leader")// determine whether the campaign session is doneselect {case <-s.Done():log.Println("Campaign session done")}}

中,如果Session lease超时,Campaign处是感觉不到的,所以当Campaign返回后,需要额外判断Session是否Done了:

	for {s, err := concurrency.NewSession(cli, concurrency.WithTTL(2))if err != nil {log.Fatal(err)}defer s.Close()e := concurrency.NewElection(s, "/test/election")log.Println("Start campaign", e.Key())if err := e.Campaign(cli.Ctx(), etcdServerIpAndPort); err != nil {log.Fatal(err)}select {case <-s.Done():log.Println("Campaign session done")continue}// TODO: send a message indicating that the current node has become the leaderlog.Println("Campaign success, become leader")// determine whether the campaign session is doneselect {case <-s.Done():log.Println("Campaign session done")}}

这篇关于etcd campaign的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/920538

相关文章

Golang使用etcd构建分布式锁的示例分享

《Golang使用etcd构建分布式锁的示例分享》在本教程中,我们将学习如何使用Go和etcd构建分布式锁系统,分布式锁系统对于管理对分布式系统中共享资源的并发访问至关重要,它有助于维护一致性,防止竞... 目录引言环境准备新建Go项目实现加锁和解锁功能测试分布式锁重构实现失败重试总结引言我们将使用Go作

etcd-io/gofail 介绍与源代码分析(一)

gofail FreeBSD failpoints 的 Golang 版本实现 通过代码或者环境变量,其允许程序在特定的地方动态地注入错误或者其他行为 Github 地址: https://github.com/etcd-io/gofail gofail 工作原理 在具体的代码点加入// gofail:特殊格式的注释(插桩)使用 gofail 工具,把这些桩动态替换为其他代码从而编译出具

etcd配置详解

参数名称含义–name ‘default’本member的名字–data-dir指定节点的数据存储目录–listen-peer-urls本member侧使用,用于监听其他member发送信息的地址–listen-client-urls本member侧使用,用于监听etcd客户发送信息的地址–wal-dir专用wal目录的路径–snapshot-count要将快照触发到磁盘的已提交事务数–heart

centos7 etcd 3.4.10安装

下载地址:https://github.com/etcd-io/etcd/releases tar -zxvf etcd-v3.4.10-linux-amd64.tar.gz -C /usr/local/etcd #文件名换成自己的 cp /usr/local/etcd/etcd /usr/bin/ cp /usr/local/etcd/etcdctl /usr/bin/ 添加配置文件个工作目

关于 etcd lease,以下说法正确的是?

关于 etcd lease,以下说法正确的是? A. etcd 创建 lease 对象时,需要指定一个时间作为其超时时间。 B. lease 对象被创建后,超过设定的时间一定会被系统自动回收。 C. 将 key 关联到 lease 对象上,当 lease 对象超时后,key 会被系统自动回收。 D. etcd 支持将多个 key 关联到同一个 lease 对象上,从而大幅降低刷新 lease 的性

容器网络(桥接、host、none)及跨主机网络(etcd、flannel、docker)

1.本地网络 1.bridge         所有容器连接到桥就可以使用外网,使用nat让容器可以访问外网,使用ip a s指令查看桥,所有容器连接到此桥,ip地址都是 172.17.0.0/16网段,桥是启动docker服务后出现,在centos使用bridge-utils安装。 下载bridge-utils [root@docker0 ~]# yum -y install bridg

快速学习安装使用etcd

1. 什么是 etcd? etcd 是一个分布式键值存储系统,主要用于分布式系统的配置管理和服务发现。它提供了可靠的数据存储,etcd 可以用来构建高可用的分布式键值数据库,根据官网介绍并且支持分布式锁、Leader 选举等功能,通常被用作微服务架构中的注册中心。在目前go的大部分组件还有框架中都被采用为注册中心组件 相当于zookper和redis 2. 安装 etcd 2.1. 在本地安

Kubernetes 外部 etcd 集群的快速 Docker Compose 部署指南

一、背景 在高可用 Kubernetes 部署中,需要单独部署外部 etcd 集群,而不是使用 kubeadm 默认在 master 节点上部署的 etcd。以下是关于这一配置场景的详细记录。 二、etcd简介 etcd 是一个高可用的分布式键值存储系统,主要用于存储和管理配置信息、服务发现信息以及其他重要的元数据。etcd 是由 CoreOS 开发的,基于 Raft 共识算法来保证数据的一

[etcd]raft总结/选举/数据同步,协议缺陷与解决/Multi Raft

raft协议是multi paxos协议的实现.Etcd、Consu都使用了raft 1.角色 raft协议中包含这几种角色 领导者:带头大哥1.提出提议,但是不需要确认,因为我是大哥;2.复制日志,数据以大哥为准,3,领导者会定时发送心跳,确定自己的位置.告诉小弟老实呆着,一旦心跳超时,小弟就会重新选举大哥. 跟随者:只要大哥发送心跳,我就老实的同步日志.一旦没有心跳,我就变成候选人,开

Kubernetes中如何对etcd进行备份和还原

​ ​ 您好,我是程序员小羊! 前言 在Kubernetes集群中,etcd是核心的分布式键值存储系统,负责存储集群的所有配置数据和状态信息。为了确保Kubernetes集群的稳定性和可恢复性,定期备份和正确的恢复etcd数据是至关重要的。本指南将详细介绍如何在Kubernetes中对etcd进行备份和还原,包括使用etcdctl工具的具体步骤和一些注意事项。 一、etcd备份的重要