zookeeper服务端
服务端启动时,就启动线程通过NIO监听网络端口。每个连接都会有一个上下文环境对象,当接收到请求后,会在上下文环境对象中进行处理。
服务端启动线程,监听网络端口,(NIOServerCnxn.Factory):
static public class Factory extends Thread {
static {
//设置全局的异常处理
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
public void uncaughtException(Thread t, Throwable e) {
LOG.error("Thread " + t + " died", e);
}
});
/**
* jvm早期的nullpoint bug
* http://bugs.sun.com/view_bug.do?bug_id=6427854
*/
try {
Selector.open().close();
} catch(IOException ie) {
LOG.error("Selector failed to open", ie);
}
}
//服务器通道
final ServerSocketChannel ss;
//选择器
final Selector selector = Selector.open();
ZooKeeperServer zks; final ByteBuffer directBuffer = ByteBuffer.allocateDirect(64 * 1024);
//所有的上下文环境
final HashSet<NIOServerCnxn> cnxns = new HashSet<NIOServerCnxn>();
//ip对应的上下文环境
final HashMap<InetAddress, Set<NIOServerCnxn>> ipMap =
new HashMap<InetAddress, Set<NIOServerCnxn>>( );
int maxClientCnxns = 10; public Factory(InetSocketAddress addr, int maxcc) throws IOException {
setDaemon(true);
//单个client链接最大数
maxClientCnxns = maxcc;
//创建服务器通道
this.ss = ServerSocketChannel.open();
ss.socket().setReuseAddress(true);
//绑定端口
ss.socket().bind(addr);
//设置通道为非阻塞通道
ss.configureBlocking(false);
//把通道注册到选择器中
ss.register(selector, SelectionKey.OP_ACCEPT);
}
public void run() {
while (!ss.socket().isClosed()) {
try {
//选择一组键
selector.select(1000);
Set<SelectionKey> selected;
synchronized (this) {
selected = selector.selectedKeys();
}
ArrayList<SelectionKey> selectedList = new ArrayList<SelectionKey>(
selected);
Collections.shuffle(selectedList);
for (SelectionKey k : selectedList) {
//如果通道已经准备好接收套接字
if ((k.readyOps() & SelectionKey.OP_ACCEPT) != 0) {
SocketChannel sc = ((ServerSocketChannel) k
.channel()).accept();
InetAddress ia = sc.socket().getInetAddress();
//判断最大连接数
int cnxncount = getClientCnxnCount(ia);
if (maxClientCnxns > 0 && cnxncount >= maxClientCnxns){
sc.close();
} else {
// 配置为非阻塞
sc.configureBlocking(false);
//把通道注册到选择器中
SelectionKey sk = sc.register(selector,
SelectionKey.OP_READ);
NIOServerCnxn cnxn = createConnection(sc, sk);
//给该通道附带一个上下文环境
sk.attach(cnxn);
addCnxn(cnxn);
}
} else if ((k.readyOps() & (SelectionKey.OP_READ | SelectionKey.OP_WRITE)) != 0) {
//通过上线文件来进行读写
NIOServerCnxn c = (NIOServerCnxn) k.attachment();
c.doIO(k);
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("Unexpected ops in select "
+ k.readyOps());
}
}
}
selected.clear();
catch (Exception e) {
LOG.warn("Ignoring exception", e);
}
}
clear();
}
//关闭
public void shutdown() {
try {
ss.close();
clear();
this.interrupt();
this.join();
} catch (Exception e) {
LOG.warn("Ignoring unexpected exception during shutdown", e);
}
try {
selector.close();
} catch (IOException e) {
LOG.warn("Selector closing", e);
}
if (zks != null) {
zks.shutdown();
}
}
synchronized public void clear() {
selector.wakeup();
HashSet<NIOServerCnxn> cnxns;
synchronized (this.cnxns) {
cnxns = (HashSet<NIOServerCnxn>)this.cnxns.clone();
}
// got to clear all the connections that we have in the selector
for (NIOServerCnxn cnxn: cnxns) {
try {
// don't hold this.cnxns lock as deadlock may occur
cnxn.close();
} catch (Exception e) {
LOG.warn("Ignoring exception closing cnxn sessionid 0x"
+ Long.toHexString(cnxn.sessionId), e);
}
}
}
}
上下文环境主要通过NIO读取请求数据,首先会读取4个字节的请求数据,该数据分为两种情况,一种是真正的数据包长度,一种是命令。如果是真正的数据包长度会按长度读取数据报,进行处理。如果是命令,会根据命令进行处理。
服务端通道上下文,通过NIO进行读写(NIOServerCnxn.doIO):
void doIO(SelectionKey k) throws InterruptedException {
try {
//如果通道可以读取数据
if (k.isReadable()) {
//读取数据到缓存中
int rc = sock.read(incomingBuffer);
//如果读满缓存
if (incomingBuffer.remaining() == 0) {
boolean isPayload;
//如果第一次读取,则先读取长度内容,相应分配缓存;否则读取指定长度的数据内容
if (incomingBuffer == lenBuffer) {
incomingBuffer.flip();
isPayload = readLength(k);
incomingBuffer.clear();
} else {
isPayload = true;
}
if (isPayload) {
//读取数据,初始化、读取请求数据封装成packet
readPayload();
}
}
}
//如果通道可以读写数据
if (k.isWritable()) {
//缓存中有数据需要写入
if (outgoingBuffers.size() > 0) {
//创建bytebuffer
ByteBuffer directBuffer = factory.directBuffer;
directBuffer.clear();
//从队列中读取数据知道缓存读满
for (ByteBuffer b : outgoingBuffers) {
if (directBuffer.remaining() < b.remaining()) {
b = (ByteBuffer) b.slice().limit(
directBuffer.remaining());
}
int p = b.position();
directBuffer.put(b);
b.position(p);
if (directBuffer.remaining() == 0) {
break;
}
}
//将数据写入通道
directBuffer.flip();
int sent = sock.write(directBuffer);
ByteBuffer bb;
//从缓存中已经发送的删除数据
while (outgoingBuffers.size() > 0) {
bb = outgoingBuffers.peek();
int left = bb.remaining() - sent;
if (left > 0) {
bb.position(bb.position() + sent);
break;
}
sent -= bb.remaining();
outgoingBuffers.remove();
}
} synchronized(this.factory){
if (outgoingBuffers.size() == 0) {
sk.interestOps(sk.interestOps()
& (~SelectionKey.OP_WRITE));
} else {
sk.interestOps(sk.interestOps()
| SelectionKey.OP_WRITE);
}
}
}
}catch (IOException e) {
close();
}
}
private void readPayload() throws IOException, InterruptedException {
if (incomingBuffer.remaining() != 0) { // have we read length bytes?
int rc = sock.read(incomingBuffer); // sock is non-blocking, so ok
}
if (incomingBuffer.remaining() == 0) {
//重置缓存
incomingBuffer.flip();
//如果没有进行初始化,首先要初始化;如果已经链接,则读取请求数据,封装成packet
if (!initialized) {
readConnectRequest();
} else {
readRequest();
}
//重置
lenBuffer.clear();
incomingBuffer = lenBuffer;
}
}
服务端通道上下文,读取长度,如果是命令,另起线程处理命令操作(NIOServerCnxn.readLength)
private boolean readLength(SelectionKey k) throws IOException {
//如果是请求数据,根据长度分配缓存;如果是命令,执行相应命令。
int len = lenBuffer.getInt();
if (!initialized && checkFourLetterWord(k, len)) {
return false;
}
if (len < 0 || len > BinaryInputArchive.maxBuffer) {
throw new IOException("Len error " + len);
}
if (zk == null) {
throw new IOException("ZooKeeperServer not running");
}
incomingBuffer = ByteBuffer.allocate(len);
return true;
}
private boolean checkFourLetterWord(final SelectionKey k, final int len)
throws IOException
{
//获取命令
String cmd = cmd2String.get(len);
/** cancel the selection key to remove the socket handling
* from selector. This is to prevent netcat problem wherein
* netcat immediately closes the sending side after sending the
* commands and still keeps the receiving channel open.
* The idea is to remove the selectionkey from the selector
* so that the selector does not notice the closed read on the
* socket channel and keep the socket alive to write the data to
* and makes sure to close the socket after its done writing the data
*/
if (k != null) {
try {
k.cancel();
} catch(Exception e) {
LOG.error("Error cancelling command selection key ", e);
}
}
//根据命令类型,执行相应内容
final PrintWriter pwriter = new PrintWriter(
new BufferedWriter(new SendBufferWriter()));
if (len == ruokCmd) {
RuokCommand ruok = new RuokCommand(pwriter);
ruok.start();
return true;
} else if (len == getTraceMaskCmd) {
TraceMaskCommand tmask = new TraceMaskCommand(pwriter);
tmask.start();
return true;
} else if (len == setTraceMaskCmd) {
int rc = sock.read(incomingBuffer);
if (rc < 0) {
throw new IOException("Read error");
} incomingBuffer.flip();
long traceMask = incomingBuffer.getLong();
ZooTrace.setTextTraceLevel(traceMask);
SetTraceMaskCommand setMask = new SetTraceMaskCommand(pwriter, traceMask);
setMask.start();
return true;
} else if (len == enviCmd) {
EnvCommand env = new EnvCommand(pwriter);
env.start();
return true;
} else if (len == confCmd) {
ConfCommand ccmd = new ConfCommand(pwriter);
ccmd.start();
return true;
} else if (len == srstCmd) {
StatResetCommand strst = new StatResetCommand(pwriter);
strst.start();
return true;
} else if (len == crstCmd) {
CnxnStatResetCommand crst = new CnxnStatResetCommand(pwriter);
crst.start();
return true;
} else if (len == dumpCmd) {
DumpCommand dump = new DumpCommand(pwriter);
dump.start();
return true;
} else if (len == statCmd || len == srvrCmd) {
StatCommand stat = new StatCommand(pwriter, len);
stat.start();
return true;
} else if (len == consCmd) {
ConsCommand cons = new ConsCommand(pwriter);
cons.start();
return true;
} else if (len == wchpCmd || len == wchcCmd || len == wchsCmd) {
WatchCommand wcmd = new WatchCommand(pwriter, len);
wcmd.start();
return true;
}
return false;
}
服务端通道上下文,如果是请求数据,处理请求数据(NIOServerCnxn.readRequest\NIOServerCnxn.readConnectRequest):
private void readRequest() throws IOException {
//反序列化请求数据
InputStream bais = new ByteBufferInputStream(incomingBuffer);
BinaryInputArchive bia = BinaryInputArchive.getArchive(bais);
RequestHeader h = new RequestHeader();
h.deserialize(bia, "header");
incomingBuffer = incomingBuffer.slice();
if (h.getType() == OpCode.auth) {
//如果是认证请求
AuthPacket authPacket = new AuthPacket();
ZooKeeperServer.byteBuffer2Record(incomingBuffer, authPacket);
String scheme = authPacket.getScheme();
//进行认证
AuthenticationProvider ap = ProviderRegistry.getProvider(scheme);
if (ap == null
|| (ap.handleAuthentication(this, authPacket.getAuth())
!= KeeperException.Code.OK)) {
// 认证失败,返回失败内容
ReplyHeader rh = new ReplyHeader(h.getXid(), 0,
KeeperException.Code.AUTHFAILED.intValue());
sendResponse(rh, null, null);
//关闭链接
sendCloseSession();
disableRecv();
} else {
//认证成功,返回成功内容
ReplyHeader rh = new ReplyHeader(h.getXid(), 0,
KeeperException.Code.OK.intValue());
sendResponse(rh, null, null);
}
return;
} else {
//如果是请求,提交到zk处理
Request si = new Request(this, sessionId, h.getXid(), h.getType(), incomingBuffer, authInfo);
si.setOwner(ServerCnxn.me);
zk.submitRequest(si);
}
} private void readConnectRequest() throws IOException, InterruptedException {
//反序列化链接请求对象
BinaryInputArchive bia = BinaryInputArchive
.getArchive(new ByteBufferInputStream(incomingBuffer));
ConnectRequest connReq = new ConnectRequest();
connReq.deserialize(bia, "connect");
if (connReq.getLastZxidSeen() > zk.getZKDatabase().getDataTreeLastProcessedZxid()) {
throw new CloseRequestException(msg);
}
sessionTimeout = connReq.getTimeOut();
byte passwd[] = connReq.getPasswd();
//初始化session
disableRecv();
if (connReq.getSessionId() != 0) {
long clientSessionId = connReq.getSessionId();
factory.closeSessionWithoutWakeup(clientSessionId);
setSessionId(clientSessionId);
zk.reopenSession(this, sessionId, passwd, sessionTimeout);
} else {
zk.createSession(this, passwd, sessionTimeout);
}
initialized = true;
}
服务端通道上下文,写返回数据(NIOServerCnxn.sendResponse)
synchronized public void sendResponse(ReplyHeader h, Record r, String tag) {
try {
//序列化返回结果,
ByteArrayOutputStream baos = new ByteArrayOutputStream();
BinaryOutputArchive bos = BinaryOutputArchive.getArchive(baos);
try {
baos.write(fourBytes);
bos.writeRecord(h, "header");
if (r != null) {
bos.writeRecord(r, tag);
}
baos.close();
} catch (IOException e) {
LOG.error("Error serializing response");
}
//写入数据长度
byte b[] = baos.toByteArray();
ByteBuffer bb = ByteBuffer.wrap(b);
bb.putInt(b.length - 4).rewind();
//
sendBuffer(bb);
} catch(Exception e) {
LOG.warn("Unexpected exception. Destruction averted.", e);
}
}
void sendBuffer(ByteBuffer bb) {
try {
if (bb != closeConn) {
//直接发送数据
if ((sk.interestOps() & SelectionKey.OP_WRITE) == 0) {
try {
sock.write(bb);
} catch (IOException e) {
// we are just doing best effort right now
}
}
if (bb.remaining() == 0) {
packetSent();
return;
}
}
//写入缓存中。
synchronized(this.factory){
sk.selector().wakeup();
outgoingBuffers.add(bb);
if (sk.isValid()) {
sk.interestOps(sk.interestOps() | SelectionKey.OP_WRITE);
}
} } catch(Exception e) {
LOG.error("Unexpected Exception: ", e);
}
}
zookeeper服务端的更多相关文章
- 【分布式】Zookeeper服务端启动
一.前言 前面已经了解了Zookeeper会话相关知识点,接着来学习Zookeeper服务端相关细节. 二.服务端 服务端整体架构如下 Zookeeper服务器的启动,大致可以分为以下五个步骤 1. ...
- Zookeeper 源码(四)Zookeeper 服务端源码
Zookeeper 源码(四)Zookeeper 服务端源码 Zookeeper 服务端的启动入口为 QuorumPeerMain public static void main(String[] a ...
- zookeeper 服务端上下线,客户端感知
package pfs.y2017.m11.zookeeper.demo03; import java.util.ArrayList; import java.util.List; import or ...
- Zookeeper系列(十)zookeeper的服务端启动详述
作者:leesf 掌控之中,才会成功:掌控之外,注定失败.出处:http://www.cnblogs.com/leesf456/p/6105276.html尊重原创,大家功能学习进步: 一.前 ...
- zookeeper源码之服务端
zookeeper服务端主要包括一下几个模块: 1.启动模块. 2.核心执行模块 3.数据管理模块. 启动模块 读取配置文件,启动程序.详见:zookeeper源码之服务端启动模块. 核心执行 ...
- 使用Kazoo操作ZooKeeper服务治理
单机服务的可靠性及可扩展性有限,某台服务宕机可能会影响整个系统的正常使用:分布式服务能够有效地解决这一问题,但同时分布式服务也会带来一些新的问题,如:服务发现(新增或者删除了服务如何确保能让客户端知道 ...
- Apache ZooKeeper 服务启动源码解释
转载:https://www.ibm.com/developerworks/cn/opensource/os-cn-zookeeper-code/ 本文首先讲解了 Apache ZooKeeper 服 ...
- Watcher详解 工作机制, Watcher客户端注册、Watcher 服务端注册
Watcher详解.接口 在 ZooKeeper 中, 接口类 Watcher 用于表示一个标注你的事件处理器,其定义了事件通知相关的逻辑,包含 KeeperState 和 EventType 两个枚 ...
- Zookeeper服务注册与发现原理浅析
了解Zookeeper的我们都知道,Zookeeper是一种分布式协调服务,在分布式应用中,主要用来实现分布式服务的注册与发现以及分布式锁,本文我们简单介绍一下Zookeeper是如何实现服务的注册与 ...
随机推荐
- redis 过期策略你知道多少,看完文章你会不自觉说喔哦
Redis 所有的数据结构都可以设置过期时间,时间一到,就会自动删除.你可以想象 Redis 内部有一个死神,时刻盯着所有设置了过期时间的 key,寿命一到就会立即收割. 你还可以进一步站在死神的角度 ...
- Mybatis进阶使用-一级缓存与二级缓存
简介 缓存是一般的ORM 框架都会提供的功能,目的就是提升查询的效率和减少数据库的压力.跟Hibernate 一样,MyBatis 也有一级缓存和二级缓存,并且预留了集成第三方缓存的接口. 一级缓存 ...
- 使用disk-image-builder(DIB)制作Ironic 裸金属镜像
export DIB_DEV_USER_USERNAME=centos export DIB_DEV_USER_PASSWORD= export DIB_DEV_USER_PWDLESS_SUDO=Y ...
- 第2篇scrum
第2篇scrum 一.站立式会议 1.1会议照片 想得美 1.2项目进展 团队成员 昨日完成任务 今日计划任务 感想 吴茂平 完善用户系统 改进评论数据表,增加评论,删除评论,查询评论 今天也是元气满 ...
- python字典的概念与基本操作
字典是非常常用的一种数据结构,它与json格式的数据非常相似,核心就是以键值对的形式存储数据,关于Python中的字典做如下四点说明: 1.构造字典对象需要用大括号表示 {},每个字典元素都是以键值对 ...
- linux系统学习
一.linux系统如何配置环境变量 1.在Windows 系统下,很多软件安装都需要配置环境变量,比如 安装 jdk ,如果不配置环境变量,在非软件安装的目录下运行javac 命令,将会报告找不到文件 ...
- 必看!2020最新黑马JAVA 学习路线
https://www.fang1688.cn/2020/08/24/%e5%bf%85%e7%9c%8b%ef%bc%81java-%e5%ad%a6%e4%b9%a0%e8%b7%af%e7%ba ...
- Myeclipse maven 配置有问题 改之后重启还是不好用
在配置maven项目的时候我一大意选错了maven服务,然后回来改配置文件的时候发现改完之后重启并没有效果,重新清了好几次编译也不好用,最后发现最好是手动去更新一下maven服务的配置文件 位置如下: ...
- 查看/设置 mysql时区
# 查看时区 show variables like '%time_zone%'; # 设置全局 set global time_zone='+8:00'; # 设置当前会话 set time_zo ...
- e3mall商城的归纳总结9之activemq整合spring、redis的缓存
敬给读者 本节主要给大家说一下activemq整合spring,该如何进行配置,上一节我们说了activemq的搭建和测试(单独测试),想看的可以点击时空隧道前去查看.讲完了之后我们还说一说在项目中使 ...