本次将记录[利用etcd选主sdk实践master/slave高可用], 并利用etcdctl原生脚本验证选主sdk的工作原理。

master/slave高可用集群

本文目标

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

基本思路

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

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

  • 谁是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客户端。

为快速确定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 , 也就是说master/slave故障转移并不是随机的

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 :以x标准(创建时间)检索数据

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

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

使用etcd选举sdk实践master/slave故障转移的更多相关文章

  1. Redis源码解析:22sentinel(三)客观下线以及故障转移之选举领导节点

    八:判断实例是否客观下线 当前哨兵一旦监测到某个主节点实例主观下线之后,就会向其他哨兵发送"is-master-down-by-addr"命令,询问其他哨兵是否也认为该主节点主观下 ...

  2. Redis Master/Slave 实践

    本次我们将模拟 Master(1) + Slave(4) 的场景,并通过ASP.NET WEB API进行数据的提交及查询,监控 Redis Master/Slave 数据分发情况,只大致概述,不会按 ...

  3. Redis master/slave,sentinel,Cluster简单总结

    现在互联网项目中大量使用了redis,本文著主要分析下redis 单点,master/slave,sentinel模式.cluster的一些特点. 一.单节点模式 单节点实例还是比较简单的,平时做个测 ...

  4. mongodb集群故障转移实践

    简介 NOSQL有这些优势: 大数据量,可以通过廉价服务器存储大量的数据,轻松摆脱传统mysql单表存储量级限制. 高扩展性,Nosql去掉了关系数据库的关系型特性,很容易横向扩展,摆脱了以往老是纵向 ...

  5. redis之master.slave主从复制

    简介 主机数据更新后根据配置和策略,自动同步到备机的master/slave机制,master以写为主,slave以读为主 从库配置 配置从库,不配主库 配置从库: 格式: slaveof 主库ip ...

  6. Windows下搭建MySQL Master Slave

    一.背景 服务器上放了很多MySQL数据库,为了安全,现在需要做Master/Slave方案,因为操作系统是Window的,所以没有办法使用keepalived这个HA工具,但是我们可以接受人工进行切 ...

  7. Windows下搭建MySQL Master Slave[转]

    Windows下搭建MySQL Master Slave 一.背景 服务器上放了很多MySQL数据库,为了安全,现在需要做Master/Slave方案,因为操作系统是Window的,所以没有办法使用k ...

  8. MySQL的Master/Slave群集安装和配置

    本文介绍MySQL的Master/Slave群集安装和配置,版本号安装最新的稳定版GA 5.6.19. 为了支持有限HA.我们用Master/Slave读写简单孤立的集群.有限HA这是当Master不 ...

  9. Redis主从复制(Master/Slave)

    Redis主从复制(Master/Slave) 修改配置文件 拷贝多个redis.conf文件分别配置如下参数: 开启daemonize yes pidfile port logfile dbfile ...

随机推荐

  1. 常见的url编码

    URL编码值 字符 %20 空格 %22 " %23 # %25 % %26 &; %28 ( %29 ) %2B + %2C , %2F / %3A : %3B ; %3C < ...

  2. 一个lseek引起的思考

    先看一段代码: int find_value(int fd) { int ret; char buff[8] = ""; struct timeval st,ed; long lo ...

  3. 羽夏看Win系统内核—— x64 番外篇

    写在前面   此系列是本人一个字一个字码出来的,包括示例和实验截图.由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新. 如有好的建议,欢迎反馈.码字不易, ...

  4. java实现稀疏矩阵的压缩与解压

    任务要求 把棋盘当作一个稀疏矩阵,0表示没棋,1表示黑棋,2表示蓝棋. 把该稀疏矩阵压缩以三元组形式表示并以文件形式保存,再写另一个程序读取文件中的信息把压缩后的三元组还原成原来的稀疏矩阵. 其中三元 ...

  5. PWA全称Progressive Web App,即渐进式WEB应用?

    一个 PWA 应用首先是一个网页, 可以通过 Web 技术编写出一个网页应用. 随后添加上 App Manifest 和 Service Worker 来实现 PWA 的安装和离线等功能解决了哪些问题 ...

  6. String s = new String("xyz");创建了几个字符串对象?

    两个对象,一个是静态区的"xyz",一个是用new创建在堆上的对象.

  7. homebrew 安装nginx+php+mysql

    转:https://juejin.im/post/5c8fb28a6fb9a07103548318 brew search nginxbrew install nginx /usr/local/etc ...

  8. 百度移动统计调用api教程,少进坑(82001错误)

    相信很多小伙伴使用了百度统计,来查看自己应用使用的情况,但是会发现百度移动统计在官网没有api调用取数据的接口, 现在我就以自己成功调用api并且成功拿到数据,将这个步骤给大家参考,(末尾有调用移动统 ...

  9. Rust 中的数据布局--非正常大小的类型

    非正常大小的类型 大多数的时候,我们期望类型在编译时能够有一个静态已知的非零大小,但这并不总是 Rust 的常态. Dynamically Sized Types (DSTs) Rust 支持动态大小 ...

  10. python学习笔记(六)——异常处理

    异常处理 在 Python 中,通过两个重要的功能来处理程序在运行中出现的异常和错误.其一是本文提到的异常处理,其二是断言(Assertions). python中,所有的异常均派生自 BaseExc ...