ZookeeperNet客户端源码解析
说明:
源代码版本为GitHub 3.3.4.8,本文主要讲解ZookeeperNet内部运行机制,不过多介绍客户端调用方法。
框架图:
主要流程:客户端通过TCP建立与Zookeeper服务器集群的连接,同时会启动两个线程SendThread及EventThread,SendThread负责处理与Zookeeper Server之间的通信,EventThread处理由服务器触发的事件,OutgoingQueue为客户端发送消息队列,PendingQueue为客户端接收服务器消息队列。
源码解析:
建立与zookeeper服务器连接
var zk = new ZooKeeper(Config.IP, new TimeSpan(0, 0, 0, 50000), new Watcher());//建立连接
看下ZooKeeper构造器,设置默认watcher,实例化ClientConnection对象
public ZooKeeper(string connectstring, TimeSpan sessionTimeout, IWatcher watcher)
{
LOG.InfoFormat("Initiating client connection, connectstring={0} sessionTimeout={1} watcher={2}", connectstring, sessionTimeout, watcher);
watchManager.DefaultWatcher = watcher;
cnxn = new ClientConnection(connectstring, sessionTimeout, this, watchManager);
cnxn.Start();
}
public ClientConnection(string connectionString, TimeSpan sessionTimeout, ZooKeeper zooKeeper, ZKWatchManager watcher):
this(connectionString, sessionTimeout, zooKeeper, watcher, 0, new byte[16], DefaultConnectTimeout)
{
}
public ClientConnection(string hosts, TimeSpan sessionTimeout, ZooKeeper zooKeeper, ZKWatchManager watcher, long sessionId, byte[] sessionPasswd, TimeSpan connectTimeout)
{
this.hosts = hosts;
this.zooKeeper = zooKeeper;
this.watcher = watcher;
SessionTimeout = sessionTimeout;
SessionId = sessionId;
SessionPassword = sessionPasswd;
ConnectionTimeout = connectTimeout;
// parse out chroot, if any
hosts = SetChrootPath();
GetHosts(hosts);
SetTimeouts(sessionTimeout);
CreateConsumer();
CreateProducer();
}
private void CreateConsumer()
{
consumer = new ClientConnectionEventConsumer(this);
}
public ClientConnectionEventConsumer(ClientConnection conn)
{
this.conn = conn;
eventThread = new Thread(new SafeThreadStart(PollEvents).Run) { Name = new StringBuilder("ZK-EventThread ").Append(conn.zooKeeper.Id).ToString(), IsBackground = true };
}
private void CreateProducer()
{
producer = new ClientConnectionRequestProducer(this);
}
public ClientConnectionRequestProducer(ClientConnection conn)
{
this.conn = conn;
zooKeeper = conn.zooKeeper;
zkEndpoints = new ZooKeeperEndpoints(conn.serverAddrs);
requestThread = new Thread(new SafeThreadStart(SendRequests).Run) { Name = new StringBuilder("ZK-SendThread ").Append(conn.zooKeeper.Id).ToString(), IsBackground = true };
}
实例化ClientConnection对象,设置SessionTimeout 为客户端传入的值,ConnectionTimeout 为默认值,SetTimeouts(sessionTimeout)设置readTimeout=sessionTimeout*2/3。CreateConsumer()、CreateProducer()会启动两个后台线程,对应的方法为PollEvents、SendRequests,PollEvents的工作相对简单,负责处理服务器触发的事件,SendRequests负责处理与服务器的通信。
PollEvents代码:
public void PollEvents()
{
try
{
while(!waitingEvents.IsCompleted)
{
try
{
ClientConnection.WatcherSetEventPair pair = null;
if (waitingEvents.TryTake(out pair, -1))
{
ProcessWatcher(pair.Watchers, pair.WatchedEvent);
}
}
catch (ObjectDisposedException)
{
}
catch (InvalidOperationException)
{
}
catch (OperationCanceledException)
{
//ignored
}
catch (Exception t)
{
LOG.Error("Caught unexpected throwable", t);
}
}
}
catch (ThreadInterruptedException e)
{
LOG.Error("Event thread exiting due to interruption", e);
}
LOG.Info("EventThread shut down");
}
private static void ProcessWatcher(IEnumerable<IWatcher> watchers,WatchedEvent watchedEvent)
{
foreach (IWatcher watcher in watchers)
{
try
{
if (null != watcher)
{
watcher.Process(watchedEvent);
}
}
catch (Exception t)
{
LOG.Error("Error while calling watcher ", t);
}
}
}
waitingEvents.TryTake(out pair, -1),如果waitingEvents没值会阻塞当前线程阻塞,一旦有服务器返回的事件需要处理便会执行事件的Process方法。
SendRequests代码:
private void SendRequests()
{
DateTime now = DateTime.UtcNow;
DateTime lastSend = now;
Packet packet = null;
while (zooKeeper.State.IsAlive())
{
try
{
now = DateTime.UtcNow;
if ((client == null || client.Client == null) || (!client.Connected || zooKeeper.State == ZooKeeper.States.NOT_CONNECTED))
{
// don't re-establish connection if we are closing
if(conn.IsClosed || closing)
break;
StartConnect();
lastSend = now;
}
TimeSpan idleSend = now - lastSend;
if (zooKeeper.State == ZooKeeper.States.CONNECTED)
{
TimeSpan timeToNextPing = new TimeSpan(0, 0, 0, 0, Convert.ToInt32(conn.readTimeout.TotalMilliseconds / 2 - idleSend.TotalMilliseconds));
if (timeToNextPing <= TimeSpan.Zero)
SendPing();
}
// Everything below and until we get back to the select is
// non blocking, so time is effectively a constant. That is
// Why we just have to do this once, here
packet = null;
lock (outgoingQueue)
{
if(!outgoingQueue.IsEmpty())
{
packet = outgoingQueue.First();
outgoingQueue.RemoveFirst();
}
}
if (packet != null)
{
// We have something to send so it's the same
// as if we do the send now.
DoSend(packet);
lastSend = DateTime.UtcNow;
packet = null;
}
else
packetAre.WaitOne(TimeSpan.FromMilliseconds(1));
}
catch (Exception e)
{
if (conn.IsClosed || closing)
{
if (LOG.IsDebugEnabled)
{
// closing so this is expected
LOG.DebugFormat(