zookeeper leader选举原理
leaderLatch 和leaderSelector
分布式任务调度系统往往是一个服务集群,但是为了防止任务重复执行,通常只有一个leader去任务池里取任务,
leaderLatch和leaderSelector 就是Curator基于zookeepe封装的leader选举工具类。
LeaderLatch 原理是利用临时有序节点,最先创建的序号最小的节点成为leader节点
LeaderSelector利用Curator中InterProcessMutex分布式锁进行抢主,抢到锁的即为Leader
leaderLatch和Selector 看起来原理都是利用分布式锁原理,区别在于,selector中leader丢失leader之后会重新
进入leader争夺,即在这个目录下再创建一个临时节点,等待。
代码实现
leader节点去执行定时任务,不是leader则不执行定时任务
定时任务
普通的定时任务
@Configuration
public class QuartzConfig
{
// leaderLatch 选举
// @Bean
// public ZkSchedulerFactoryBean schedulerFactoryBean() throws Exception {
// ZkSchedulerFactoryBean schedulerFactoryBean=new ZkSchedulerFactoryBean();
// schedulerFactoryBean.setJobDetails(jobDetail());
// schedulerFactoryBean.setTriggers(trigger());
// return schedulerFactoryBean;
// }
@Bean
public JobDetail jobDetail()
{
return JobBuilder.newJob(QuartzJob.class).storeDurably().build();
}
@Bean
public Trigger trigger()
{
SimpleScheduleBuilder simpleScheduleBuilder = SimpleScheduleBuilder.simpleSchedule().
withIntervalInSeconds(1).repeatForever();
return TriggerBuilder.newTrigger().forJob(jobDetail()).
withSchedule(simpleScheduleBuilder).build();
}
}
detailJob
public class QuartzJob extends QuartzJobBean{
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println("[QuartzJob]-----:"+
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
}
}
public class ZkSchedulerFactoryBean extends SchedulerFactoryBean
{
private static CuratorFramework zkClient;
private static String ZOOKEEPER_CONNECTION_STRING = "localhost:2181";
private LeaderLatch leaderLatch; //leader选举的api
private static final String LEADER_PATH = "/leader";
Logger LOG = LoggerFactory.getLogger(ZkSchedulerFactoryBean.class);
public ZkSchedulerFactoryBean() throws Exception
{
this.setAutoStartup(false); //设置为非自动启动
leaderLatch = new LeaderLatch(getClient(), LEADER_PATH);
leaderLatch.addListener(new ZkJobLeaderLatchListener(getIp(), this));
leaderLatch.start(); //表示当前节点参与到leader选举中来
}
@Override
protected void startScheduler(Scheduler scheduler, int startupDelay) throws SchedulerException
{
if (this.isAutoStartup())
{//默认情况下,是true
super.startScheduler(scheduler, startupDelay);
}
}
@Override
public void destroy() throws SchedulerException
{
CloseableUtils.closeQuietly(leaderLatch);
super.destroy();
}
//初始化连接
private CuratorFramework getClient()
{
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
zkClient = CuratorFrameworkFactory.builder().
connectString(ZOOKEEPER_CONNECTION_STRING).retryPolicy(retryPolicy).build();
zkClient.start();
return zkClient;
}
private String getIp()
{
String host = null;
try
{
host = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e)
{
e.printStackTrace();
}
return host;
}
class ZkJobLeaderLatchListener implements LeaderLatchListener
{
private String ip;
private SchedulerFactoryBean schedulerFactoryBean;
public ZkJobLeaderLatchListener(String ip)
{
this.ip = ip;
}
public ZkJobLeaderLatchListener(String ip, SchedulerFactoryBean schedulerFactoryBean)
{
this.ip = ip;
this.schedulerFactoryBean = schedulerFactoryBean;
}
@Override
public void isLeader()
{
LOG.info("ip:{} 成为leader,执行scheduler~", ip);
schedulerFactoryBean.setAutoStartup(true);
schedulerFactoryBean.start(); //启动(抢占到leader的节点去执行任务)
}
@Override
public void notLeader()
{
LOG.info("ip:{} 不是leader,停止scheduler~", ip);
schedulerFactoryBean.setAutoStartup(false);
schedulerFactoryBean.stop(); //启动(抢占到leader的节点去执行任务)
}
}
}
起两个客户端
两个客户端都会执行定时任务,打印时间
当ZkSchedulerFactoryBean 注解放开的时候, 两个客户端只有一个会打印时间,当停止已经获得leader的客户端时
另一个没有获得leader会开始执行定时任务打印时间。
zookeeper 一致性
zk是一种分布式系统,他的高可用性需要采取分布式模式,那也必须遵循CAP理论
zk是一个弱一致性模型
他的数据同步是2pc协议,过半follow提交即可
zk是顺序一致性模型,
由于zookeeper设计出来是提供分布式锁服务,那么意味着它本身需要实现顺序一致性
zk从以下几点上保证的数据的一致性
1、顺序一致性
任意的更新都会被按顺序提交;某个值改成A,后面又改成B,则客户端在读取到B之后就不会再读到A了
2、原子性
更新要么成功,要么失败;如果更新失败,不会有客户端看到失败的结果
3、单一系统映像
一个客户端无论连接到哪一台服务器,它看到的都是同样的系统视图
4、持久性
一个更新一旦成功提交,其结果就持久存在且不会被撤销
ZAB协议
zookeeper通过ZAB协议来保持事务的最终一致性
zk中所有的写请求都是leader进程操作的,再由leadler同步到follow,在数据同步过程中,如果leader故障了,会过
zab协议来保持数据一致性。
ZAB(Zookeeper Atomic Broadcast) 协议是为分布式协调服务 ZooKeeper 专门设计的一种支持崩溃
恢复的原子广播协议。在 ZooKeeper 中,主要依赖 ZAB 协议来实现分布式数据一致性,基于该协议,
ZooKeeper 实现了一种主备模式的系统架构来保持集群中各个副本之间的数据一致性。
zab协议又可以分为两部分:消息广播阶段、崩溃恢复阶段。
消息广播
leader节点接受事务请求,并将新的proposal(提议)请求广播给follower,收集各个节点反馈,再决定是否提交
1、事务请求进来后,leader将事务包装成proposal事务,zxid递增加1
2、广播proposal事务
3、follower接收到proposal,持久化到本地磁盘,完全写入后,发送ack给leader
4、leader收到半数follower的ack,会提交本机的事务,并广播commit,follower收到commit后,提交各自事务
崩溃恢复
leader节点宕机后,重新选举leader,并同步最新的数据,保持数据一致性。
下面的几种情况都会进入崩溃恢复阶段:
1、集群启动时
2、leader挂了
3、leader跟半数集群节点失去连接
崩溃恢复时数据同步的原则是:
1、已经被处理的消息不能丢失
2、被丢弃的消息不能再次出现
模式切换
zk集群的一致性保证就是在两个模式之间切换,leader正常时就是消息广播模式,leader挂了时就进入崩溃恢复模式,选举leader、同步最新数据之后,又进入广播模式。
Zxid
zxid是事务编码,是64位的数字,高32位是epoch(leader周期),低32位是事务递增id(每个事务请求就加1,重新选举leader后重置为0)
节点状态
zab中节点有三种状态:
1、leadering
2、following
3、election/looking 选举状态
Zab 与 Paxos 算法的联系与区别
上面讲了zab协议,这里对比一下paxos算法
zab协议是基于paxos算法实现的,看下联系:
1、都存在一个leader进程的角色,负责协调多个follower运行
2、都应用 Quorum 机制,leader在超半数的follower做出正确的反馈之后,在将一个提案提交
3、在zab协议中,zxid的epoch当leader周期,paxos算法中,也有这样一个标识, Ballot (投票) Numbe
区别是:
paxos是理论,zab是实践,
zab协议增加了崩溃恢复的功能
leader选举过程
集群启动时选举leader
集群启动时,每个节点都是looking状态,leader选举至少是2台
1、serv1启动时单机无法选举,serv2也启动了,才开始选举
2、每个服务器发出投票。serv1、serv2都投自己,投票信息包含myid、zxid、epoch,格式是(myid,zxid,epoch),将这个投票发给集群中的其他机器
3、接受其他服务器的投票。集群每个服务器接收到投票后,检查是否有效:是否本轮epoch、是否是looking
4、处理投票。针对每一个投票,服务器都要把别人的投票和自己的PK,PK规则:
i. 优先比较epoch
ii. 其次检查ZXID。ZXID比较大的服务器优先作为Leader
iii. 如果ZXID相同,那么就比较myid。myid较大的服务器作为Leader服务器。
比较到比自己大的,更新自己的投票信息,再投票。
5、统计投票。每次投票后,服务器都会统计投票数,选出超半数投的节点
6、改变服务器状态。确定了leader,每个服务器就更新自己的状态,变成follower或者leader。
leader宕机是选举leader
1、leader节点挂了之后,除了Observer节点不参加选举之外,所有的follower节点变成 Looking,开始选举
2、第一次投票,每个节点投自己,然后将各自的投票发给集群中所有节点,在运行期间,每个zxid大概率不同
3、集群接受各个服务器的投票,开始比对zxid,比对的依据是:
1、epoch
2、zxid 事务id 低32位值
3、集群zoo.cfg中的yid,最大的yid优先
在选举中有节点获得超半数节点的投票数,则成为新的leader