客户端
客户段交互过程
客户端使用
CountDownLatch countDownLatch=new CountDownLatch(1);
ZooKeeper zooKeeper = new ZooKeeper(CLUSTER_CONNECT_STR, 4000, new Watcher() {
@Override
public void process(WatchedEvent event) {
if(Event.KeeperState.SyncConnected==event.getState()
&& event.getType()== Event.EventType.None){
//如果收到了服务端的响应事件,连接成功
countDownLatch.countDown();
System.out.println("连接建立");
}
}
});
countDownLatch.await();
Stat stat = zooKeeper.exists("/user",false);
if(null ==stat){
//创建持久节点
zooKeeper.create("/user","lose_weight".getBytes(),ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
//持久监听
zooKeeper.addWatch("/user",new Watcher() {
@Override
public void process(WatchedEvent event) {
System.out.println(event);
}
},AddWatchMode.PERSISTENT);
Thread.sleep(Integer.MAX_VALUE);
客户端源码分析
ZooKeeper
//获取一个ClientCnxn对象,并调用该对象的start方法
public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher){
// 解析输入的zookeeper地址
ConnectStringParser connectStringParser = new ConnectStringParser(connectString);
// 调用ClientCnxn的new ClientCnxn()方法初始化ClientCnxn对象
cnxn = createConnection(
connectStringParser.getChrootPath(),
hostProvider,
sessionTimeout,
this.clientConfig,
watcher,
// 获取连接类型,有ClientCnxnSocketNIO(默认)和ClientCnxnSocketNetty两种
getClientCnxnSocket(),
canBeReadOnly);
cnxn.start();
}
// 对特定目录添加监听
public void addWatch(String basePath, Watcher watcher, AddWatchMode mode){
String serverPath = prependChroot(basePath);
RequestHeader h = new RequestHeader();
h.setType(ZooDefs.OpCode.addWatch);
AddWatchRequest request = new AddWatchRequest(serverPath, mode.getMode());
// submitRequest会等待服务端响应或者响应超时后才返回结果
ReplyHeader r = cnxn.submitRequest(h, request, new ErrorResponse(),
new AddWatchRegistration(watcher, basePath, mode));
if (r.getErr() != 0) {
throw KeeperException.create(KeeperException.Code.get(r.getErr()),
basePath);
}
}
ClientCnxn
管理客户端的socket i/o并维护可连接的可用服务器列表,根据需要“透明”地切换所连接的服务器。
//ClientCnxn的初始化方法
public ClientCnxn(
......
ZKClientConfig clientConfig,
Watcher defaultWatcher,
ClientCnxnSocket clientCnxnSocket,
......
) throws IOException {
......
// 对建立zookeeper时传入的Watcher的包装
this.watchManager = new ZKWatchManager(
clientConfig.getBoolean(ZKClientConfig.DISABLE_AUTO_WATCH_RESET),
defaultWatcher);
......
this.sendThread = new SendThread(clientCnxnSocket);
this.eventThread = new EventThread();
// 初始化requestTimeout,如果请求超过这个时间还没有响应,就认为这个响应丢失了
initRequestTimeout();
}
// ClientCnxn的start方法
public void start() {
sendThread.start();
eventThread.start();
}
// 将客户端的请求发送到服务端
public ReplyHeader submitRequest(
RequestHeader h,
Record request,
Record response,
WatchRegistration watchRegistration,
WatchDeregistration watchDeregistration) throws InterruptedException {
ReplyHeader r = new ReplyHeader();
// 包装请求,并将请求放到outgoingQueue队列中由SendThread发送
Packet packet = queuePacket(
h,
r,
request,
response,
null,
null,
null,
null,
watchRegistration,
watchDeregistration);
// 等待服务端响应
synchronized (packet) {
if (requestTimeout > 0) {
// Wait for request completion with timeout
waitForPacketFinish(r, packet);
} else {
// Wait for request completion infinitely
while (!packet.finished) {
packet.wait();
}
}
}
if (r.getErr() == Code.REQUESTTIMEOUT.intValue()) {
sendThread.cleanAndNotifyState();
}
return r;
}
ClientCnxn.SendThread
public void run() {
// outgoingQueue来自ClientCnxn,保存需要发送的packetd
// 将SendThread,sessionId和需要发送的消息的阻塞队列注册到ClientCnxnSocket
clientCnxnSocket.introduce(this, sessionId, outgoingQueue);
......
while (state.isAlive()) {
......
// 第一次循环时条件为真
if (!clientCnxnSocket.isConnected()) {
......
// 与服务端建立连接
startConnect(serverAddress);
......
}
if (state.isConnected()) {
.......
// clientCnxnSocket.getIdleRecv()返回从收到上一条消息到发送上一条消息过了多长时间
to = readTimeout - clientCnxnSocket.getIdleRecv();
} else {
to = connectTimeout - clientCnxnSocket.getIdleRecv();
}
if (to <= 0) {
throw new SessionTimeoutException(warnInfo);
}
if (state.isConnected()) {
int timeToNextPing = readTimeout / 2
- clientCnxnSocket.getIdleSend()
- ((clientCnxnSocket.getIdleSend() > 1000) ? 1000 : 0);
if (timeToNextPing <= 0 || clientCnxnSocket.getIdleSend() > MAX_SEND_PING_INTERVAL) {
// 发送ping消息
sendPing();
clientCnxnSocket.updateLastSend();
} else {
if (timeToNextPing < to) {
to = timeToNextPing;
}
}
}
clientCnxnSocket.doTransport(to, pendingQueue, ClientCnxn.this);
......
}
// 客户端关闭或者权限检验失败逻辑
}
private void startConnect(InetSocketAddress addr){
......
// 本文中假设实现类为ClientCnxnSocketNetty
clientCnxnSocket.connect(addr);
}
// 处理从server段发来的消息
void readResponse(ByteBuffer incomingBuffer){
......
synchronized (pendingQueue) {
// 非ping和auth请求在sendThread发送消息时都会加入pendingQueue中
if (pendingQueue.size() == 0) {
throw new IOException("Nothing in the queue, but got " + replyHdr.getXid());
}
packet = pendingQueue.remove();
}
try {
// 如果pendingQueue中的第一个消息和响应的中的消息的客户端ID不一致,认为有异常
// 默认消息响应的顺序与发送的顺序一致
if (packet.requestHeader.getXid() != replyHdr.getXid()) {
packet.replyHeader.setErr(KeeperException.Code.CONNECTIONLOSS.intValue());
throw new IOException();
}
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);
}
}
protected void finishPacket(Packet p) {
int err = p.replyHeader.getErr();
if (p.watchRegistration != null) {
p.watchRegistration.register(err);
}
// Add all the removed watch events to the event queue, so that the
// clients will be notified with 'Data/Child WatchRemoved' event type.
if (p.watchDeregistration != null) {
Map<EventType, Set<Watcher>> materializedWatchers = null;
try {
// 获取Packet对应的watcher
materializedWatchers = p.watchDeregistration.unregister(err);
for (Entry<EventType, Set<Watcher>> entry : materializedWatchers.entrySet()) {
Set<Watcher> watchers = entry.getValue();
if (watchers.size() > 0) {
// 把传入的信息包装为WatcherSetEventPair并放入EventThread的waitingEvents队列中,让EventThread处理
queueEvent(p.watchDeregistration.getClientPath(), err, watchers, entry.getKey());
// ignore connectionloss when removing from local
// session
p.replyHeader.setErr(Code.OK.intValue());
}
}
} catch (KeeperException.NoWatcherException nwe) {
p.replyHeader.setErr(nwe.code().intValue());
} catch (KeeperException ke) {
p.replyHeader.setErr(ke.code().intValue());
}
}
// 使用zookeeper.getData方法时,cb为null,getData方法将消息放入outgoing队列后,调用了wait方法,这里需要调用p.notifyAll()来唤醒,表示已经得到了结果
if (p.cb == null) {
synchronized (p) {
p.finished = true;
p.notifyAll();
}
} else {
p.finished = true;
eventThread.queuePacket(p);
}
}
ClientCnxn.EventThread
// 存放需要处理的event事件的阻塞队列
private final LinkedBlockingQueue<Object> waitingEvents = new LinkedBlockingQueue<Object>();
public void run() {
......
while (true) {
Object event = waitingEvents.take();
......
processEvent(event);
......
}
......
}
private void processEvent(Object event) {
......
// 服务端对客户端的getData等方法给的响应最后在加入waitingEvents时会包装为WatcherSetEventPair对象,这里就只展示这部分代码
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);
}
}
}
......
}
ClientCnxnSocketNetty
void connect(InetSocketAddress addr){
// 保证ClientCnxn.SendThread在第一次连接调用doTransport时连接已经建立
firstConnect = new CountDownLatch(1);
// handler为ClientCnxnSocketNetty.ZKClientHandler
Bootstrap bootstrap = new Bootstrap().group(eventLoopGroup)
.channel(NettyUtils.nioOrEpollSocketChannel())
.option(ChannelOption.SO_LINGER, -1)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ZKClientPipelineFactory(addr.getHostString(), addr.getPort()));
bootstrap = configureBootstrapAllocator(bootstrap);
connectLock.lock();
try {
connectFuture = bootstrap.connect(addr);
// listener的operationComplete方法中调用了firstConnect.countDown()
connectFuture.addListener(new ChannelFutureListener() {......}
}
}
void doTransport(
int waitTimeOut,
Queue<Packet> pendingQueue,
ClientCnxn cnxn) throws IOException, InterruptedException {
// 等待与server的连接建立成功,在connect或者onClosing方法中调用firstConnect.countDown()
if (!firstConnect.await(waitTimeOut, TimeUnit.MILLISECONDS)) {
return;
}
......
// 从消息队列中获取需要发送的消息
head = outgoingQueue.poll(waitTimeOut, TimeUnit.MILLISECONDS);
......
if (head != null) {
doWrite(pendingQueue, head, cnxn);
}
}
......
// 更新发送消息的时间
updateNow();
}
// 将outgoingQueue中的消息全部发给server,并且将非auth和ping相关的命令保存到pendingQueue队列中
private void doWrite(Queue<Packet> pendingQueue, Packet p, ClientCnxn cnxn) {
updateNow();
boolean anyPacketsSent = false;
while (true) {
if (p != WakeupPacket.getInstance()) {
if ((p.requestHeader != null)
&& (p.requestHeader.getType() != ZooDefs.OpCode.ping)
&& (p.requestHeader.getType() != ZooDefs.OpCode.auth)) {
p.requestHeader.setXid(cnxn.getXid());
synchronized (pendingQueue) {
pendingQueue.add(p);
}
}
// 将消息写到channel中,全部写入后再flush
sendPktOnly(p);
anyPacketsSent = true;
}
if (outgoingQueue.isEmpty()) {
break;
}
p = outgoingQueue.remove();
}
// TODO: maybe we should flush in the loop above every N packets/bytes?
// But, how do we determine the right value for N ...
if (anyPacketsSent) {
channel.flush();
}
}
ClientCnxnSocketNetty.ZKClientPipelineFactory
继承自ChannelInitializer,客户端Netty服务的初始化工程
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
if (clientConfig.getBoolean(ZKClientConfig.SECURE_CLIENT)) {
initSSL(pipeline);
}
pipeline.addLast("handler", new ZKClientHandler());
}
ClientCnxnSocketNetty.ZKClientHandler
客户端nettry消息的处理类
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf buf) throws Exception {
updateNow();
while (buf.isReadable()) {
if (incomingBuffer.remaining() > buf.readableBytes()) {
int newLimit = incomingBuffer.position() + buf.readableBytes();
incomingBuffer.limit(newLimit);
}
buf.readBytes(incomingBuffer);
incomingBuffer.limit(incomingBuffer.capacity());
if (!incomingBuffer.hasRemaining()) {
incomingBuffer.flip();
if (incomingBuffer == lenBuffer) {
recvCount.getAndIncrement();
readLength();
} else if (!initialized) {
readConnectResult();
lenBuffer.clear();
incomingBuffer = lenBuffer;
initialized = true;
updateLastHeard();
} else {
// 处理server段对client的请求的响应消息
sendThread.readResponse(incomingBuffer);
lenBuffer.clear();
incomingBuffer = lenBuffer;
updateLastHeard();
}
}
}
// 往outgoingQueue中写入一条消息,唤醒SendThread
wakeupCnxn();
}