一、前言

  前面阐述了服务器的总体框架,下面来分析服务器的所有父类ZooKeeperServer。

二、ZooKeeperServer源码分析

  2.1 类的继承关系 

public class ZooKeeperServer implements SessionExpirer, ServerStats.Provider {}

  说明:ZooKeeperServer是ZooKeeper中所有服务器的父类,其实现了Session.Expirer和ServerStats.Provider接口,SessionExpirer中定义了expire方法(表示会话过期)和getServerId方法(表示获取服务器ID),而Provider则主要定义了获取服务器某些数据的方法。

  2.2 类的内部类

  1. DataTreeBuilder类

    public interface DataTreeBuilder {
// 构建DataTree
public DataTree build();
}

  说明:其定义了构建树DataTree的接口。

  2. BasicDataTreeBuilder类  

    static public class BasicDataTreeBuilder implements DataTreeBuilder {
public DataTree build() {
return new DataTree();
}
}

  说明:实现DataTreeBuilder接口,返回新创建的树DataTree。

  3. MissingSessionException类  

    public static class MissingSessionException extends IOException {
private static final long serialVersionUID = 7467414635467261007L; public MissingSessionException(String msg) {
super(msg);
}
}

  说明:表示会话缺失异常。

  4. ChangeRecord类 

    static class ChangeRecord {
ChangeRecord(long zxid, String path, StatPersisted stat, int childCount,
List<ACL> acl) {
// 属性赋值
this.zxid = zxid;
this.path = path;
this.stat = stat;
this.childCount = childCount;
this.acl = acl;
} // zxid
long zxid; // 路径
String path; // 统计数据
StatPersisted stat; /* Make sure to create a new object when changing */ // 子节点个数
int childCount; // ACL列表
List<ACL> acl; /* Make sure to create a new object when changing */ @SuppressWarnings("unchecked")
// 拷贝
ChangeRecord duplicate(long zxid) {
StatPersisted stat = new StatPersisted();
if (this.stat != null) {
DataTree.copyStatPersisted(this.stat, stat);
}
return new ChangeRecord(zxid, path, stat, childCount,
acl == null ? new ArrayList<ACL>() : new ArrayList(acl));
}
}

  说明:ChangeRecord数据结构是用于方便PrepRequestProcessor和FinalRequestProcessor之间进行信息共享,其包含了一个拷贝方法duplicate,用于返回属性相同的ChangeRecord实例。

  2.3 类的属性  

public class ZooKeeperServer implements SessionExpirer, ServerStats.Provider {
// 日志器
protected static final Logger LOG; static {
// 初始化日志器
LOG = LoggerFactory.getLogger(ZooKeeperServer.class); Environment.logEnv("Server environment:", LOG);
}
// JMX服务
protected ZooKeeperServerBean jmxServerBean;
protected DataTreeBean jmxDataTreeBean; // 默认心跳频率
public static final int DEFAULT_TICK_TIME = 3000;
protected int tickTime = DEFAULT_TICK_TIME;
/** value of -1 indicates unset, use default */
// 最小会话过期时间
protected int minSessionTimeout = -1;
/** value of -1 indicates unset, use default */
// 最大会话过期时间
protected int maxSessionTimeout = -1;
// 会话跟踪器
protected SessionTracker sessionTracker;
// 事务日志快照
private FileTxnSnapLog txnLogFactory = null;
// Zookeeper内存数据库
private ZKDatabase zkDb;
//
protected long hzxid = 0;
// 异常
public final static Exception ok = new Exception("No prob");
// 请求处理器
protected RequestProcessor firstProcessor;
// 运行标志
protected volatile boolean running; /**
* This is the secret that we use to generate passwords, for the moment it
* is more of a sanity check.
*/
// 生成密码的密钥
static final private long superSecret = 0XB3415C00L; //
int requestsInProcess; // 未处理的ChangeRecord
final List<ChangeRecord> outstandingChanges = new ArrayList<ChangeRecord>(); // this data structure must be accessed under the outstandingChanges lock
// 记录path对应的ChangeRecord
final HashMap<String, ChangeRecord> outstandingChangesForPath =
new HashMap<String, ChangeRecord>(); // 连接工厂
private ServerCnxnFactory serverCnxnFactory; // 服务器统计数据
private final ServerStats serverStats;
}

类的属性

  说明:类中包含了心跳频率,会话跟踪器(处理会话)、事务日志快照、内存数据库、请求处理器、未处理的ChangeRecord、服务器统计信息等。

  2.4 类的构造函数

  1. ZooKeeperServer()型构造函数  

    public ZooKeeperServer() {
serverStats = new ServerStats(this);
}

  说明:其只初始化了服务器的统计信息。

  2. ZooKeeperServer(FileTxnSnapLog, int, int, int, DataTreeBuilder, ZKDatabase)型构造函数  

    public ZooKeeperServer(FileTxnSnapLog txnLogFactory, int tickTime,
int minSessionTimeout, int maxSessionTimeout,
DataTreeBuilder treeBuilder, ZKDatabase zkDb) {
// 给属性赋值
serverStats = new ServerStats(this);
this.txnLogFactory = txnLogFactory;
this.zkDb = zkDb;
this.tickTime = tickTime;
this.minSessionTimeout = minSessionTimeout;
this.maxSessionTimeout = maxSessionTimeout; LOG.info("Created server with tickTime " + tickTime
+ " minSessionTimeout " + getMinSessionTimeout()
+ " maxSessionTimeout " + getMaxSessionTimeout()
+ " datadir " + txnLogFactory.getDataDir()
+ " snapdir " + txnLogFactory.getSnapDir());
}

  说明:该构造函数会初始化服务器统计数据、事务日志工厂、心跳时间、会话时间(最短超时时间和最长超时时间)。

  3. ZooKeeperServer(FileTxnSnapLog, int, DataTreeBuilder)型构造函数  

    public ZooKeeperServer(FileTxnSnapLog txnLogFactory, int tickTime,
DataTreeBuilder treeBuilder) throws IOException {
this(txnLogFactory, tickTime, -1, -1, treeBuilder,
new ZKDatabase(txnLogFactory));
}

  说明:其首先会生成ZooKeeper内存数据库后,然后调用第二个构造函数进行初始化操作。

  4. ZooKeeperServer(File, File, int)型构造函数 

    public ZooKeeperServer(File snapDir, File logDir, int tickTime)
throws IOException {
this( new FileTxnSnapLog(snapDir, logDir),
tickTime, new BasicDataTreeBuilder());
}

  说明:其会调用同名构造函数进行初始化操作。

  5. ZooKeeperServer(FileTxnSnapLog, DataTreeBuilder)型构造函数  

    public ZooKeeperServer(FileTxnSnapLog txnLogFactory,
DataTreeBuilder treeBuilder)
throws IOException
{
this(txnLogFactory, DEFAULT_TICK_TIME, -1, -1, treeBuilder,
new ZKDatabase(txnLogFactory));
}

  说明:其生成内存数据库之后再调用同名构造函数进行初始化操作。

  2.5 核心函数分析

  1. loadData函数 

    public void loadData() throws IOException, InterruptedException {
/*
* When a new leader starts executing Leader#lead, it
* invokes this method. The database, however, has been
* initialized before running leader election so that
* the server could pick its zxid for its initial vote.
* It does it by invoking QuorumPeer#getLastLoggedZxid.
* Consequently, we don't need to initialize it once more
* and avoid the penalty of loading it a second time. Not
* reloading it is particularly important for applications
* that host a large database.
*
* The following if block checks whether the database has
* been initialized or not. Note that this method is
* invoked by at least one other method:
* ZooKeeperServer#startdata.
*
* See ZOOKEEPER-1642 for more detail.
*/
if(zkDb.isInitialized()){ // 内存数据库已被初始化
// 设置为最后处理的Zxid
setZxid(zkDb.getDataTreeLastProcessedZxid());
}
else { // 未被初始化,则加载数据库
setZxid(zkDb.loadDataBase());
} // Clean up dead sessions
LinkedList<Long> deadSessions = new LinkedList<Long>();
for (Long session : zkDb.getSessions()) { // 遍历所有的会话
if (zkDb.getSessionWithTimeOuts().get(session) == null) { // 删除过期的会话
deadSessions.add(session);
}
}
// 完成DataTree的初始化
zkDb.setDataTreeInit(true);
for (long session : deadSessions) { // 遍历过期会话
// XXX: Is lastProcessedZxid really the best thing to use?
// 删除会话
killSession(session, zkDb.getDataTreeLastProcessedZxid());
}
}

  说明:该函数用于加载数据,其首先会判断内存库是否已经加载设置zxid,之后会调用killSession函数删除过期的会话,killSession会从sessionTracker中删除session,并且killSession最后会调用DataTree的killSession函数,其源码如下 

    void killSession(long session, long zxid) {
// the list is already removed from the ephemerals
// so we do not have to worry about synchronizing on
// the list. This is only called from FinalRequestProcessor
// so there is no need for synchronization. The list is not
// changed here. Only create and delete change the list which
// are again called from FinalRequestProcessor in sequence.
// 移除session,并获取该session对应的所有临时节点
HashSet<String> list = ephemerals.remove(session);
if (list != null) {
for (String path : list) { // 遍历所有临时节点
try {
// 删除路径对应的节点
deleteNode(path, zxid);
if (LOG.isDebugEnabled()) {
LOG
.debug("Deleting ephemeral node " + path
+ " for session 0x"
+ Long.toHexString(session));
}
} catch (NoNodeException e) {
LOG.warn("Ignoring NoNodeException for path " + path
+ " while removing ephemeral for dead session 0x"
+ Long.toHexString(session));
}
}
}
}

  说明:DataTree的killSession函数的逻辑首先移除session,然后取得该session下的所有临时节点,然后逐一删除临时节点。

  2. submit函数 

    public void submitRequest(Request si) {
if (firstProcessor == null) { // 第一个处理器为空
synchronized (this) {
try {
while (!running) { // 直到running为true,否则继续等待
wait(1000);
}
} catch (InterruptedException e) {
LOG.warn("Unexpected interruption", e);
}
if (firstProcessor == null) {
throw new RuntimeException("Not started");
}
}
}
try {
touch(si.cnxn);
// 是否为合法的packet
boolean validpacket = Request.isValid(si.type);
if (validpacket) {
// 处理请求
firstProcessor.processRequest(si);
if (si.cnxn != null) {
incInProcess();
}
} else {
LOG.warn("Received packet at server of unknown type " + si.type);
new UnimplementedRequestProcessor().processRequest(si);
}
} catch (MissingSessionException e) {
if (LOG.isDebugEnabled()) {
LOG.debug("Dropping request: " + e.getMessage());
}
} catch (RequestProcessorException e) {
LOG.error("Unable to process request:" + e.getMessage(), e);
}
}

  说明:当firstProcessor为空时,并且running标志为false时,其会一直等待,直到running标志为true,之后调用touch函数判断session是否存在或者已经超时,之后判断请求的类型是否合法,合法则使用请求处理器进行处理。

  3. processConnectRequest函数  

    public void processConnectRequest(ServerCnxn cnxn, ByteBuffer incomingBuffer) throws IOException {
BinaryInputArchive bia = BinaryInputArchive.getArchive(new ByteBufferInputStream(incomingBuffer));
ConnectRequest connReq = new ConnectRequest();
// 反序列化
connReq.deserialize(bia, "connect");
if (LOG.isDebugEnabled()) {
LOG.debug("Session establishment request from client "
+ cnxn.getRemoteSocketAddress()
+ " client's lastZxid is 0x"
+ Long.toHexString(connReq.getLastZxidSeen()));
}
boolean readOnly = false;
try {
// 是否为只读
readOnly = bia.readBool("readOnly");
cnxn.isOldClient = false;
} catch (IOException e) {
// this is ok -- just a packet from an old client which
// doesn't contain readOnly field
LOG.warn("Connection request from old client "
+ cnxn.getRemoteSocketAddress()
+ "; will be dropped if server is in r-o mode");
}
if (readOnly == false && this instanceof ReadOnlyZooKeeperServer) { // 为只读模式但是该服务器是只读服务器,抛出异常
String msg = "Refusing session request for not-read-only client "
+ cnxn.getRemoteSocketAddress();
LOG.info(msg);
throw new CloseRequestException(msg);
}
if (connReq.getLastZxidSeen() > zkDb.dataTree.lastProcessedZxid) { // 请求连接的zxid大于DataTree处理的最大的zxid,抛出异常
String msg = "Refusing session request for client "
+ cnxn.getRemoteSocketAddress()
+ " as it has seen zxid 0x"
+ Long.toHexString(connReq.getLastZxidSeen())
+ " our last zxid is 0x"
+ Long.toHexString(getZKDatabase().getDataTreeLastProcessedZxid())
+ " client must try another server"; LOG.info(msg);
throw new CloseRequestException(msg);
}
// 获取超时时间
int sessionTimeout = connReq.getTimeOut();
// 获取密码
byte passwd[] = connReq.getPasswd();
// 获取最短超时时间
int minSessionTimeout = getMinSessionTimeout();
if (sessionTimeout < minSessionTimeout) {
sessionTimeout = minSessionTimeout;
}
// 获取最长超时时间
int maxSessionTimeout = getMaxSessionTimeout();
if (sessionTimeout > maxSessionTimeout) {
sessionTimeout = maxSessionTimeout;
}
// 设置超时时间
cnxn.setSessionTimeout(sessionTimeout);
// We don't want to receive any packets until we are sure that the
// session is setup
// 不接收任何packet,直到会话创建成功
cnxn.disableRecv();
// 获取会话id
long sessionId = connReq.getSessionId();
if (sessionId != 0) { // 表示重新创建会话
long clientSessionId = connReq.getSessionId();
LOG.info("Client attempting to renew session 0x"
+ Long.toHexString(clientSessionId)
+ " at " + cnxn.getRemoteSocketAddress());
// 关闭会话
serverCnxnFactory.closeSession(sessionId);
// 设置会话id
cnxn.setSessionId(sessionId);
// 重新打开会话
reopenSession(cnxn, sessionId, passwd, sessionTimeout);
} else {
LOG.info("Client attempting to establish new session at "
+ cnxn.getRemoteSocketAddress());
// 创建会话
createSession(cnxn, passwd, sessionTimeout);
}
}

processConnectRequest

  说明:其首先将传递的ByteBuffer进行反序列化,转化为相应的ConnectRequest,之后进行一系列判断(可能抛出异常),然后获取并判断该ConnectRequest中会话id是否为0,若为0,则表示可以创建会话,否则,重新打开会话。

  4. processPacket函数 

    public void processPacket(ServerCnxn cnxn, ByteBuffer incomingBuffer) throws IOException {
// We have the request, now process and setup for next
InputStream bais = new ByteBufferInputStream(incomingBuffer);
BinaryInputArchive bia = BinaryInputArchive.getArchive(bais);
// 创建请求头
RequestHeader h = new RequestHeader();
// 将头反序列化为RequestHeader
h.deserialize(bia, "header");
// Through the magic of byte buffers, txn will not be
// pointing
// to the start of the txn
incomingBuffer = incomingBuffer.slice();
if (h.getType() == OpCode.auth) { // 需要进行认证(有密码)
LOG.info("got auth packet " + cnxn.getRemoteSocketAddress());
AuthPacket authPacket = new AuthPacket();
// 将ByteBuffer转化为AuthPacket
ByteBufferInputStream.byteBuffer2Record(incomingBuffer, authPacket);
// 获取AuthPacket的模式
String scheme = authPacket.getScheme();
AuthenticationProvider ap = ProviderRegistry.getProvider(scheme);
Code authReturn = KeeperException.Code.AUTHFAILED;
if(ap != null) {
try {
// 进行认证
authReturn = ap.handleAuthentication(cnxn, authPacket.getAuth());
} catch(RuntimeException e) {
LOG.warn("Caught runtime exception from AuthenticationProvider: " + scheme + " due to " + e);
authReturn = KeeperException.Code.AUTHFAILED;
}
}
if (authReturn!= KeeperException.Code.OK) { // 认证失败
if (ap == null) {
LOG.warn("No authentication provider for scheme: "
+ scheme + " has "
+ ProviderRegistry.listProviders());
} else {
LOG.warn("Authentication failed for scheme: " + scheme);
}
// send a response...
// 构造响应头
ReplyHeader rh = new ReplyHeader(h.getXid(), 0,
KeeperException.Code.AUTHFAILED.intValue());
// 发送响应
cnxn.sendResponse(rh, null, null);
// ... and close connection
// 关闭连接的信息
cnxn.sendBuffer(ServerCnxnFactory.closeConn);
// 不接收任何packet
cnxn.disableRecv();
} else { // 认证成功
if (LOG.isDebugEnabled()) {
LOG.debug("Authentication succeeded for scheme: "
+ scheme);
}
LOG.info("auth success " + cnxn.getRemoteSocketAddress());
// 构造响应头
ReplyHeader rh = new ReplyHeader(h.getXid(), 0,
KeeperException.Code.OK.intValue());
// 发送响应
cnxn.sendResponse(rh, null, null);
}
return;
} else {
if (h.getType() == OpCode.sasl) { // 为SASL类型
// 处理SASL
Record rsp = processSasl(incomingBuffer,cnxn);
// 构造响应头
ReplyHeader rh = new ReplyHeader(h.getXid(), 0, KeeperException.Code.OK.intValue());
// 发送响应
cnxn.sendResponse(rh,rsp, "response"); // not sure about 3rd arg..what is it?
}
else { // 不为SASL类型
// 创建请求
Request si = new Request(cnxn, cnxn.getSessionId(), h.getXid(),
h.getType(), incomingBuffer, cnxn.getAuthInfo());
// 设置请求所有者
si.setOwner(ServerCnxn.me);
// 提交请求
submitRequest(si);
}
}
//
cnxn.incrOutstandingRequests(h);
}

processPacket

  说明:该函数首先将传递的ByteBuffer进行反序列,转化为相应的RequestHeader,然后根据该RequestHeader判断是否需要认证,若认证失败,则构造认证失败的响应并发送给客户端,然后关闭连接,并且再补接收任何packet。若认证成功,则构造认证成功的响应并发送给客户端。若不需要认证,则再判断其是否为SASL类型,若是,则进行处理,然后构造响应并发送给客户端,否则,构造请求并且提交请求。

三、总结

  本篇分析了ZooKeeperServer的源码,了解了其对于请求和会话的处理,也谢谢各位园友的观看~

【Zookeeper】源码分析之服务器(二)之ZooKeeperServer的更多相关文章

  1. zookeeper源码分析之四服务端(单机)处理请求流程

    上文: zookeeper源码分析之一服务端启动过程 中,我们介绍了zookeeper服务器的启动过程,其中单机是ZookeeperServer启动,集群使用QuorumPeer启动,那么这次我们分析 ...

  2. zookeeper源码分析之三客户端发送请求流程

    znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个功能是zookeeper对于应用最重要的特性,通过这个特性可以实现的功能包括配置的 ...

  3. ZooKeeper源码阅读——client(二)

    原创技术文章,转载请注明:转自http://newliferen.github.io/ 如何连接ZooKeeper集群   要想了解ZooKeeper客户端实现原理,首先需要关注一下客户端的使用方式, ...

  4. zookeeper源码分析之五服务端(集群leader)处理请求流程

    leader的实现类为LeaderZooKeeperServer,它间接继承自标准ZookeeperServer.它规定了请求到达leader时需要经历的路径: PrepRequestProcesso ...

  5. Zookeeper 源码分析-启动

    Zookeeper 源码分析-启动 博客分类: Zookeeper   本文主要介绍了zookeeper启动的过程 运行zkServer.sh start命令可以启动zookeeper.入口的main ...

  6. 手机自动化测试:appium源码分析之bootstrap二

    手机自动化测试:appium源码分析之bootstrap二   在bootstrap项目中的io.appium.android.bootstrap.handler包中的类都是对应的指令类, priva ...

  7. 【Zookeeper】源码分析之服务器(二)

    一.前言 前面阐述了服务器的总体框架,下面来分析服务器的所有父类ZooKeeperServer. 二.ZooKeeperServer源码分析 2.1 类的继承关系 public class ZooKe ...

  8. 【Zookeeper】源码分析之服务器(四)

    一.前言 前面分析了LeaderZooKeeperServer,接着分析FollowerZooKeeperServer. 二.FollowerZooKeeperServer源码分析 2.1 类的继承关 ...

  9. 【Zookeeper】源码分析之服务器(五)之ObserverZooKeeperServer

    一.前言 前面分析了FollowerZooKeeperServer,接着分析ObserverZooKeeperServer. 二.ObserverZooKeeperServer源码分析 2.1 类的继 ...

随机推荐

  1. 步步为营-23-通过GridView实现增删改

    说明:把xml中的数据放入到数据源list中然后显示到gridview中,参考上一节内容 1 UI页面 2创建student类 public class Student { public int ID ...

  2. 把A表的多个字段更新到B表

    sqlServer中可用 update A set A.sex = B.sex, A.na=B.na from A,B where A.id = B.id mysql没试,应该也可以 Mysql版本 ...

  3. linux安装目录

    Linux 的软件安装目录是也是有讲究的,理解这一点,在对系统管理是有益的 /usr:系统级的目录,可以理解为C:/Windows/,/usr/lib理解为C:/Windows/System32./u ...

  4. BZOJ4994 [Usaco2017 Feb]Why Did the Cow Cross the Road III 树状数组

    欢迎访问~原文出处——博客园-zhouzhendong 去博客园看该题解 题目传送门 - BZOJ4994 题意概括 给定长度为2N的序列,1~N各处现过2次,i第一次出现位置记为ai,第二次记为bi ...

  5. 2.Django|简介与静态文件| URL控制器

    1.简介  MVC Web服务器开发领域里著名的MVC模式,所谓MVC就是把Web应用分为模型(M),控制器(C)和视图(V)三层,他们之间以一种插件式的.松耦合的方式连接在一起,模型负责业务对象与数 ...

  6. 067 Flume协作框架

    一:介绍 1.概述 ->flume的三大功能 collecting, aggregating, and moving 收集 聚合 移动 数据源:web service              ...

  7. HDU 2844 Coins 【多重背包】(模板)

    <题目连接> 题目大意: 一位同学想要买手表,他有n种硬币,每种硬币已知有num[i]个.已知手表的价钱最多m元,问她用这些钱能够凑出多少种价格来买手表. 解题分析: 很明显,这是一道多重 ...

  8. Linux学习之分区自动挂载与fstab文件修复(九)

    linux分区自动挂载与fstab文件修复 在前面我们实现新添加硬盘,进行分区与格式化,然后手动挂载,这样做,在重启后,需要重新挂载才能使用. https://www.cnblogs.com/-wen ...

  9. P2326 AKN’s PPAP

    P2326 AKN’s PPAP比较裸的贪心从高位向下枚举,如果当前位为1的个数大于1,ans+=(1<<i),然后从这些数中再向下枚举. #include<iostream> ...

  10. js基础梳理-内存空间

    我估计有很多像我这样非计算机专业的人进入到前端之后,总是在写业务代码,思考什么什么效果如何实现,导致很多基础概念型的东西都理解得并不太清楚.经常一碰到群里讨论的些笔试题什么的,总觉得自己像是一个假前端 ...