在分布式集群部署模式下,为了维护数据一致性,通常需要选举出一个leader来进行协调,并且在leader挂掉后能从集群中选举出一个新的leader。选举leader的方案有很多种,对Paxos和Raft协议有过了解的同学应该对leader选举有一些认识,一般都是按照少数服从多数的原则来实现,但是因为分布式环境中无法避免的网络不稳定、数据不同步、时间偏差等问题,要想搞好leader选举并不是一件特别容易的事。这篇文章将提供一个使用Consul做leader选举的简单方案。

原理

Consul 的leader选举只有两步:

1、Create Session:参与选举的应用分别去创建Session,Session的存活状态关联到健康检查。

2、Acquire KV:多个应用带着创建好的Session去锁定同一个KV,只能有一个应用锁定住,锁定成功的应用就是leader。

如上图所示,这里假设App2用Session锁定住了KV,其实就是KV的Session属性设置为了Session2。

什么时候会触发重新选举呢?

  • Session失效:Session被删除、Session关联的健康检查失败、Session TTL过期等。
  • KV被删除:这个没什么需要特别说明的。

那应用们怎么感知这些情况呢?

应用在选举结束后,应该保持一个到KV的阻塞查询,这个查询会在超时或者KV发生变化的时候返回结果,这时候应用可以根据返回结果判断是否发起新的选举。

示例

这里给出一个Java的例子:这是一个控制台程序,程序会创建一个Session,然后尝试使用这个Session锁定key为“program/leader”的Consul KV,同时也会尝试设置KV的值为当前节点Id“007”。不管捕获成功还是失败,程序随后都会启动一个针对“program/leader”的阻塞查询,在阻塞查询返回时会判断KV是否存在或者绑定的Session是否存在,如果有任何一个不存在,则发起选举,否则继续阻塞查询。这个“阻塞查询->选举”的操作是一个无限循环操作。

package cn.bossma;

import com.ecwid.consul.v1.ConsulClient;
import com.ecwid.consul.v1.QueryParams;
import com.ecwid.consul.v1.kv.model.GetValue;
import com.ecwid.consul.v1.kv.model.PutParams;
import com.ecwid.consul.v1.session.model.NewSession;
import com.ecwid.consul.v1.session.model.Session;
import org.apache.commons.lang3.StringUtils; /**
* consul leader 选举演示程序
*
* @author: bossma.cn
*/
public class Main { private static ConsulClient client = new ConsulClient();
private static String sesssionId = "";
private static String nodeId = "007";
private static String electName = "program/leader"; /**
* @param args
*/
public static void main(String[] args) {
System.out.println("starting");
watch();
} /**
* 监控选举
*
* @param:
* @return:
* @author: bossma.cn
*/
private static void watch() { System.out.println("start first leader election"); // 上来就先选举一次,看看结果
ElectResponse electResponse = elect();
System.out.printf("elect result: %s, current manager: %s" + System.getProperty("line.separator"), electResponse.getElectResult(), electResponse.getLeaderId()); long waitIndex = electResponse.modifyIndex++;
int waitTime = 30; do {
try {
System.out.println("start leader watch query"); // 阻塞查询
GetValue kv = getKVValue(electName, waitTime, waitIndex); // kv被删除或者kv绑定的session不存在
if (null == kv || StringUtils.isEmpty(kv.getSession())) {
System.out.println("leader missing, start election right away");
electResponse = elect();
waitIndex = electResponse.modifyIndex++;
System.out.printf("elect result: %s, current manager: %s" + System.getProperty("line.separator"), electResponse.getElectResult(), electResponse.getLeaderId());
} else {
long kvModifyIndex = kv.getModifyIndex();
waitIndex = kvModifyIndex++;
}
} catch (Exception ex) {
System.out.print("leader watch异常:" + ex.getMessage()); try {
Thread.sleep(3000);
} catch (Exception ex2) {
System.out.printf(ex2.getMessage());
}
}
}
while (true);
} /**
* 执行选举
*
* @param:
* @return:
* @author: bossma.cn
*/
private static ElectResponse elect() { ElectResponse response = new ElectResponse(); Boolean electResult = false; // 创建一个关联到当前节点的Session
if (StringUtils.isNotEmpty(sesssionId)) {
Session s = getSession(sesssionId);
if (null == s) {
// 这里session关联的健康检查只绑定了consul节点的健康检查
// 实际使用的时候建议把当前应用程序的健康检查也绑定上,否则如果只是程序关掉了,session也不会失效
sesssionId = createSession(10);
}
} else {
sesssionId = createSession(10);
} // 获取选举要锁定的Consul KV对象
GetValue kv = getKVValue(electName);
if (null == kv) {
kv = new GetValue();
} // 谁先捕获到KV,谁就是leader
// 注意:如果程序关闭后很快启动,session关联的健康检查可能不会失败,所以session不会失效
// 这时候可以把程序创建的sessionId保存起来,重启后首先尝试用上次的sessionId,
electResult = acquireKV(electName, nodeId, sesssionId); // 无论参选成功与否,获取当前的Leader
kv = getKVValue(electName);
response.setElectResult(electResult);
response.setLeaderId(kv.getDecodedValue());
response.setModifyIndex(kv.getModifyIndex());
return response;
} /**
* 创建Session
*
* @param: lockDealy session从kv释放后,kv再次绑定session的延迟时间
* @return:
* @author: bossma.cn
*/
private static String createSession(int lockDelay) {
NewSession session = new NewSession();
session.setLockDelay(lockDelay);
return client.sessionCreate(session, QueryParams.DEFAULT).getValue();
} /**
* 获取指定的session信息
*
* @param: sessionId
* @return: Session对象
* @author: bossma.cn
*/
private static Session getSession(String sessionId) {
return client.getSessionInfo(sessionId, QueryParams.DEFAULT).getValue();
} /**
* 使用Session捕获KV
*
* @param key
* @param value
* @param sessionId
* @return
* @author: bossma.cn
*/
public static Boolean acquireKV(String key, String value, String sessionId) {
PutParams putParams = new PutParams();
putParams.setAcquireSession(sessionId); return client.setKVValue(key, value, putParams).getValue();
} /**
* 获取指定key对应的值
*
* @param: key
* @return:
* @author: bossma.cn
*/
private static GetValue getKVValue(String key) {
return client.getKVValue(key).getValue();
} /**
* block获取指定key对应的值
*
* @param: key, waitTime, waitIndex
* @return:
* @author: bossma.cn
*/
private static GetValue getKVValue(String key, int waitTime, long waitIndex) {
QueryParams paras = QueryParams.Builder.builder()
.setWaitTime(waitTime)
.setIndex(waitIndex)
.build();
return client.getKVValue(key, paras).getValue();
} /**
* leader选举结果
*
* @author: bossma.cn
*/
private static class ElectResponse { private Boolean electResult = false;
private long modifyIndex = 0;
private String leaderId; public String getLeaderId() {
return leaderId;
} public void setLeaderId(String leaderId) {
this.leaderId = leaderId;
} public Boolean getElectResult() {
return electResult;
} public void setElectResult(Boolean electResult) {
this.electResult = electResult;
} public long getModifyIndex() {
return modifyIndex;
} public void setModifyIndex(long modifyIndex) {
this.modifyIndex = modifyIndex;
}
}
}

1、用于选举的Consul KV必须使用锁定的session进行更新,如果通过其它方式更新,KV绑定的Session不会有影响,也就是说KV还是被原来的程序锁定,但是却被其它的程序修改了,这不符合leader的规则。

2、Session 关联的健康检查默认只有当前节点的健康检查,如果应用程序停止,Session并不会失效,所以建议将Session 关联的健康检查包含应用的健康检查;但是如果只有应用的健康检查,服务器停止,应用的健康检查仍可能是健康的,所以Session的健康检查应该把应用程序和Consul 节点的健康检查都纳入进来。

3、如果程序关闭后很快启动,session关联的健康检查可能不会失败,所以session不会失效,程序启动后如果创建一个新的Session去锁定KV,就不能成功锁定KV,这时候建议将SessionId持久化存储,如果Session还存在,就还是用这个Session 去锁定。

4、lockdelay:这不是一个坑,是一个保护机制,但需要考虑好用不用。它可以在session失效后短暂的不允许其它session来锁定KV,这是因为此时应用程序可能还是正常的,session关联的健康检查误报了,应用程序可能还在处理业务,需要一段时间来结束处理。也可以使用0值把这个机制禁止掉。

5、可能leader加载的东西比较多,leader切换比较麻烦,考虑到session关联的健康检查误报的问题,希望leader选举优先上次锁定KV的程序,这样可以提高效率。此时可以在选举程序中增加一些逻辑:如果选举的时候发现上次的leader是当前程序,则立即选举;如果发现上次的leader不是当前程序,则等待两个固定的时间周期再提交选举。

整体上看,Consul提供的Leader选举方案还是比较简单的,无论是集群部署中的leader选举,还是传统主备部署,都可以适用。其中的关键是Session,一定要结合自己的业务考虑周全。最后欢迎加入800人Consul交流群234939415,一起探讨使用Consul的各种场景和问题。

使用Consul做leader选举的方案的更多相关文章

  1. 一个基于Consul的.NET Leader选举类库

    前段时间有传言说Consul将不能在我国继续使用,后被查明是因法律问题Vault企业版产品不能在国内销售.Valut和Consul都是HashiCorp公司的产品,并且都推出了开源版本,继续使用开源版 ...

  2. 使用Consul做服务发现的若干姿势

    从2016年起就开始接触Consul,使用的主要目的就是做服务发现,后来逐步应用于生产环境,并总结了少许使用经验.最开始使用Consul的人不多,为了方便交流创建了一个QQ群,这两年微服务越来越火,使 ...

  3. Consul做服务发现

    使用Consul做服务发现的若干姿势 https://www.cnblogs.com/bossma/p/9756809.html 从2016年起就开始接触Consul,使用的主要目的就是做服务发现,后 ...

  4. kafka知识体系-kafka设计和原理分析-kafka leader选举

    kafka leader选举 一条消息只有被ISR中的所有follower都从leader复制过去才会被认为已提交.这样就避免了部分数据被写进了leader,还没来得及被任何follower复制就宕机 ...

  5. 【分布式】Zookeeper的Leader选举

    一.前言 前面学习了Zookeeper服务端的相关细节,其中对于集群启动而言,很重要的一部分就是Leader选举,接着就开始深入学习Leader选举. 二.Leader选举 2.1 Leader选举概 ...

  6. Curator leader 选举(一)

    要想使用Leader选举功能,需要添加recipes包,可以在maven中添加如下依赖: <dependency> <groupId>org.apache.curator< ...

  7. 第四章 Leader选举算法分析

    Leader选举 学习leader选举算法,主要是从选举概述,算法分析与源码分析(后续章节写)三个方面进行. Leader选举概述 服务器启动时期的Leader选举 选举的隐式条件便是ZooKeepe ...

  8. 【Zookeeper】源码分析之Leader选举(二)

    一.前言 前面学习了Leader选举的总体框架,接着来学习Zookeeper中默认的选举策略,FastLeaderElection. 二.FastLeaderElection源码分析 2.1 类的继承 ...

  9. zookeeper源码 — 二、集群启动—leader选举

    上一篇介绍了zookeeper的单机启动,集群模式下启动和单机启动有相似的地方,但是也有各自的特点.集群模式的配置方式和单机模式也是不一样的,这一篇主要包含以下内容: 概念介绍:角色,服务器状态 服务 ...

随机推荐

  1. 右键新建 .md

    右键新建 .md 文件 声明:虽然我成功了,并且右键出来了两个,但是在添加 .html 的过程中又失败了,找不到解决办法. win + r --> regedit --> enter 点击 ...

  2. sparksession创建DataFrame方式

    spark创建dataFrame方式有很多种,官方API也比较多 公司业务上的个别场景使用了下面两种方式 1.通过List创建dataFrame /** * Applies a schema to a ...

  3. SpringBoot中快速实现邮箱发送

    前言 在许多企业级项目中,需要用到邮件发送的功能,如: 注册用户时需要邮箱发送验证 用户生日时发送邮件通知祝贺 发送邮件给用户等 创建工程导入依赖 <!-- 邮箱发送依赖 --> < ...

  4. .net测试篇之测试神器Autofixture在几个复杂场景下的使用示例以及与Moq结合

    系列目录 为String指定一个值. 在第三节里我们讲了如何使用自定义配置加上一个自定义算法生成一个自定义字符串,然而有些时候我们仅仅是需要某个字段是有意义的,这个时候随便生成的字符串也满足不了我们的 ...

  5. 如何配置sigar在Linux和Windows下使用java语言获得各种系统信息

    转自:https://blog.csdn.net/qq_27093465/article/details/70227619

  6. 01 - zabbix | LLD自动发现

    01 - zabbix | LLD自动发现 1. 原理 zabbix支持设置变量,用{#VAR_NAME}来表示.然后有一些系统保留的变量 2. 设置 2.1 交换机电源自动发现   名字写好后进进入 ...

  7. 从0系统学Android-2.5更多隐式Intent用法

    本系列文章,参考<第一行代码>,作为个人笔记 更多内容:更多精品文章分类 从0系统学Android-2.5更多隐式Intent用法 上一节中我们学习了通过隐式 Intent 来启动 Act ...

  8. dotnet core各rpc组件的性能测试

    一般rpc通讯组件都具有高性特性,因为大部分rpc都是基于二进制和连接复用的特点,相对于HTTP(2.0以下的版本)来说有着很大的性能优势,非常适合服务间通讯交互.本文针对了dotnet core平台 ...

  9. Java深层复制方式

    为什么需要深层复制 Object 的 clone() 方法是浅层复制(但是 native 很高效).另外,Java 提供了数组和集合的复制方法,分别是 Arrays.copy() 和 Collecti ...

  10. bzoj 2001 CITY 城市建设 cdq分治

    题目传送门 题解: 对整个修改的区间进行分治.对于当前修改区间来说,我们对整幅图中将要修改的边权都先改成-inf,跑一遍最小生成树,然后对于一条树边并且他的权值不为-inf,那么这条边一定就是树边了. ...