zookeeper集群角色
- leader:集群leader,负责处理事务请求,处理查询请求
- follower:集群follower,同步leader节点数据,转化事务请求到leader,处理查询请求,参与选举投票
- observer:与follower不同于,不参与选举的投票
myid
集群节点id属性
epoch
在每一轮投票递增,记录投票的”朝代“。
zxid(事务id)
leader节点在每次事务操作后都会递增zxid,follower节点同步leader的zxid,保证节点数据与leader节点数据一致。
节点的状态
- looking:选举阶段
- following:follower节点
- observering:observer节点,不参与选举
- leading:leader节点
leader选举选票的判断顺序
每一轮选举都会发送 myid/zxid/epoch
- epoch最大
- zxid最大
- myid最大
- 当某个节点的得票数大于集群节点数一半则成为leader
leader的选举过程
假设一个3个节点的集群,启动时节点状态为looking,这时每个节点的epoch为1,每个节点都投票自己为leader(1/zxid/1,2/zxid/1,3/zxid/1),每个节点都会收到其他节点的投票信息。这时epoch一致的情况下判断zxid,如果都一致,说明每个节点的数据都是最新的,则判断myid,修改自己节点的投票信息,以选票中的最大值修改。节点会保存有其他节点发送过来的选票,自己发送出去的投票信息。每一轮投票都递增epoch。每个节点保存自己的epoch。当节点收到其他节点发送过来的投票,判断epoch,相等则判断zxid,如果还相等再判断myid。而如果epoch不相等,则更新节点保存的自己的投票信息为对方的投票信息,并保存所有接收到的投票信息,发送自己新的选票。对接收到的投票信息进行归纳,判断接收到的选票有超过一般的选票与自己刚刚更新保存的选票信息相同的leader,则选择该节点为leader,否则继续投票。如果节点成为了follower,则需要设置节点状态为follower,再链接leader节点,再同步数据。每个节点有一个队列保存其他节点发送过来的投票信息,当还没选举出leader时,不断循环判断选票信息,直到有新的选票信息比当前保存的还有大。然后不断循环直到选出leader。
源码导读
基于3.5.5版本首先从zk服务器的入口开始,查看zkServer.sh脚本,其中启动关键的命令为
//1
ZOOMAIN="org.apache.zookeeper.server.quorum.QuorumPeerMain"
//2
nohup "$JAVA" $ZOO_DATADIR_AUTOCREATE "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" \
"-Dzookeeper.log.file=${ZOO_LOG_FILE}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" \
-XX:+HeapDumpOnOutOfMemoryError -XX:OnOutOfMemoryError='kill -9 %p' \
-cp "$CLASSPATH" $JVMFLAGS $ZOOMAIN "$ZOOCFG" > "$_ZOO_DAEMON_OUT" 2>&1 < /dev/null &
那么可以知道org.apache.zookeeper.server.quorum.QuorumPeerMain
为启动的入口
@InterfaceAudience.Public
public class QuorumPeerMain {
private static final Logger LOG = LoggerFactory.getLogger(QuorumPeerMain.class);
private static final String USAGE = "Usage: QuorumPeerMain configfile";
protected QuorumPeer quorumPeer;
public static void main(String[] args) {
QuorumPeerMain main = new QuorumPeerMain();
try {
main.initializeAndRun(args);
} catch (IllegalArgumentException e) {
//
}
//...省略代码
}
protected void initializeAndRun(String[] args)
throws ConfigException, IOException, AdminServerException
{
QuorumPeerConfig config = new QuorumPeerConfig();
if (args.length == 1) {
// 解析配置文件
config.parse(args[0]);
}
// ....
if (args.length == 1 && config.isDistributed()) {
runFromConfig(config);
} else {
LOG.warn("Either no config or no quorum defined in config, running "
+ " in standalone mode");
// there is only server in the quorum -- run as standalone
ZooKeeperServerMain.main(args);
}
}
public void runFromConfig(QuorumPeerConfig config)
throws IOException, AdminServerException
{
// ...
}
}
启动方法QuorumPeerMain #runFromConfig
public void runFromConfig(QuorumPeerConfig config)
throws IOException, AdminServerException
{
try {
// 节点通信连接相关
ServerCnxnFactory cnxnFactory = null;
ServerCnxnFactory secureCnxnFactory = null;
if (config.getClientPortAddress() != null) {
// 默认没有指定为 NIOServerCnxnFactory
cnxnFactory = ServerCnxnFactory.createFactory();
cnxnFactory.configure(config.getClientPortAddress(),
config.getMaxClientCnxns(),
false);
}
if (config.getSecureClientPortAddress() != null) {
secureCnxnFactory = ServerCnxnFactory.createFactory();
secureCnxnFactory.configure(config.getSecureClientPortAddress(),
config.getMaxClientCnxns(),
true);
}
// 节点参数的配置
quorumPeer = getQuorumPeer();
quorumPeer.setTxnFactory(new FileTxnSnapLog(
config.getDataLogDir(),
config.getDataDir()));
quorumPeer.enableLocalSessions(config.areLocalSessionsEnabled());
quorumPeer.enableLocalSessionsUpgrading(
config.isLocalSessionsUpgradingEnabled());
//quorumPeer.setQuorumPeers(config.getAllMembers());
// leader选举类型 默认为3,其他类型再源码中也已标记为过期
quorumPeer.setElectionType(config.getElectionAlg());
quorumPeer.setMyid(config.getServerId());
quorumPeer.setTickTime(config.getTickTime());
quorumPeer.setMinSessionTimeout(config.getMinSessionTimeout());
quorumPeer.setMaxSessionTimeout(config.getMaxSessionTimeout());
quorumPeer.setInitLimit(config.getInitLimit());
quorumPeer.setSyncLimit(config.getSyncLimi