Loading [MathJax]/jax/output/CommonHTML/config.js
部署DeepSeek模型,进群交流最in玩法!
立即加群
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >一种基于etcd实践节点自动故障转移的思路

一种基于etcd实践节点自动故障转移的思路

作者头像
有态度的马甲
发布于 2025-04-07 06:40:38
发布于 2025-04-07 06:40:38
7402
代码可运行
举报
文章被收录于专栏:精益码农精益码农
运行总次数:2
代码可运行

自动故障转移是服务高可用的一种实现方式。mongodb,redis哨兵集群、 etcd都具备某种程度的故障转移能力。

今天记录利用etcd选举sdk实践 服务自动故障转移

服务以leader、follower多节点启动,日常leader接受所有业务流量,follower作为备用实例,不接受业务流量;

监测到leader宕机,follower节点自动提升为leader并接管业务流量。

1. 节点故障转移

既然是故障转移, 故所有节点的状态会发生变化, 这是一个状态机模型。

1>.  各节点向etcd注册节点标记, 并各自维持稳定的心跳保活;

2>.  参与选举

2>.  算法选出leader

4>.  迅速感知选举结果和换届

etcd作为基于raft强一致性协议实现的分布式存储, CP模型,天生对外输出协调和共识能力, 能确保不同客户端在同一时间读到的内容相同。 在我们这个故障转移的场景,能稳定的输出唯一的leader。

2.  etcd 实现节点故障的实践

etcd的客户端concurrent包提供了依赖于etcd并发操作的上层行为, 比如: 分布式锁、选举、屏障。

选举能力由concurrent 包中的Election中提供。

下面是一个故障转移客户端

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
type Client struct {
 addr               []string
 Leader             string                  // 每个服务节点都知道集群中谁是leader
 cli                  *clientv3.Client
 val                 string                   // 注册到etcd的值, 标记节点
 election          *concurrency.Election
 electSessionDoneCh <-chan struct{}      // 换届的信号
 IsLeader           bool                        //标示当前节点是否是leader
}

2.1  节点初始化,维持心跳保活

每个节点需要维持稳定的心跳保活,  以便参选和换届。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    session, err := concurrency.NewSession(cli, concurrency.WithTTL(10))         // 使用etcd的租约机制来实现心跳保活
 if err != nil {
  return nil, err
 }
 ele := concurrency.NewElection(session, LeaderkeyPfx)     

`NewSession`[1] 实现保活会话。

对应到原始的etcdctl是利用租约:

etcd 有租约操作,租约可以绑定到键值对,实现键值对的存活周期控制; 甚至租约可以不绑定到键值对,仅做心跳保活(有刷新租约的机制)

etcdctl lease grent 30 etcdctl lease keep-alive 41ce93a9f806a53b

2.2 参选

向etcd注册节点标识,这里会将以上保活会话绑定到键值对,

注意: 没有选上的节点会阻塞等待,选上的节点快速返回执行业务逻辑。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func (c *Client) Election(ctx context.Context, id string) bool {
 c.Leader = c.leader() 
 err := c.election.Campaign(ctx, id)
 if err != nil {
  log.WithError(err).WithField("id", id).Error("Campaign error")
  return false
 }
 c.IsLeader = true
 return true
}

核心是利用`Campaign`API[2],    这里面有etcd的事务,源码值得一看,

对应到原始的etcdctl操作:etcdctl put ‐‐lease=41ce93a9f806a53b /merc/leader/41ce93a9f806a53b 127.0.0.1:8686注意: key= /merc/leader/41ce93a9f806a53b, value= 127.0.0.1:8686, 租约是41ce93a9f806a53b(持续保活的租约)

2.3  选举算法

根据当前存活的、最早创建的节点信息键值对 来决定leader , 核心API是Leader接口

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Leader returns the leader value for the current election.
func (e *Election) Leader(ctx context.Context) (*v3.GetResponse, error) {
 client := e.session.Client()
 resp, err := client.Get(ctx, e.keyPrefix, v3.WithFirstCreate()...)
if err != nil {
return nil, err
 } elseif len(resp.Kvs) == 0 {
  // no leader currently elected
return nil, ErrElectionNoLeader
 }
return resp, nil
}

func (c *Client) leader() string {
 ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
 defer cancel()

 resp, err := c.election.Leader(ctx)
if err != nil {
return""
 }
return string(resp.Kvs[0].Value)
}

对应到原始的etcdctl操作是:etcdctl --endpoints=127.0.0.1:2379 get /merc/leader --prefix --sort-by=CREATE --order=ASCEND --limit=1

--prefix  这里我们指定--prefix /merc/leader筛选key 

--sort-by :以x标准(创建时间)检索数据 -- order : 以升降序对已检出的数据排序 -- limit: 从已检出的数据中取x条数据显示

2.4  监控选举结果和换届

通过watch机制通知节点业务代码leader变更,核心是`Observe` API[3]

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func (c *Client) Watchloop(id string, notify chan<- bool) error {
 ch := c.election.Observe(context.TODO()) // 信道传递最新的leader节点, 但是如果底层watcher被一其他方式中段或者超时, 信道会被关闭
 tick := time.NewTicker(time.Minute*5)          // 5min去问下,防止假死
 defer tick.Stop()
for {
  select {
case <-c.electSessionDoneCh: // Done returns a channel that closes when the lease is orphaned, expires, or  is otherwise no longer being refreshed.
   log.Warning("Recv session event")
   return fmt.Errorf("session Done")    // 意味心跳保活失败
case latestLeaderResp, ok := <-ch: // 注意, 从closed(chan) 会持续读到零值, 造成死循环。
   if !ok {
    log.WithField("topic", "watch-loop").Warn("channel closed, something underlying cause error.")
    ch = c.election.Observe(context.TODO())
   } else {
    log.WithField("topic", "watch-loop").Info(latestLeaderResp)
   }
        case <-tick.C:
  }
  ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
  defer cancel()
  resp, err := c.election.Leader(ctx)
  var isLeader bool
if err != nil {
   if err == concurrency.ErrElectionNoLeader {
    ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
    defer cancel()
    isLeader = c.Election(ctx, id)
   } else {
    log.WithError(err).Errorf("watchLoop get leader error")
    isLeader = false
   }
  } elseif string(resp.Kvs[0].Value) != id { //收到leader变化消息,判断发现自己不是leader,停止工作
   c.Leader = string(resp.Kvs[0].Value)
   isLeader = false
  } else {
   c.Leader = string(resp.Kvs[0].Value)
   isLeader = true
  }
if isLeader != c.IsLeader {
   log.WithField("after", isLeader).WithField("before", c.IsLeader).WithField("leader", c.Leader).Info("reElect")
   notify <- isLeader
   c.IsLeader = isLeader
  }
 }
}

监听etcd键值对的变化, 用到了etcd的watch机制:

etcdctl watch --preifx=/merc/leader --start-rev=12345 --prefix监听指定前缀key在全局12345修订版之后的键值对。

2.5 自动故障转移是节点的基础服务

自动故障转移,不是业务代码, 故需要在后台持续运行, 我们开两个goroutine去执行选举和监控换届的逻辑。

讲道理,每个节点只需要知道两个信息就能各司其职

  • 谁是leader  ==> 当前节点是什么角色===> 当前节点该做什么事情
  • 感知集群leader变更的能力 ===>当前节点现在要不要改变行为
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 go func() {
  err := eCli.Watchloop(Id, notify) //后台监测与etcd的连通性以及leader节点的变化
  log.WithError(err).Error("watchLoop error")
  notify <- false
 }()

 go func() {
  if eCli.Election(context.TODO(), Id) {
   notify <- true
  }
 }()

业务逻辑的承载有赖于  notify信道的传递。

3.  etcd式特色民主选举

如果不考虑依赖的CP模型,我们甚至可以使用 mysql,redis做选举

3.1 etcd强烈推荐使用层次化的键空间

与redis类似,虽然可以插入hello:world到键值对存储, 但在编程实践都都推荐使用命名空间的做法来避免键值冲突, redis推荐使用 shopping:users:u1200;

etcd v3[4] 从逻辑上也是一个扁平的二进制键空间, 推荐使用前缀字符串来做命名空间。

这里我提出一个疑问?

Q:  为什么相比redis,etcd将键前缀看的如此重要,单独提供了查询配置选项?

A: etcd用于是为分布式系统的配置管理、服务发现和协调而设计的,它提供了强一致性和高可用性。它允许以层次化的方式组织数据, 这种层次化的特性比redis缓存要求的命名空间更强烈。

election.Campaign(val) 的实质是将K:V(节点id)添加到etcd, 并给予持续保活. etcdctl put ‐‐lease=41ce93a9f806a53b /merc/leader/41ce93a9f806a53b 127.0.0.1:8686 内部实现上, 续约值成为了注册键的一部分, 参与竞选的节点都注册到/merc/leader 前缀下。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    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())
     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
     if !resp.Succeeded {
      kv := resp.Responses[0].GetResponseRange().Kvs[0]
      e.leaderRev = kv.CreateRevision
      if string(kv.Value) != val {
       if err = e.Proclaim(ctx, val); err != nil {
        e.Resign(ctx)
        return err
       }
      }
     }

3.2 etcd全局修订版本号在选举算法中的应用?

当选: 当前存活的、最早创建的key是leader , 也就是说master/slave故障转移并不是随机的,下一个当上leader的是次早创建的节点。

client.Get(ctx, e.keyPrefix, v3.WithFirstCreate()...)

当前存活的,最早创建的节点:  如何定义最早创建的节点?

应该是利用找到的存活节点中 revision最小的那一个key。

什么是revision修订版? etcd 提供了对不常变更的数据的一致性查询和变更监听能力, 同时包含对于变更的快照查询和历史版本查询, etcd在全局键空间有一个revision修订版。键空间任意一个变更,该修订版都会单调递增。

etcd的客户端交互有赖于grpc请求, 我们看了通过发起的grpc请求来验证此次使用了修订版机制。

etcd的客户端交互API氛围三大类: KV、Watch、Lease, KV 操作都收敛到do()函数内枚举发起grpc请求:

=> client.Get(ctx, e.keyPrefix, v3.WithFirstCreate()...) ==>   r, err := kv.Do(ctx, OpGet(key, opts...)) ====> op.toRangeRequest() 产生了https://etcd.io/docs/v3.5/learning/api/ 文档规定的grpc参数

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func (kv *kv) Do(ctx context.Context, op Op) (OpResponse, error) {
 var err error
 switch op.t {
case tRange:
if op.IsSortOptionValid() {
   var resp *pb.RangeResponse
   resp, err = kv.remote.Range(ctx, op.toRangeRequest(), kv.callOpts...)
   if err == nil {
    return OpResponse{get: (*GetResponse)(resp)}, nil
   }
  } else {
   err = rpctypes.ErrInvalidSortOption
  }
case tPut:

其中: v3.WithFirstCreate()构造了[]OpOption { return withTop(SortByCreateRevision, SortAscend) } grpc range请求参数, 此处可验证找到一组key之后, 按照revision升序排列,第一个key为leader。

3.3 etcd的watch机制

在监控换届时,我们用到了etcd异步监听变更事件的watch API[5]

异步监听变更, 本身是一个长期运行的行为, 在etcd是使用grpc的双向流式通信来实现。这个留待读者自行去解码[6]。

复盘

本文从一个自动故障转移的生产实践, 延伸到

  • 状态机机制
  • etcd提供一致性共识的基础能力
  • 选举需要实现的:  参选机制、当选算法、换届方式、

描述了使用编程来实现选举和换届这一民主生活实践。

落地到etcd式特色选举,提炼了etcd全局修订版机制在选举算法中的应用, grpc流式通信在etcd watch机制中的应用。

参考资料

[1]

NewSession:https://github.com/etcd-io/etcd/blob/55500416335e959e347d368c7f8a7a0229db3f6a/client/v3/concurrency/session.go#L38

[2]

CampaignAPI:https://github.com/etcd-io/etcd/blob/9fa35e53f429ca8f21b0d6b26f24e1848f2652a6/client/v3/concurrency/election.go#L69

[3]

Observe API:https://github.com/etcd-io/etcd/blob/9fa35e53f429ca8f21b0d6b26f24e1848f2652a6/client/v3/concurrency/election.go#L173

[4]

etcd v3:https://etcd.io/docs/v3.5/learning/data_model/#logical-view

[5]

watch API:https://etcd.io/docs/v3.5/learning/api/#watch-api

[6]

解码:https://github.com/etcd-io/etcd/blob/9fa35e53f429ca8f21b0d6b26f24e1848f2652a6/client/v3/watch.go#L548

本篇文字和图片均为原创,读者可结合图片探索源码, 欢迎反馈 ~。。~。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-12-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 精益码农 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
利用etcd选举sdk实践master/slave故障转移
本次记录[利用etcd选主sdk实践master/slave故障转移], 并利用etcdctl客户端验证选主sdk的工作原理。
有态度的马甲
2022/08/23
3980
利用etcd选举sdk实践master/slave故障转移
etcd使用场景——Master选举分析与实现
  etcd有多种使用场景,Master选举是其中一种。说起Master选举,过去常常使用zookeeper,通过创建EPHEMERAL_SEQUENTIAL节点(临时有序节点),我们选择序号最小的节点作为Master,逻辑直观,实现简单是其优势,但是要实现一个高健壮性的选举并不简单,同时zookeeper繁杂的扩缩容机制也是沉重的负担。
andydbhe
2019/07/07
11.5K0
etcd使用场景——Master选举分析与实现
如何与 etcd 服务端进行通信?客户端 API 实践与核心方法介绍
学习客户端与 etcd 服务端的通信以及 etcd 集群节点的内部通信接口对于我们更好地使用和掌握 etcd 组件很有帮助,也是所必需了解的内容。
aoho求索
2021/10/26
3.2K0
如何与 etcd 服务端进行通信?客户端 API 实践与核心方法介绍
又超时了!Etcd分布式锁你用明白了吗?
线上程序报错,错误信息:lock failed: context deadline exceeded, retry
米开朗基杨
2021/04/02
1.4K0
golang etcd简明教程
etcd 是一个高可用强一致性的键值仓库在很多分布式系统架构中得到了广泛的应用,本教程结合一些简单的例子介绍golang版本的 etcd/clientv3中提供的主要功能及其使用方法。
KevinYan
2019/11/04
5K0
GO 中 ETCD 的编码案例分享
要是对 服务注册与发现,ETCD 还有点兴趣的话,欢迎查看文章 服务注册与发现之ETCD
阿兵云原生
2023/02/16
3400
还活在上个时代,Etcd 3.0 实现分布式锁竟如此简单!
传统 Python 单机系统部署中,由于 GIL 的存在,相同进程中我们可以不用处理并发问题。但是随着业务发展需要,原有单机系统演变成分布式或多进程系统后。这将使原有的单机单进程并发控制策略失效。为了解决该问题需要引入一种跨进程、跨机器的互斥锁机制来控制共享资源的访问,这也就是分布式锁的由来。
程序员荒生
2022/05/19
1.2K0
还活在上个时代,Etcd 3.0 实现分布式锁竟如此简单!
读猿码系列——2. 搞懂Etcd核心API
小区从管控区调整为防范区了,40多天的封闭后终于可以光明正大地下楼遛狗了!许愿能尽快吃上平价麦当劳,而且每顿都有可口可乐!日拱一卒,让我们开始吧!(长文预警哦)
才浅Coding攻略
2022/12/12
5710
读猿码系列——2. 搞懂Etcd核心API
golang源码分析:etcd(5)
在分析完etcd的client如何使用后,我们看下etcd的client源码,etcd是通过rpc和server通信的,其中关于kv相关操作位于etcd/api的api/v3@v3.5.6/etcdserverpb/rpc.pb.go
golangLeetcode
2023/08/09
4170
彻底搞懂 etcd 系列文章(五):etcdctl 的使用
etcd 是云原生架构中重要的基础组件,由 CNCF 孵化托管。etcd 在微服务和 Kubernates 集群中不仅可以作为服务注册与发现,还可以作为 key-value 存储的中间件。
aoho求索
2020/07/15
4K0
Etcd基础学习之架构及工作原理
Etcd应用背景说明: 在实际生产环境中,有很多应用在同一时刻只能启动一个实例,例如更新数据库的操作,多个实例同时更新不仅会降低系统性能,还可能导致数据的不一致。但是单点部署也使得系统的容灾性减弱,比如进程异常退出 目前进程保活,也有很多方案如supervisor和systemd。但是如果宿主机down掉呢? 所有的进程保活方法都会无济于事,于是我们可以采用基于etcd自带的leader选举机制,轻松的使服务具备了高可用性。
全栈工程师修炼指南
2022/09/29
3.5K0
Etcd基础学习之架构及工作原理
golang-etcd系列(一)--初识
etcd 是一个 golang 编写的分布式、高可用的一致性键值存储系统,是目前容器编排领域火热的 Kubernetes(k8s) 内置的服务发现与节点一致性中间件,用于提供可靠的分布式键值(key-value)存储、配置共享和服务发现等功能。etcd 可以用于存储关键数据和实现分布式调度,在现代化的集群运行中能够起到关键性的作用。
astraw99
2021/09/22
6200
golang-etcd系列(一)--初识
【Golang】使用Go语言操作etcd——配置中心
【etcd】etcd使用与集群搭建 博文中已经大致介绍了 etcd与集群搭建,下面将针对etcd的使用场景之一的 配置中心做开发实战。
DDGarfield
2022/06/23
4.7K0
【Golang】使用Go语言操作etcd——配置中心
Windows下安装etcd集群及zRPC的简单使用
etcd是CoreOS团队于2013年6月发起的开源项目,它的目标是构建一个高可用的分布式键值(key-value)数据库。etcd内部采用raft协议作为一致性算法,etcd基于Go语言实现。
杨永贞
2021/03/04
3.2K0
还不会使用分布式锁?从零开始基于 etcd 实现分布式锁
在单进程的系统中,当存在多个线程可以同时改变某个变量时,就需要对变量或代码块做同步,使其在修改这种变量时能够线性执行消除并发修改变量。而同步本质上通过锁来实现。为了实现多个线程在一个时刻同一个代码块只能有一个线程可执行,那么需要在某个地方做个标记,这个标记必须每个线程都能看到,当标记不存在时可以设置该标记,其余后续线程发现已经有标记了则等待拥有标记的线程结束同步代码块取消标记后再去尝试设置标记。
aoho求索
2021/05/11
7850
Botposter.com集群ETCD2.3.7升级至3.0实录
7月1日,ETCD隆重发布了3.0版本。Botposter.com也在第一时间对集群进行了升级。本文是升级过程的记录与总结(文中假设读者已经使用或测试过ETCD V2,如有不妥请见谅)。
架构师精进
2018/08/27
7360
还不了解 etcd?一文带你快速入门(万字长文)
2018年12月 etcd 作为孵化项目 CNCF(云原生计算基金会),几天前 CNCF 宣布 etcd 正式毕业,成为 CNCF 顶级项目。CNCF 官方表示 etcd 项目的采用率持续增加,也有稳定的治理流程,功能已达一定成熟度。
aoho求索
2020/12/01
17.5K0
还不了解 etcd?一文带你快速入门(万字长文)
etcd Lease:etcd 如何实现租约?
你好,我是 aoho,今天我和你分享的主题是 etcd Lease:etcd 如何实现租约?
aoho求索
2022/12/03
1.6K0
etcd Lease:etcd 如何实现租约?
实战etcd的服务发现
在云原生的时代,服务发现已经是必不可少的功能,我借着最近迁移 gRPC 服务的机会尝试了一下如何用 etcd 实现服务发现,期间遇到诸多问题,本文逐一记之。
LA0WAN9
2021/12/14
1.4K1
实战etcd的服务发现
彻底搞懂 etcd 系列文章(七):etcd gRPC 服务 API
etcd 是云原生架构中重要的基础组件,由 CNCF 孵化托管。etcd 在微服务和 Kubernates 集群中不仅可以作为服务注册与发现,还可以作为 key-value 存储的中间件。
aoho求索
2020/09/01
3.6K0
相关推荐
利用etcd选举sdk实践master/slave故障转移
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验