zookeeper(4) 网络
zookeeper底层通过NIO进行网络传输,如果对NIO不是很熟悉,先参见java NIO。下面我们来逐步实现基于NIO的zookeeper实现,首先我们要定义应用层的通信协议。
传输协议
请求协议格式:
| 字段 | 长度 | 说明 | |
| len | 4 | 请求长度,解决半包粘包 | |
| 请求头 | xid | 4 | 请求在客户端的id,用于唯一标识和保证响应顺序 |
| type | 4 | 请求类型code | |
| 请求体 | request | 不同的请求类型,有不同的请求体结构 |
响应协议格式:
| 字段 | 长度 | 解释 | |
| len | 4 | 响应长度,解决半包粘包 | |
| 响应头 | xid | 4 | 包序号,唯一标识一个包,通过该标识找到对应的客户端Packet对象 |
| zxid | 8 | 服务端事务处理id | |
| err | 4 | 返回状态code | |
| 响应体 | response | 不同的响应类型有不同的响应体 |
请求和响应体类型:
List<String> children
| 请求code | 请求头类型 | 说明 | 请求格式 | 字段说明 | 响应格式 | 字段说明 |
| 0 | notification | |||||
| 1 | create | 创建节点 | String path | 路径 | String path | 路径 |
| byte[] data | 节点值 | |||||
| List<ACL> acl | acl值 | |||||
| int flags | 节点类型 | |||||
| 2 | delete | 删除节点 | String path | 路径 | 无 | |
| int version | 版本 | |||||
| 3 | exists | 是否存在指定节点 | String path | 路径 | Stat stat | 节点状态 |
| boolean watch | 是否有watch | |||||
| 4 | getData | 获取节点数据 | String path | 路径 | Stat stat | 节点状态 |
| boolean watch | 是否有watch | byte[] data | 节点数据 | |||
| 5 | setData | 设置节点数据 | String path | 路径 | Stat stat | 节点状态 |
| byte[] data | 数据 | |||||
| int version | 版本 | |||||
| 6 | getACL | 获取节点权限 | String path | 路径 | List<ACL> acl | 权限 |
| Stat stat | 节点状态 | |||||
| 7 | setACL | 设置节点权限 | String path | 路径 | Stat stat | 节点状态 |
| List<ACL> acl | 权限 | |||||
| int version | 版本 | |||||
| 8 | getChildren | 获取子节点 | String path | 路径 | List<String> children | 子节点名 |
| boolean watch | 是否有watch | |||||
| 9 | sync | 同步路径 | String path | 路径 | String path | 路径 |
| 11 | ping | |||||
| 12 | getChildren2 | |||||
| 100 | auth | |||||
| 101 | setWatches | |||||
| -10 | createSession | |||||
| -11 | closeSession | |||||
| -1 | error |
1.将请求封装成Packet对象放入outgoingQueue队列中。
2.有一个收发送线程会从outgoingQueue队列中取出一个可发送的Packet对象,并发送序列化信息,然后把该Packet放入到pendingQueue队列中。
3.收发线程会接收服务端响应,反序列号出结果数据,然后在pendingQueue中找到对应的Packet,设置结果,将Packet放入到waitingEvents队列中。
4.有一个事件线程,会不断从waitingEvents队列中取出一个Packet,并调用响应的callback方法。
发送packet包
Packet queuePacket(RequestHeader h, ReplyHeader r, Record request,
Record response, AsyncCallback cb, String clientPath,
String serverPath, Object ctx, WatchRegistration watchRegistration)
{ Packet packet = null;
synchronized (outgoingQueue) {
//设置一个全局唯一的id,作为数据包的id
if (h.getType() != OpCode.ping && h.getType() != OpCode.auth) {
h.setXid(getXid());
}
//将请求头,请求体,返回结果,watcher等封装成数据包。
packet = new Packet(h, r, request, response, null,
watchRegistration);
packet.cb = cb;
packet.ctx = ctx;
packet.clientPath = clientPath;
packet.serverPath = serverPath;
//将数据包添加到outgoing队列中。
outgoingQueue.add(packet);
}
sendThread.wakeup();
return packet;
}
发送线程主流程(ClientCnxn.SendThread.run):
class SendThread extends Thread {
SelectionKey sockKey;
private final Selector selector = Selector.open();
public void run() {
while (zooKeeper.state.isAlive()) {
//建立连接
startConnect();
//获取注册通道
selector.select(1000);
Set<SelectionKey> selected;
synchronized (this) {
selected = selector.selectedKeys();
}
for (SelectionKey k : selected) {
SocketChannel sc = ((SocketChannel) k.channel());
if ((k.readyOps() & SelectionKey.OP_CONNECT) != 0) {
//建立连接
if (sc.finishConnect()) {
primeConnection(k);
}
//读写数据
} else if ((k.readyOps() & (SelectionKey.OP_READ | SelectionKey.OP_WRITE)) != 0) {
doIO();
}
}
}
try {
selector.close();
} catch (IOException e) {
LOG.warn("Ignoring exception during selector close", e);
}
}
//通过nio建立连接
private void startConnect() throws IOException {
//从服务器列表中获取一台服务器地址
InetSocketAddress addr = serverAddrs.get(nextAddrToTry);
nextAddrToTry++;
if (nextAddrToTry == serverAddrs.size()) {
nextAddrToTry = 0;
}
//通过nio注册
SocketChannel sock;
sock = SocketChannel.open();
sock.configureBlocking(false);
sock.socket().setSoLinger(false, -1);
sock.socket().setTcpNoDelay(true);
try {
sockKey = sock.register(selector, SelectionKey.OP_CONNECT);
} catch (IOException e) {
sock.close();
throw e;
}
//初始化缓存
lenBuffer.clear();
incomingBuffer = lenBuffer;
}
}
处理读写主流程,主要是nio操作(ClientCnxn.SendThread.doIO):
boolean doIO() throws InterruptedException, IOException {
boolean packetReceived = false;
//获取socketchannel
SocketChannel sock = (SocketChannel) sockKey.channel();
//如果可读
if (sockKey.isReadable()) {
//读取数据到缓存中
int rc = sock.read(incomingBuffer);
//直到缓存读满
if (!incomingBuffer.hasRemaining()) {
//重置缓存
incomingBuffer.flip();
//如果读取的是长度信息,读取长度信息,并且分配相应缓存
if (incomingBuffer == lenBuffer) {
int len = incomingBuffer.getInt();
incomingBuffer = ByteBuffer.allocate(len);
} else if (!initialized) {
//如果是connect命令的返回值,获取session,timeout等相关信息
readConnectResult();
enableRead();
lenBuffer.clear();
//重置缓存
incomingBuffer = lenBuffer;
initialized = true;
} else {
//读取数据内容
readResponse();
//重置缓存
lenBuffer.clear();
incomingBuffer = lenBuffer;
packetReceived = true;
}
}
}
//如果是写
if (sockKey.isWritable()) {
synchronized (outgoingQueue) {
if (!outgoingQueue.isEmpty()) {
//从outgoingQueue队列中拿数据包写入通道
ByteBuffer pbb = outgoingQueue.getFirst().bb;
sock.write(pbb);
if (!pbb.hasRemaining()) {
sentCount++;
Packet p = outgoingQueue.removeFirst();
if (p.header != null
&& p.header.getType() != OpCode.ping
&& p.header.getType() != OpCode.auth) {
pendingQueue.add(p);
}
}
}
}
}
if (outgoingQueue.isEmpty()) {
disableWrite();
} else {
enableWrite();
}
return packetReceived;
}
处理返回结果,xid=-2为ping命令的返回结果;xid=-4为auth命令;xid=-1为服务器发送的notification;其他命令返回结果。
void readResponse() throws IOException {
//对返回数据进行反序列化
ByteBufferInputStream bbis = new ByteBufferInputStream(
incomingBuffer);
BinaryInputArchive bbia = BinaryInputArchive.getArchive(bbis);
ReplyHeader replyHdr = new ReplyHeader();
replyHdr.deserialize(bbia, "header");
//根据返回头信息,封装想要的事件,放入事件队列中,交给eventthread处理
//向消息队列放入验证失败消息
if (replyHdr.getXid() == -4) {
// -4 is the xid for AuthPacket
if(replyHdr.getErr() == KeeperException.Code.AUTHFAILED.intValue()) {
zooKeeper.state = States.AUTH_FAILED;
eventThread.queueEvent( new WatchedEvent(Watcher.Event.EventType.None,
Watcher.Event.KeeperState.AuthFailed, null) );
}
return;
}
//
if (replyHdr.getXid() == -1) {
WatcherEvent event = new WatcherEvent();
event.deserialize(bbia, "response");
if (chrootPath != null) {
String serverPath = event.getPath();
if(serverPath.compareTo(chrootPath)==0)
event.setPath("/");
else
event.setPath(serverPath.substring(chrootPath.length()));
}
WatchedEvent we = new WatchedEvent(event);
eventThread.queueEvent( we );
return;
}
//反序列化返回结果
Packet packet = null;
synchronized (pendingQueue) {
packet = pendingQueue.remove();
}
try {
packet.replyHeader.setXid(replyHdr.getXid());
packet.replyHeader.setErr(replyHdr.getErr());
packet.replyHeader.setZxid(replyHdr.getZxid());
if (replyHdr.getZxid() > 0) {
lastZxid = replyHdr.getZxid();
}
if (packet.response != null && replyHdr.getErr() == 0) {
packet.response.deserialize(bbia, "response");
}
} finally {
finishPacket(packet);
}
}
private void finishPacket(Packet p) {
if (p.watchRegistration != null) {
p.watchRegistration.register(p.replyHeader.getErr());
}
if (p.cb == null) {
synchronized (p) {
p.finished = true;
p.notifyAll();
}
} else {
p.finished = true;
eventThread.queuePacket(p);
}
}
事件线程主要是处理回调函数(ClientCnxn.EventThread.run):
public void run() {
try {
isRunning = true;
while (true) {
//从队列中获取事件
Object event = waitingEvents.take();
if (event == eventOfDeath) {
wasKilled = true;
} else {
//处理事件
processEvent(event);
}
if (wasKilled)
synchronized (waitingEvents) {
if (waitingEvents.isEmpty()) {
isRunning = false;
break;
}
}
}
} catch (InterruptedException e) {
LOG.error("Event thread exiting due to interruption", e);
}
LOG.info("EventThread shut down");
}
处理回调函数和watcher
private void processEvent(Object event) {
try {
//处理watcher
if (event instanceof WatcherSetEventPair) {
WatcherSetEventPair pair = (WatcherSetEventPair) event;
for (Watcher watcher : pair.watchers) {
try {
watcher.process(pair.event);
} catch (Throwable t) {
LOG.error("Error while calling watcher ", t);
}
}
} else {
//
Packet p = (Packet) event;
int rc = 0;
String clientPath = p.clientPath;
if (p.replyHeader.getErr() != 0) {
rc = p.replyHeader.getErr();
}
if (p.response instanceof ExistsResponse
|| p.response instanceof SetDataResponse
|| p.response instanceof SetACLResponse) {
StatCallback cb = (StatCallback) p.cb;
if (rc == 0) {
if (p.response instanceof ExistsResponse) {
cb.processResult(rc, clientPath, p.ctx,
((ExistsResponse) p.response)
.getStat());
} else if (p.response instanceof SetDataResponse) {
cb.processResult(rc, clientPath, p.ctx,
((SetDataResponse) p.response)
.getStat());
} else if (p.response instanceof SetACLResponse) {
cb.processResult(rc, clientPath, p.ctx,
((SetACLResponse) p.response)
.getStat());
}
} else {
cb.processResult(rc, clientPath, p.ctx, null);
}
} else if (p.response instanceof GetDataResponse) {
DataCallback cb = (DataCallback) p.cb;
GetDataResponse rsp = (GetDataResponse) p.response;
if (rc == 0) {
cb.processResult(rc, clientPath, p.ctx, rsp
.getData(), rsp.getStat());
} else {
cb.processResult(rc, clientPath, p.ctx, null,
null);
}
} else if (p.response instanceof GetACLResponse) {
ACLCallback cb = (ACLCallback) p.cb;
GetACLResponse rsp = (GetACLResponse) p.response;
if (rc == 0) {
cb.processResult(rc, clientPath, p.ctx, rsp
.getAcl(), rsp.getStat());
} else {
cb.processResult(rc, clientPath, p.ctx, null,
null);
}
} else if (p.response instanceof GetChildrenResponse) {
ChildrenCallback cb = (ChildrenCallback) p.cb;
GetChildrenResponse rsp = (GetChildrenResponse) p.response;
if (rc == 0) {
cb.processResult(rc, clientPath, p.ctx, rsp
.getChildren());
} else {
cb.processResult(rc, clientPath, p.ctx, null);
}
} else if (p.response instanceof GetChildren2Response) {
Children2Callback cb = (Children2Callback) p.cb;
GetChildren2Response rsp = (GetChildren2Response) p.response;
if (rc == 0) {
cb.processResult(rc, clientPath, p.ctx, rsp
.getChildren(), rsp.getStat());
} else {
cb.processResult(rc, clientPath, p.ctx, null, null);
}
} else if (p.response instanceof CreateResponse) {
StringCallback cb = (StringCallback) p.cb;
CreateResponse rsp = (CreateResponse) p.response;
if (rc == 0) {
cb.processResult(rc, clientPath, p.ctx,
(chrootPath == null
? rsp.getPath()
: rsp.getPath()
.substring(chrootPath.length())));
} else {
cb.processResult(rc, clientPath, p.ctx, null);
}
} else if (p.cb instanceof VoidCallback) {
VoidCallback cb = (VoidCallback) p.cb;
cb.processResult(rc, clientPath, p.ctx);
}
}
} catch (Throwable t) {
LOG.error("Caught unexpected throwable", t);
}
}
Packet结构
包,ClientCnxn内部管理请求内容的模块。由以下几个模块组成:
1.RequestHeader header 请求头
2.Record request 请求内容
3.ByteBuffer bb 实际需要发送的请求内容。
4.ReplyHeader replyHeader 响应头
5.Record response 响应内容
6.String clientPath
7.String serverPath
8.boolean finished
9.AsyncCallback cb
10.Object ctx
11.WatchRegistration watchRegistration
Packet(RequestHeader header, ReplyHeader replyHeader, Record record,
Record response, ByteBuffer bb,
WatchRegistration watchRegistration) {
this.header = header;
this.replyHeader = replyHeader;
this.request = record;
this.response = response;
if (bb != null) {
this.bb = bb;
} else {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
BinaryOutputArchive boa = BinaryOutputArchive
.getArchive(baos);
boa.writeInt(-1, "len"); // We'll fill this in later
header.serialize(boa, "header");
if (record != null) {
record.serialize(boa, "request");
}
baos.close();
this.bb = ByteBuffer.wrap(baos.toByteArray());
this.bb.putInt(this.bb.capacity() - 4);
this.bb.rewind();
} catch (IOException e) {
LOG.warn("Ignoring unexpected exception", e);
}
}
this.watchRegistration = watchRegistration;
}
zookeeper(4) 网络的更多相关文章
- 【分布式】Zookeeper序列化及通信协议
一.前言 前面介绍了Zookeeper的系统模型,下面进一步学习Zookeeper的底层序列化机制,Zookeeper的客户端与服务端之间会进行一系列的网络通信来实现数据传输,Zookeeper使用J ...
- Zookeeper 脑裂
转自 http://blog.csdn.net/u010185262/article/details/49910301 Zookeeper zookeeper是一个分布式应用程序的协调服务.它是一个为 ...
- ZooKeeper 分布式锁实现
1 场景描述 在分布式应用, 往往存在多个进程提供同一服务. 这些进程有可能在相同的机器上, 也有可能分布在不同的机器上. 如果这些进程共享了一些资源, 可能就需要分布式锁来锁定对这些资源的访问. 2 ...
- 【转载】zookeeper 分布式锁 实现
agapple 基于zookeeper的分布式lock实现 博客分类: opensource java distributed 背景 继续上一篇文章:http://agapple.iteye. ...
- 配置Zookeeper、Dubbox
CentOS的配置: 1.给CentOS安装Zookeeper: 网络配置成仅主机 上传tar.gz:比如用FTP tar -xvzf ... cd zookeeper mkdir data cd c ...
- ZooKeeper是按照CP原则构建的,不适合做Service服务发现
一.cap 分布式领域中存在CAP理论,且该理论已被证明:任何分布式系统只可同时满足两点,无法三者兼顾. ①C:Consistency,一致性,数据一致更新,所有数据变动都是同步的. ②A:Avail ...
- 为什么不应该使用Zookeeper做服务发现?(转载)
转载自: http://dockone.io/article/78 [编者的话]本文作者通过ZooKeeper与Eureka作为Service发现服务(注:WebServices体系中的UDDI就是个 ...
- 为什么不应该使用ZooKeeper做服务发现
[编者的话]本文作者通过ZooKeeper与Eureka作为Service发现服务(注:WebServices体系中的UDDI就是个发现服务)的优劣对比,分享了Knewton在云计算平台部署服务的经验 ...
- Eureka与ZooKeeper 的比较(转)
https://www.cnblogs.com/zgghb/p/6515062.html Eureka的优势 1.在Eureka平台中,如果某台服务器宕机,Eureka不会有类似于ZooKeeper的 ...
随机推荐
- Windows下make clean指令错误[错误码2](系统找不到指定文件)的解决方案
问题来源 因为笔者想用GCC编译器进行Windows下的C语言编程,安装了Mingw-w64的x86_64-posix-seh版本,并按照Visual Studio Code官方的教程,将Mingw- ...
- YApi——手摸手,带你在Win10环境下安装YApi可视化接口管理平台
手摸手,带你在Win10环境下安装YApi可视化接口管理平台 YApi YApi 是高效.易用.功能强大的 api 管理平台,旨在为开发.产品.测试人员提供更优雅的接口管理服务.可以帮助开发者轻松创建 ...
- 基础知识、DOS命令
一.信息安全 1.信息源认证 https 访问控制 ACL :不能有非法软件驻留 :不能含有未授权的操作等 2.2017-OWASP-TOP5 注入 :失效的身份认证和回话配置 :跨 ...
- SpringMVC修改视图定位
@ 目录 什么是视图定位 修改springmvc-servlet.xml 修改IndexController 移动index.jsp 测试 什么是视图定位 如果代码写成这样,就表示跳转到页面 inde ...
- CentOS 桥接网卡配置
[root@controller ~]# cat /etc/sysconfig/network-scripts/ifcfg-br0 DEVICE=br0 ONBOOT=yes TYPE=Bridge ...
- windows下安装jdk+tomcat+maven并配置
一.下载安装jdk并配置 1.1 进行JDK下载 下载地址:一键直达 一般下载后,安装位置默认,一路下一步,一直到安装完毕-"关闭". 1.2 环境变量配置 不要管是不是一般情况, ...
- Android开发禁止首次进入activity弹出软键盘,限制屏幕只能竖屏或者横屏展示
作者:程序员小冰,CSDN博客:http://blog.csdn.net/qq_21376985 只需在在Manifest.xml中设定activity的属性为: android:windowSoft ...
- Selenium处理文件上传、弹框
一.文件上传 上传标签是input时,可以直接使用send_keys(文件path)的方法来进行上传 二.弹框处理 页面操作中,有时会遇到JavaScript生成的alert.confirm以及pro ...
- Selenium执行js脚本
如何使用Selenium来执行Javascript脚本呢 Selenium中提供了一个方法:execute_script 来执行js脚本 return 可以返回js的返回结果 execute_scri ...
- JavaScript 究竟是怎样去执行的?
摘要: 理解 JS 引擎运行原理. 作者:前端小智 原文:搞懂 JavaScript 引擎运行原理 Fundebug经授权转载,版权归原作者所有. 一些名词 JS 引擎 — 一个读取代码并运行的引擎, ...