微服务节点使用 Spring Boot
会方便很多,在搭建 Spring Boot
的时候碰到了个不大不小的问题,在这里记录下。
主要情况是配置好了 Dubbo Spring Boot 启动 Provider 节点的时候发现异常,抛出了两个错误:
Caused by: org.apache.zookeeper.KeeperException$UnimplementedException: KeeperErrorCode = Unimplemented for ... Caused by: java.lang.IllegalStateException: KeeperErrorCode = Unimplemented for ...
检查堆栈发现是 Dubbo 建立 ZooKeeper 链接的时候,就直接抛出了异常:
// from com.alibaba.dubbo.registry.zookeeper.ZookeeperRegistry.doRegister(ZookeeperRegistry.java:116) ~[dubbo-2.6.2.jar:2.6.2] protected void doRegister(URL url) { try { this.zkClient.create(this.toUrlPath(url), url.getParameter("dynamic", true)); } catch (Throwable var3) { throw new RpcException("Failed to register " + url + " to zookeeper " + this.getUrl() + ", cause: " + var3.getMessage(), var3); } }
但是 ZooKeeper
的服务器配置是正常的,百思不得:
$ echo stat | nc localhost 2181 Zookeeper version: 3.4.13, built on 06/29/2018 04:05 GMT
然后继续查看 KeeperException$UnimplementedException
异常的定义,
`` public static class UnimplementedException extends KeeperException { public UnimplementedException() { super(Code.UNIMPLEMENTED); } } ```
对应的调用:
// @from org.apache.zookeeper.ZooKeeper.create public String create(final String path, byte data[], List<ACL> acl, CreateMode createMode, Stat stat, long ttl) throws KeeperException, InterruptedException { final String clientPath = path; PathUtils.validatePath(clientPath, createMode.isSequential()); EphemeralType.validateTTL(createMode, ttl); final String serverPath = prependChroot(clientPath); RequestHeader h = new RequestHeader(); setCreateHeader(createMode, h); Create2Response response = new Create2Response(); if (acl != null && acl.size() == 0) { throw new KeeperException.InvalidACLException(); } Record record = makeCreateRecord(createMode, serverPath, data, acl, ttl); ReplyHeader r = cnxn.submitRequest(h, record, response, null); if (r.getErr() != 0) { // 这里抛出的异常 throw KeeperException.create(KeeperException.Code.get(r.getErr()), clientPath); } if (stat != null) { DataTree.copyStat(response.getStat(), stat); } if (cnxn.chrootPath == null) { return response.getPath(); } else { return response.getPath().substring(cnxn.chrootPath.length()); } }
那么基本上可以判定 1、是 ZooKeeper 本身链接的问题,和 Dubbo 没有关系;2、实际上 ZooKeeper 本身的服务已经连接上,但 makeCreateRecord
方法调用出现了异常。
那么,可以得出结论是 Java 端和 ZooKeeper 服务端出现了通讯问题。然后发现日志上 jar 包的版本是 zookeeper-3.5.3-beta.jar
,同时查看了下 Manifest
内容如下:
Implementation-Title: org.apache.zookeeper Implementation-Version: 3.5.3-beta Implementation-Vendor: The Apache Software Foundation
然后再运行客户端查看了下服务器的版本,两者版本不一致,突然觉得应该是版本的问题。
$ echo stat | nc localhost 2181 Zookeeper version: 3.4.13, built on 06/29/2018 04:05 GMT
很明显 Java 端的版本比服务端的版本要新,那么考虑使用和服务端同个版本的 jar 包试试。
修改对应 build.gradle 的 dependencies 如下,不要纳入 dubbo-spring-boot-starter 提供的 ZooKeeper 的 jar 包
dependencies { // ... compile('com.alibaba.boot:dubbo-spring-boot-starter:0.2.0') { exclude(module: 'org.apache.zookeeper') } compile 'org.apache.zookeeper:zookeeper:3.4.13' }
然后再运行 gradle clean bootRun -x test
发现 Dubbo 正常启动,问题解决。
这个问题有点坑,同时很难发现,我又查了下对应的资料。 官方其实已经有对应说明 ,简单的说就是 ZooKeeper 3.5.x 和 ZooKeeper 3.4.x 有不兼容的情况。
而回过头来看 dubbo-spring-boot-starter
包的 pom.xml
定义,对应的 ZooKeeper 这块的引用是这样子的:
<dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> </dependency>
没有指定版本,也就是默认是取 Maven 库的最新版本
,目前是 3.5.4-beta
自然对应本地版本 3.4.13
就有冲突了(竟然不向下兼容,坑)。
相关讨论,看来被坑的不止我: https://stackoverflow.com/questions/35734590/apache-curator-unimplemented-errors-when-trying-to-create-znodes
最后顺便说一句,如果有用到 Spring Cloud 相关的 Zookeeper 组件,也要留个心眼:
dependencies { // ... compile('org.springframework.cloud:spring-cloud-starter-zookeeper-config') { exclude group: 'org.apache.zookeeper', module: 'zookeeper' } compile('org.springframework.cloud:spring-cloud-starter-zookeeper-discovery') { exclude group: 'org.apache.zookeeper', module: 'zookeeper' } compile 'org.apache.zookeeper:zookeeper:3.4.13' }
这样子就能保证统一引用的是指定版本的 ZooKeeper 的 jar 包了。
总结下, dubbo-spring-boot-starter
项目目前相对来说还是比较新,相关的文档还是没跟上,但是已经能够日常和生产环境使用了,还是推荐使用简化配置提高些开发效率。
- eof -