微服务节点使用 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 -