Master选举
今年的第一篇就写写Master的选举吧,在之前的工作经历中,我们经常碰到Master选举问题。
比如Hbase、Hadoop的Master选举,都是通过Zookeeper来完成的,这几年比较流行K8s,
很多组件的选举是通过ETCD来完成的。说到这需要补充一点背景知识,那就是Paxos和Raft,
这里就不详细陈述了,自行搜索。
通过Redis选举
不是任何时候我们都能使用ZK、ETCD来完成选举的,比如部署规模的限制。最近碰到了一个场景,
需要进行Master的选举,但是部署规模非常小,无法引入其他组件来辅助,项目中有使用到Redis,
于是开发了一个基于Redis的简易选举方案。
如何利用Redis选举
网上有很多文章介绍,大体都是基于Redis的SetN指令的特性,进行一个分布式锁的争抢过程,
抢到锁的即为Master。如果你使用过Redission的客户端,可以直接使用Rlock来完成这个工作。
Redission中大量的封装了LuaScript,来完成一些事务性的工作。
利用Redis选举的弊端
为了性能考量,在写入时只要主节点成功,就会返回True。所以在故障时,主从可能会不一致。
在抢锁这个场景中,也就是会短暂出现两把锁。当然通过结构上的设计是可以尽量降低这个概率的,
但无法彻底消除。
选举的大致逻辑
1、 无锁时,争抢创建锁
2、 有锁时,没有抢到锁的节点,观察等待锁消失
3、 有锁时,抢到锁的节点,延续锁的TTL
代码示例
while (true) {
try {
RBucket<String> currentLock = this.redis.getBucket(this.lockName, StringCodec.INSTANCE);
String currentLockValue = currentLock.get();
if (StringUtils.isBlank(currentLockValue)) {
if (log.isDebugEnabled()) {
log.debug("start election .");
}
boolean success = currentLock.trySet(this.deviceId, DURATION_TIME, TimeUnit.SECONDS);
if (log.isDebugEnabled()) {
log.debug("election result : " + success);
}
update(success);
Util.sleepSec(10);
} else {
long remainTime = currentLock.remainTimeToLive();
if (remainTime <= 0) {
continue;
}
String tValue = currentLock.get();
if (!currentLockValue.equals(tValue)) {
continue;
}
if (this.deviceId.equals(currentLockValue)) {
if (log.isDebugEnabled()) {
log.debug("I am master .");
}
if (remainTime >= TimeUnit.SECONDS.toMillis(DISPUTE_TIME)) {
currentLock.expire(DURATION_TIME, TimeUnit.SECONDS);
if (log.isDebugEnabled()) {
log.debug("renew lock ttl .");
}
update(true);
} else {
update(false);
if (log.isDebugEnabled()) {
log.debug("unstable .");
}
}
Util.sleepSec(10);
} else {
update(false);
if (log.isDebugEnabled()) {
log.debug("wait for next time .");
}
Util.sleepMS(remainTime);
}
}
} catch (Throwable e) {
log.error("Election Error : ", e);
Util.sleepSec(1);
}
}