今年的第一篇就写写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);
            }
        }