Zookeeper内部实现分布式数据一致性(客户端)(六)
Zookeeper的客户端主要由以下几个核心组件:
Zookeeper实例:客户端入口;
ClientWatchManager:客户端Watcher管理器;
HostProvider:客户端地址列表管理器。
ClientCnxn:客户端核心线程。其内部包含两个线程:SendThread和EventThread。前一个是I/O线程,主要负责Zookeeper客户端和服务端之间的网络I/O通信,后一个是事件线程,主要负责符服务端事件进行处理。
<1> 先从构造函数入手看一下Zookeeper客户端的初始化与启动环节;
1)初始化Zookeeper对象;
2)设置默认Watcher;
3)构造Zookeeper服务器地址列表管理器:HostProvider
4)创建并初始化客户端网络连接器,ClientCnxn
5)初始化SendThread和EventThread;
下面从源码来看这个流程;
//构造参数传入,服务器地址列表,超时时间,自定义Watcher; ZooKeeper zookeeper = new ZooKeeper("127.0.0.1:2181", 5000,new ZooKeeper_Constructor_Usage_Simple()); public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher)throws IOException { this(connectString, sessionTimeout, watcher, false); } public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, boolean canBeReadOnly) throws IOException { this(connectString, sessionTimeout, watcher, canBeReadOnly, createDefaultHostProvider(connectString)); } //说一下这个createDefaultHostProvider,创建客户单列表管理器; private static HostProvider createDefaultHostProvider(String connectString) { return new StaticHostProvider( new ConnectStringParser(connectString).getServerAddresses()); } //然后看一下ConnectStringParser方法怎么解析:省略了一些不是很关键的代码; public ConnectStringParser(String connectString) { int off = connectString.indexOf('/'); if (off >= 0) { ………… connectString = connectString.substring(0, off); } else { this.chrootPath = null; } List<String> hostsList = split(connectString,","); for (String host : hostsList) { int port = DEFAULT_PORT; int pidx = host.lastIndexOf(':'); if (pidx >= 0) { if (pidx < host.length() - 1) { port = Integer.parseInt(host.substring(pidx + 1)); } host = host.substring(0, pidx); } // 封装成InetSocketAddress,放入Arrylist中; serverAddresses.add(InetSocketAddress.createUnresolved(host, port)); } }
//将ArrayList<InetSocketAddress>封装 public StaticHostProvider(Collection<InetSocketAddress> serverAddresses) { init(serverAddresses, System.currentTimeMillis() ^ this.hashCode(), new Resolver() { @Override public InetAddress[] getAllByName(String name) throws UnknownHostException { return InetAddress.getAllByName(name); } }); }
//先记住init方法中的参数;
//init方法中的Resolver是一个内部类,非原生库中东西; //InetAddress.getAllByName(host):通过host来获取一个可用地址; private void init(Collection<InetSocketAddress> serverAddresses, long randomnessSeed, Resolver resolver) { //生成一个随机数,将多个服务器地址打算,为生成一个环行队列准备 this.sourceOfRandomness = new Random(randomnessSeed); this.resolver = resolver; if (serverAddresses.isEmpty()) { throw new IllegalArgumentException( "A HostProvider may not be empty!"); } //shuffle方法最终调用Collections.shuffle方法,执行地址打散; this.serverAddresses = shuffle(serverAddresses); //表示当前遍历到的那个地址位置, currentIndex = -1; //表示当前正在使用哪个服务器位置; lastIndex = -1; //循环的实现: //在每一次尝试获取一个服务器地址时,都会首先将currentIndex向前 //移动1,如果发现右边移动超过了整个地址列表的长度,那就重置为0 //这样就实现了循环; //如果lastIndex和currentIndex相等,即如果发现当前游标位置和上 // 次使用过的地址位置一样,就行spinDelay的时间等待 }
总结一下:此时源码才分析到:createDefaultHostProvider方法返回:以上只做了一件事:就是构造了服务器地址列表;
有个细节:
public interface HostProvider { public int size(); //主要介绍一个这个next方法:返回一个服务器地址,以便客户端连接; public InetSocketAddress next(long spinDelay); //回调方法,如果客户端与服务器成功创建连接,就通过这个方法来通知 //HostProvider public void onConnected(); boolean updateServerList(Collection<InetSocketAddress> serverAddresses, InetSocketAddress currentHost); }
//StaticHostProvider实现了HostProvider,对next方法做了实现,上述将了lastIndex和currentIndex的逻辑就在此next方法中;
接着往下走
public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, boolean canBeReadOnly, HostProvider aHostProvider) throws IOException { this(connectString, sessionTimeout, watcher, canBeReadOnly, aHostProvider, null); }
public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, boolean canBeReadOnly, HostProvider aHostProvider, ZKClientConfig clientConfig) throws IOException { if (clientConfig == null) { clientConfig = new ZKClientConfig(); } this.clientConfig = clientConfig; //创建一个WatchManager; watchManager = defaultWatchManager(); //注册Watcher当做默认watcher watchManager.defaultWatcher = watcher; ConnectStringParser connectStringParser = new ConnectStringParser(connectString); hostProvider = aHostProvider; cnxn = createConnection(connectStringParser.getChrootPath(), hostProvider, sessionTimeout, this, watchManager, getClientCnxnSocket(), canBeReadOnly); cnxn.start(); }
关注 createConnection 方法:
protected ClientCnxn createConnection(String chrootPath, HostProvider hostProvider, int sessionTimeout, ZooKeeper zooKeeper, ClientWatchManager watcher, ClientCnxnSocket clientCnxnSocket, boolean canBeReadOnly) throws IOException { return new ClientCnxn(chrootPath, hostProvider, sessionTimeout, this, watchManager, clientCnxnSocket, canBeReadOnly); }
public ClientCnxn(String chrootPath, HostProvider hostProvider, int sessionTimeout, ZooKeeper zooKeeper, ClientWatchManager watcher, ClientCnxnSocket clientCnxnSocket, boolean canBeReadOnly) throws IOException { this(chrootPath, hostProvider, sessionTimeout, zooKeeper, watcher, clientCnxnSocket, 0, new byte[16], canBeReadOnly); }
在初始化之前(ClientCnxn):
public class ClientCnxn { private static final int SET_WATCHES_MAX_LENGTH = 128 * 1024; private final LinkedList<Packet> pendingQueue = new LinkedList<Packet>(); private final LinkedBlockingDeque<Packet> outgoingQueue = new LinkedBlockingDeque<Packet>(); ……………… //初始化了两个队列,outgoingQueue队列和pendingQueue队列,分别是客户端待发送请求队列和服务端响应的等待队列; }
public ClientCnxn(String chrootPath, HostProvider hostProvider, int sessionTimeout, ZooKeeper zooKeeper, ClientWatchManager watcher, ClientCnxnSocket clientCnxnSocket, long sessionId, byte[] sessionPasswd, boolean canBeReadOnly) { this.zooKeeper = zooKeeper; this.watcher = watcher; this.sessionId = sessionId; this.sessionPasswd = sessionPasswd; this.sessionTimeout = sessionTimeout; this.hostProvider = hostProvider; this.chrootPath = chrootPath; connectTimeout = sessionTimeout / hostProvider.size(); readTimeout = sessionTimeout * 2 / 3; readOnly = canBeReadOnly; sendThread = new SendThread(clientCnxnSocket); eventThread = new EventThread(); this.clientConfig=zooKeeper.getClientConfig(); initRequestTimeout(); }
主要关注SendThread和EventThread线程;
SendThread管理服务端和客户端所有IO连接。clientCnxnSocket是一个底层网络处理器。
EventThread和SendThread继承自ZookeeperThread--->Thread;
SendThread的run方法中包含了所有客户端处理的核心;这里不列出run方法,大致说一些作用;
zookeeper的最后一行是cnxn.start();就是启动以上两个线程:
进入会话创建的正真阶段(run方法的逻辑):
SendThread:
1.判断当前客户端状态,进行一系列清理工作,为发起会话做准备;
2.获取一个服务端地址(HostProvider的next()方法);
3.创建TCP长连接;
4.构造ConnectRequest请求(建立TCP连接不等于zookeeper客户端会话创建完成)。将该请求包装成Packet对象,放入outgoingQueue队列。
5.发送请求;
响应处理阶段:
6.还是ClientCnxnSocket:接收到服务端的响应后,判断客户端是否已初始化,即该响应是会话创建的请求响应,还是其他响应;
7.处理响应:得到ConnectResponse对象,获取Zookeeper服务端分配的会话sessionId.
8.连接成功后,通知SendThread线程对客户端进行会话参数设置,更新客户端状态,另外通知HostProvider当前成功连接的服务器地址。
9.生成事件,让上层应用感知:SendThread生成事件(SyncConnected-None),表示客户端与会话服务器创建成功,将该事件传递给EventThread线程。
10.EventThread收到事件后,从ClientWatchManager中查询对应Watcher,将其放到EventThread的waitingEvents队列当中。
11.处理事件:EventThread不断从waitingEvents队列中取出待处理的Watcher对象,然后调用process方法;
至此,客户端会话创建包括处理逻辑都已结束。

浙公网安备 33010602011771号