Android设备连接ZooKeeper服务器
最近一直在学习ZooKeeper的相关知识,主要完成了用Android手机连接到ZooKeeper服务器,并进行建立节点、读取节点数据、删除节点等操作。下面对这一学习过程做一下总结。
- 初识ZooKeeper
- ZooKeeper客户端脚本
- ZooKeeper Java客户端API
- Android客户端连接ZooKeeper服务器
- 源码示例
初识ZooKeeper
ZooKeeper是Apache开源的分布式应用程序协调服务,其设计目标是将复杂易出错的分布式一致性服务封装起来,构成一个高效可靠的原语集,并为用户提供简单易用的接口。
ZooKeeper主要有以下特性:
-顺序一致性:从同一个客户端发起的事务请求,最终会严格按照其发起顺序被应用到ZooKeeper中去。
-原子性:所有事务请求的处理结果在整个集群中所有机器上的应用情况是一致的。即要么整个集群的所有机器都成功应用了某一事务,要么都没有应用。
-单一视图:无论客户端连接了哪个ZooKeeper服务器,其看到的服务端数据模型都是一致的。
-可靠性:一旦服务端成功应用了一个事务,并完成对客户端的响应,那么该事务所引起的服务端状态变更将会被一直保留下来,除非有别的事务又对其做了变更。
-实时性:ZooKeeper能保证在一定的时间段内,客户端一定能从服务端读取到最新的数据状态。
ZooKeeper客户端脚本
搭建好ZooKeeper集群后,进入ZooKeeper的bin目录,执行./zkCli.sh
即可启动客户端。
-创建节点
create [-s] [-e] path data acl
-读取指定节点下所有子节点
ls path [watch]
-获取指定节点的数据内容和属性信息
get path [watch]
-更新指定节点的数据内容
set path data [version]
-删除指定节点
delete path [version]
ZooKeeper Java客户端API
-创建会话
ZooKeeper(String connectString, int sessionTimeout, Watcher watcher);
connectString指要连接的ZooKeeper服务器列表,host:port形式,多个服务器地址之间用英文逗号隔开。sessionTimeout指会话超时时间,以毫秒为单位。watcher是已经声明的Watcher类的一个对象,指定为默认的Watcher事件通知处理器。
例:ZooKeeper zk = new ZooKeeper("192.168.3.6:2181", 5000, watcher);
-创建节点
String create(final String path, byte data[] , List, CreateMode createMode)
path为要创建的节点路径,data[]是一个字节数组,是节点创建后的初始内容,acl是节点的ACL策略,createMode为节点类型,有持久(PERSISTENT)、持久顺序(PERSISTENT_SEQUENTIAL)、临时(EPHEMERAL)和临时顺序(EPHEMERAL_SEQUENTIAL)四种类型。
例:zk.create("/DeviceList", "DeviceList".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
-删除节点
public void delete(final String path, int version)
path指定要删除节点的路径,version指定要删除节点的数据版本。
例:zk.delete("/Devicelist", -1)
-读取指定节点下的子节点
List getChildren(final String path, Watcher watcher)
List getChildren(final String path, boolean watch)
List getChildren(final String path, Watcher watcher, Stat stat)
List getChildren(final String path, boolean watch, Stat stat)
path指定节点路径。watcher是注册的Watcher,在本次获取子节点之后,如果子节点列表发生变更,就会向客户端发送通知,该参数允许传入null。watch表明是否要注册一个Watcher,如果为true会使用默认Watcher,为false则表明不需要注册Watcher。stat指定数据节点的状态信息。
例:List <String> childrenList = zk.getChildren ("/DeviceList",true);
-获取指定节点的数据内容
byte[] getData(final String path, Watcher watcher, Stat stat)
byte[] getData(final String path, boolean watch, Stat stat)
例:String data = new String( zk.getData ("/DeviceList", true, stat) );
-更新数据
Stat setData (final String path, byte data[], int version)
path指定节点路径。data[]是一个字节数组,即需要用该数据内容来覆盖节点现在的数据内容。version指定节点的数据版本,即表明本次更新操作是针对该数据版本进行的。
例:Stat stat = zk.setData ("/DeviceList", "123".getBytes(), -1);
-检测节点是否存在
public Stat exists (final String path, Watcher watcher)
public Stat exists (final String path, boolean watch)
例:zk.exists(path, true);
Android客户端连接ZooKeeper服务器
我的开发环境是Android studio.2.2.1,使用的ZooKeeper版本是比较稳定的3.4.6。
建好工程后首先要导入zookeeper-3.4.6.jar,此外我还导入了log4j-1.2.15.jar, slf4j-log4j12-1.6.4.jar和slf4j-api-1.6.4.jar。
在Android中可以很随意的使用上面介绍的ZooKeeper Java API,只是需要注意几点:
1.与ZooKeeper服务器建立连接要进行网络访问,因此要获取权限,具体操作为在AndroidMainfest.xml中添加
2.Android中与网络访问有关的操作不能放在主线程,因此需要以多线程的方式实现。
3.最重要也是最难解决的一点,需要在Android程序中关闭SASL认证,具体操作为:在建立会话之前插入如下语句
如果ZooKeeperSaslClient.isEnabled()输出为false则表明SASL认证关闭成功。
并且在build.gradle(Module)文件中与android{ … }并列的位置插入如下语句,定义getLayoutLibPath()方法并添加依赖。
这是因为关闭SASL认证需要获取Android系统权限,使用System.setProperty()方法进行设置,但是Android系统不能直接调用此方法,需要导入layoutlib.jar。又由于layoutlib.jar导入方式与普通jar包不同,因此颇费了些时间。但是按照图中的方法,此问题顺利解决。
4.需要在Android 5.0及以上版本中运行,否则程序会崩溃。
注意以上四点,Android客户端与ZooKeeper服务器的连接应该就可以成功建立。
源码示例
在此附上我的主要代码:
MainActivity.java
public class MainActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button1 = (Button) findViewById(R.id.button1);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//网络访问的操作不能在主线程,因此使用多线程
class MyThread implements Runnable {
@Override
public void run() {
try {
com.example.yt.zookeeper8.Constructor cons = new com.example.yt.zookeeper8.Constructor();
cons.Connect();
} catch (Exception e) {
System.out.println(e);
}
}
}
MyThread myThread = new MyThread();
new Thread(myThread).start();
}
});
}
}
Constructor.java
public class Constructor implements Watcher {
public static CountDownLatch connectSemaphore = new CountDownLatch(1);
public static void Connect() throws Exception{
System.setProperty(ZooKeeperSaslClient.ENABLE_CLIENT_SASL_KEY,"false"); //关闭SASL认证
System.out.println(ZooKeeperSaslClient.isEnabled());
ZooKeeper zooKeeper = new ZooKeeper("192.168.3.6:2181", 12000, new Constructor());
try {
connectSemaphore.await();
// System.out.println(zooKeeper.getState());
}catch (InterruptedException e){
System.out.println("ZooKeeper session failed...");
}
System.out.println("ZooKeeper session established.");
zooKeeper.create("/DeviceList","DeviceList".getBytes(),Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);//新建节点
zooKeeper.close();
}
public void process(WatchedEvent event){
System.out.println("Receive watched event:"+event);
if (Event.KeeperState.SyncConnected == event.getState()){
connectSemaphore.countDown();
}
}
}