BadVersion -ZK报错

简介

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)
  • connectString: zk的url
  • sessionTimeout: session的超时时间
  • watcher: zookeeper实例的状态监视器,由于zk的创建过程是异步的,这里可以通过注册一个监视器,监听zk的状态,当状态变为 SyncConnected 后,即表示已经连接成功。

创建节点Node

API如下:

String create(final String path, byte data[], List<ACL> acl,  CreateMode createMode) //同步方式
create(final String path, byte data[], List<ACL> acl,  CreateMode createMode,  StringCallback cb, Object ctx)  //异步方式创建节点

删除节点

API

delete(final String path, int version)  //同步方式
delete(final String path, int version, VoidCallback cb,  Object ctx)  //异步方式
  • path:路径
  • version:期望版本号,相当于数据库的乐观锁,在node里面存放着状态信息stat,里面存放有版本号version,如果设置的期待版本号与节点里的不相同,会操作节点失败。当版本号设置为-1时,忽略节点的版本号。

获取子节点

提供的API

public List<String> getChildren(String path, boolean watch)  //同步
List<String> getChildren(final String path, Watcher watcher)  //同步

public void getChildren(String path, boolean watch, Children2Callback cb,Object ctx) //异步
  • watcher:观察节点的变化,包括节点的删除,子节点的创建、删除。需要注意的是,watcher是个一次性的东西,当它被回调的时候,将会从节点中删除,所以如果要一致监控该节点,需要反复注册watcher。

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)  //异步
  • version:与文章 删除节点 里的version一致
  • cb:异步回调接口

获取节点数据

提供的部分API

 public byte[] getData(final String path, Watcher watcher, Stat stat) //同步

public void getData(String path, boolean watch, DataCallback cb, Object ctx)  // 异步
  • watcher:当节点的数据发生变化时回调,也是一次性的
  • stat:如果需要获取节点的状态信息,传一个对象

判断节点是否存在

提供的部分API

public Stat exists(final String path, Watcher watcher)
public void exists(final String path, Watcher watcher,  StatCallback cb, Object ctx)
  • watcher:可以监听子节点的创建、删除,和节点的数据更新

项目中一直在使用zk作为注册中心,并且将一些需要动态配置的字段也存储在zk中,比如:邮箱密码(定期需要修改的),最近由于项目逻辑修改,需要将zk中配置的某个缓存进行修改,而由于这个操作是在mq中进行,而当时mq监听到多个操作消息,多线程同时并行,当运行时就报了下面这个错:

org.apache.zookeeper.KeeperException$BadVersionException: KeeperErrorCode = BadVersion for 

具体代码如下:

直接代码:
 String zk = ConfClient.getByApp("hoteleb","eb-parentSupplierCode");
        logger.info("supplier-zk:{}",zk);
        if(StringUtils.isEmpty(zk)){
            ConfClient.setByApp("hoteleb","eb-parentSupplierCode","P"+parentSupplierCode+"M");
        } else {
            String zk1 = zk.contains("P" + parentSupplierCode + "M") ? zk : zk.concat(",P" + parentSupplierCode + "M");
            boolean b = ConfClient.setByApp("hoteleb", "eb-parentSupplierCode", zk1);
            logger.info("update-zk:{},{}",b,ConfClient.getByApp("hoteleb","eb-parentSupplierCode"));
        }


ConfClient类提供调用接口:
public static boolean setByApp(String appId, String confKey, String confValue) {
        String path = "/path/" + appId + "/" + confKey;
        Stat successStat = null;

        try {
            Stat stat = getInstance().exists(path, true);
            if (stat == null) {
                createWithParent(path);
                stat = getInstance().exists(path, true);
            }
            //问题出现行

            successStat = zooKeeper.setData(path, confValue.getBytes(), stat.getVersion());
        } catch (Exception var6) {
            logger.error("confcenter Exception:" + var6.getMessage(), var6);
        }

        return successStat != null;
    }
zk中setData方法:
public Stat setData(final String path, byte data[], int version)
        throws KeeperException, InterruptedException
    {
        final String clientPath = path;
        PathUtils.validatePath(clientPath);

        final String serverPath = prependChroot(clientPath);

        RequestHeader h = new RequestHeader();
        h.setType(ZooDefs.OpCode.setData);
        SetDataRequest request = new SetDataRequest();
        request.setPath(serverPath);
        request.setData(data);
        request.setVersion(version);
        SetDataResponse response = new SetDataResponse();
        ReplyHeader r = cnxn.submitRequest(h, request, response, null);
        if (r.getErr() != 0) {
            throw KeeperException.create(KeeperException.Code.get(r.getErr()),
                    clientPath);
        }
        return response.getStat();
    }

 具体分析:由于多个线程同时触发 ConfClient.setByApp("hoteleb","eb-parentSupplierCode","P"+parentSupplierCode+"M");这个操作,其实最后都调用zk的setData方法,入参version就类似于乐观锁,控制版本号,假设当前操作版本为1.0.0,而当第一个线程操作完成后,当前版本应该已经改为1.0.1,而由于是并发操作,第二个线程的版本号还是1.0.0,因此在修改数据时就会报这个错。

猜你喜欢

转载自blog.csdn.net/fz13768884254/article/details/82426314
zk