ZooKeeper API 中,有两个包是我们经常打交道的,分别是 org.apache.zookeeper, org.apache.zookeeper.data 。前一个包提供了一些API操作zk,例如对节点node增删改查,后一个包定义了一些实体类,例如对zk 节点进行权限控制的ACL类、Id类等。
zk常用的API如下。
提供了下面几种api创建zk实体
ZooKeeper(String connectString, int sessionTimeout, Watcher watcher)
ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, boolean canBeReadOnly)
ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, long sessionId, byte[] sessionPasswd)
ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, long sessionId, byte[] sessionPasswd, boolean canBeReadOnly)
API如下:
String create(final String path, byte data[], List acl, CreateMode createMode) //同步方式
create(final String path, byte data[], List acl, CreateMode createMode, StringCallback cb, Object ctx) //异步方式创建节点
API
delete(final String path, int version) //同步方式
delete(final String path, int version, VoidCallback cb, Object ctx) //异步方式
提供的API
public List getChildren(String path, boolean watch) //同步
List getChildren(final String path, Watcher watcher) //同步
public void getChildren(String path, boolean watch, Children2Callback cb,Object ctx) //异步
getChildren返回的子节点路径是路径名,不是全路径,例如节点路径是/node,子节点的路径是/node/child,通过节点 getChildren 获取的路径是 child。
API
public Stat setData(final String path, byte data[], int version) //同步
public void setData(final String path, byte data[], int version, StatCallback cb, Object ctx) //异步
提供的部分API
public byte[] getData(final String path, Watcher watcher, Stat stat) //同步
public void getData(String path, boolean watch, DataCallback cb, Object ctx) // 异步
提供的部分API
public Stat exists(final String path, Watcher watcher)
public void exists(final String path, Watcher watcher, StatCallback cb, Object ctx)
项目中一直在使用zk作为注册中心,并且将一些需要动态配置的字段也存储在zk中,比如:邮箱密码(定期需要修改的),最近由于项目逻辑修改,需要将zk中配置的某个缓存进行修改,而由于这个操作是在mq中进行,而当时mq监听到多个操作消息,多线程同时并行,当运行时就报了下面这个错:
org.apache.zookeeper.KeeperException$BadVersionException: KeeperErrorCode = BadVersion for
具体代码如下:
直接代码:
String zk = ConfClient.getByApp("test","code");
logger.info("supplier-zk:{}",zk);
if(StringUtils.isEmpty(zk)){
ConfClient.setByApp("test","code","P"+code+"M");
} else {
String zk1 = zk.contains("P" + code+ "M") ? zk : zk.concat(",P" + code+ "M");
boolean b = ConfClient.setByApp("test", "code", zk1);
logger.info("update-zk:{},{}",b,ConfClient.getByApp("test","code"));
}
//问题出现行
successStat = zooKeeper.setData(path, value.getBytes(), ver.getVersion());
} catch (Exception var6) {
logger.error("confcenter Exception:" + var6.getMessage(), var6);
}
具体分析:由于多个线程同时触发 ConfClient.setByApp("test","code","P"+code+"M");这个操作,其实最后都调用zk的setData方法,入参version就类似于乐观锁,控制版本号,假设当前操作版本为1.0.0,而当第一个线程操作完成后,当前版本应该已经改为1.0.1,而由于是并发操作,第二个线程的版本号还是1.0.0,因此在修改数据时就会报这个错。