利用etcd选举sdk实践master/slave故障转移

简介: 本次记录[利用etcd选主sdk实践master/slave故障转移], 并利用etcdctl客户端验证选主sdk的工作原理。

本次记录[利用etcd选主sdk实践master/slave故障转移], 并利用etcdctl客户端验证选主sdk的工作原理。


a595bf177e64fb04a0615a1c7a3a576f.png


master/slave高可用集群


本文目标


在异地多机房部署节点,slave作为备用实例启动,但不接受业务流量, 监测到master宕机,slave节点自动提升为master并接管业务流量。


基本思路


各节点向etcd注册带租约的节点信息, 并各自维持心跳保活,选主sdk根据目前存活的、最早创建的节点信息键值对 来确立leader, 并通过watch机制通知业务代码leader变更。


3db1f9f841cc5e93813867643aaede50.png



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


  • • 谁是leader ==> 当前节点是什么角色===> 当前节点该做什么事情


  • • 感知集群leader变更的能力 ===》当前节点现在要不要改变行为


除了官方etcd客户端go.etcd.io/etcd/client/v3, 还依赖go.etcd.io/etcd/client/v3/concurrency package:实现了基于etcd的分布式锁、屏障、选举


选主过程 实质 api
竞选前先查询leader了解现场 查询当前存活的,最早创建的kv值 *concurrency.Election.Leader()
初始化时,各节点向etcd阻塞式竞选 各节点向etcd注册带租约的键值对 *concurrency.Election.compaign
建立master/slave集群,还能及时收到变更通知 通过chan传递最新的leader value *concurrency.Election.Observe()


重点解读


1.初始化etcd go客户端


注意:etcd客户端和服务端是通过grpc来通信,目前新版本的etcd客户端默认使用非阻塞式连接, 也就是说v3.New函数仅表示从指定配置创建etcd客户端。


2838c9cda84da4d9f5aaa13152b444d5.png


为快速确定etcd选举的可用性,本实践使用阻塞式创建客户端:


cli, err := v3.New(v3.Config{
  Endpoints:   addr,
  DialTimeout: time.Second * 5,
  DialOptions: []grpc.DialOption{grpc.WithBlock()},
 })
 if err != nil {
  log.WithField("instance", Id).Errorln(err)
  return nil, err
 }


2. 竞选


使用阻塞式命令compaign竞选之前,应先查询当前leader:


// 将id:ip:port作为竞选时写入etcd的value
func (c *Client) Election(id string, notify chan<- bool) error {
 //竞选前先试图去了解情况
 ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
 defer cancel()
 resp, err := c.election.Leader(ctx)
 if err != nil {
  if err != concurrency.ErrElectionNoLeader {
   return err
  }
 } else { // 已经有leader了
  c.Leader = string(resp.Kvs[0].Value)
  notify <- (c.Leader == id)
 }
 if err = c.election.Campaign(context.TODO(), id); err != nil {
  log.WithError(err).WithField("id", id).Error("Campaign error")
  return err
 } else {
  log.Infoln("Campaign success!!!")
  c.Leader = id
  notify <- true
 }
 c.election.Key()
 return nil
}


参选:将持续刷新的leaseID作为key,将特定的客户端标记(这里使用ip:port)作为value,写到etcd.


当选:当前存活的、最早创建的key是leader , 也就是说leader故障转移并不是随机的


6fe83a2f48dc88834347a40a81c42390.png


3. watch leader变更


golang使用信道完成goroutine通信,


本例声明信道: notify = make(chan bool, 1)


一石二鸟:监听该信道能够知道集群leader是否发生变化;信道内传的值表示当前节点是否是leader


func (c *Client) Watchloop(id string, notify chan<- bool) error {
 ch := c.election.Observe(context.TODO()) // 观察leader变更
 tick := time.NewTicker(c.askTime)
 defer tick.Stop()
 for {
  var leader string
  select {
  case _ = <-c.sessionCh:
   log.Warning("Recv session event")
   return fmt.Errorf("session Done") // 一次续约不稳,立马退出程序
  case e := <-ch:
   log.WithField("event", e).Info("watch leader event")
   leader = string(e.Kvs[0].Value)
   ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
   defer cancel()
   resp, err := c.election.Leader(ctx)
   if err != nil {
    if err != concurrency.ErrElectionNoLeader {
     return err
    } else { // 目前没leader,开始竞选了
     if err = c.election.Campaign(context.TODO(), id); err != nil {
      log.WithError(err).WithField("id", id).Error("Campaign error")
      return err
     } else { // 竞选成功
      leader = id
     }
    }
   } else {
    leader = string(resp.Kvs[0].Value)
   }
  }
  if leader != c.Leader {
   log.WithField("before", c.Leader).WithField("after", leader == id).Info("leader changed")
   notify <- (leader == id)
  }
  c.Leader = leader
 }
}


c.election.Observe(context.TODO()) 返回最新的leader信息,配合select case控制结构能够及时拿到leader变更信息。


如题:通过Leader字段和chan <- bool信道, 掌控了整个选举集群的状态, 可根据这两个信息去完成业务上的master/slave故障转移。


使用etcdctl确定leader


election.Leader的源码证明了[当前存活的,最早创建的kv为leader]


// 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
 } else if len(resp.Kvs) == 0 {
  // no leader currently elected
  return nil, ErrElectionNoLeader
 }
 return resp, nil
}


等价于

./etcdctl get  /merc --prefix  --sort-by=CREATE  --order=ASCEND  --limit=1


-- sort-by :以某标准(创建时间)检索数据

-- order :以升/降序给已检出的数据排序

-- limit:从以检出的数据中取x条数据显示

相关文章
|
Java 测试技术 API
.net core实践系列之短信服务-Api的SDK的实现与测试(二)
.net core实践系列之短信服务-Api的SDK的实现与测试(二)
183 0
.net core实践系列之短信服务-Api的SDK的实现与测试(二)
|
XML JSON API
.net core实践系列之短信服务-Api的SDK的实现与测试(一)
.net core实践系列之短信服务-Api的SDK的实现与测试(一)
324 0
.net core实践系列之短信服务-Api的SDK的实现与测试(一)
|
对象存储 开发工具 算法
OSS 实践篇-OSS C SDK 深度案例
概述: 客户使用 OSS C SDK 3.5 版本,通过 get_object_to_local_file 方法直接下载 OSS文件到本地,测试过程返回 403 签名不对; 搜集信息 挖取有价值的信息很重要,通过基础信息可以筛选下客户上传日志的详细描述,通过堆栈报错可以找到客户大概原因; OSS...
1798 0
OSS 实践篇-OSS C SDK 深度案例
|
开发工具 C语言 Linux
OSS 实践篇-C SDK 安装
背景: 由于很多人对 SDK 的安装和系统依赖的环境变量不是很熟悉,导致很熟悉,浪费不必要的时间,而且导致环境变量引入也出现异常。特此写了一篇从安装到遇坑的过程给大家。 操作环境: Linux Centos 6.9 64 位系统 预先安装好的库: 1、glibc-2.14 (mxml 库需要依赖这个库): 先看下 strings /lib64/libc.so.6 | grep GLIBC 是否有 GLBC-2.14 或者以上。
1423 0
OSS 实践篇-C SDK 安装
|
Go 开发工具
Knative Serving 进阶: Knative Serving SDK 开发实践
作者 | 阿里云智能事业群技术专家 牛秋霖(冬岛) 导读:通过前面的一系列文章你已经知道如何基于 kubectl 来操作 Knative 的各种资源。但是如果想要在项目中集成 Knative 仅仅使用 kubectl 这种命令的方式是不够的,还需要在代码中基于 Knative Serving SDK 进行集成开发。
|
API 开发工具
阿里云API、SDK和CLI应用实践方案
⽬目前阿⾥里里云云产品提供了了丰富的API供⽤用户使⽤用,如果⽤用户熟悉HTTP协议以及⼀一种以上的编程资源,可以推荐 ⽤用户使⽤用阿⾥里里云提供的OpenAPI来管理理和使⽤用⽤用户的云产品资源,本⽂文档以视频云CDN的OpenAPI为例例,实践 API接⼝口的调试以及使⽤用⽅方式,特别适⽤用⽤用.
1898 0
|
3月前
|
JavaScript 前端开发 Java
[Android][Framework]系统jar包,sdk的制作及引用
[Android][Framework]系统jar包,sdk的制作及引用
95 0
|
19天前
|
Java Linux API
Android SDK
【10月更文挑战第21天】
51 1
|
29天前
|
程序员 开发工具 Android开发
Android|使用阿里云推流 SDK 实现双路推流不同画面
本文记录了一种使用没有原生支持多路推流的阿里云推流 Android SDK,实现同时推送两路不同画面的流的方法。
50 7

热门文章

最新文章

下一篇
无影云桌面