最近,使用Zookeeper实现一个简单的配置中心。 其间,使用Curator(毕竟都是Apache旗下)作为Zookeeper的客户端实现。
但是,当整合进Dubbo时,发现出现Curator版本不兼容。 Dubbo支持curator,但dubbo时间久远,当时的版本是curator: 1.1.10。而我使用的curator最新的2.11.1版本
不过查看Curator版本说明时,发现:
This is the first release of Apache Curator (it's numbered 2.0.0 because Apache Curator is a continuation of Netflix Curator which was in version 1.x).
curator 2.** 版本只是,当时Apache接手后用以区分Netflix的,基本api都保持1.*的,只是包名改为org.apache.*的命名规则
Dubbo因众所周知的原因,基本维护更新都处于停滞状态。 所以,想要用新版本的类库,都要靠自己动手实现了。
基本思路有两个:
- 从git中clone出dubbo源码,进行修改后,重新打入maven私库;
- 使用dubbo的扩展机制进行外部扩展,以外挂的方式进行增强扩展。
为了,后续维护方便,这里选择第2种方式。
那需要先来了解一下Dubbo的扩展方式。Dubbo设计之初,目标还是挺宏大的,从功能成熟度上可见一斑。 但是,阿里一贯作风就是开源等于丢弃,管杀不管埋(哈哈,吐槽ing)。
Dubbo本身提供了扩展点加载机制 。 主要基于JAVA的SPI机制。
而Dubbo针对Zookeeper的客户端实现也提供了SPI扩展点:
package com.alibaba.dubbo.remoting.zookeeper;
@SPI("zkclient")
public interface ZookeeperTransporter {
@Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY})
ZookeeperClient connect(URL url);
}
也即是,只需要通过SPI扩展实现自己的ZookeeperTransporter
即可直接扩展Dubbo的Zookeeper客户端。
第一步:
新建一个Maven工程:
pom.xml
<artifactId>roc-dubbo</artifactId>
<version>0.2.0</version>
<name>roc-dubbo</name>
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.5.3</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.11.1</version>
</dependency>
</dependencies>
第二步,实现AbstractZookeeperClient
Dubbo对于Zookeeper的使用,基本都已封装为com.alibaba.dubbo.remoting.zookeeper.ZookeeperClient
接口。另外还提供一个的抽象类com.alibaba.dubbo.remoting.zookeeper.support.AbstractZookeeperClient
。
所以,只需要继承AbstractZookeeperClient
就可以很方便的进行扩展。
Curator2ZookeeperClient.java
package com.roc.dubbo.remoting.zookeeper.curator2;
import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.remoting.zookeeper.ChildListener;
import com.alibaba.dubbo.remoting.zookeeper.StateListener;
import com.alibaba.dubbo.remoting.zookeeper.support.AbstractZookeeperClient;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.api.CuratorWatcher;
import org.apache.curator.framework.state.ConnectionState;
import org.apache.curator.framework.state.ConnectionStateListener;
import org.apache.curator.retry.RetryNTimes;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import java.util.List;
/**
* Created by roc on 2017/2/24.
*/
public class Curator2ZookeeperClient extends AbstractZookeeperClient<CuratorWatcher> {
private final CuratorFramework client;
public Curator2ZookeeperClient(URL url) {
super(url);
try {
CuratorFrameworkFactory.Builder builder = CuratorFrameworkFactory.builder()
.connectString(url.getBackupAddress())
.retryPolicy(new RetryNTimes(Integer.MAX_VALUE, 1000))
.connectionTimeoutMs(5000);
String authority = url.getAuthority();
if (authority != null && authority.length() > 0) {
builder = builder.authorization("digest", authority.getBytes());
}
client = builder.build();
client.getConnectionStateListenable().addListener(new ConnectionStateListener() {
@Override
public void stateChanged(CuratorFramework client, ConnectionState state) {
if (state == ConnectionState.LOST) {
Curator2ZookeeperClient.this.stateChanged(StateListener.DISCONNECTED);
} else if (state == ConnectionState.CONNECTED) {
Curator2ZookeeperClient.this.stateChanged(StateListener.CONNECTED);
} else if (state == ConnectionState.RECONNECTED) {
Curator2ZookeeperClient.this.stateChanged(StateListener.RECONNECTED);
}
}
});
client.start();
} catch (Exception e) {
logger.error("Zookeeper connection failed : " + e.getMessage());
throw new IllegalStateException(e.getMessage(), e);
}
}
public void delete(String path) {
try {
client.delete().forPath(path);
} catch (KeeperException.NoNodeException e) {
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
public List<String> getChildren(String path) {
try {
return client.getChildren().forPath(path);
} catch (KeeperException.NoNodeException e) {
return null;
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
public boolean isConnected() {
return client.getZookeeperClient().isConnected();
}
protected void doClose() {
client.close();
}
@Override
protected void createPersistent(String path) {
try {
client.create().forPath(path);
} catch (KeeperException.NodeExistsException e) {
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
@Override
protected void createEphemeral(String path) {
try {
client.create().withMode(CreateMode.EPHEMERAL).forPath(path);
} catch (KeeperException.NodeExistsException e) {
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
private class CuratorWatcherImpl implements CuratorWatcher {
private volatile ChildListener listener;
public CuratorWatcherImpl(ChildListener listener) {
this.listener = listener;
}
public void unwatch() {
this.listener = null;
}
@Override
public void process(WatchedEvent event) throws Exception {
if (listener != null) {
listener.childChanged(event.getPath(), client.getChildren().usingWatcher(this).forPath(event.getPath()));
}
}
}
protected CuratorWatcher createTargetChildListener(String path, ChildListener listener) {
return new CuratorWatcherImpl(listener);
}
protected List<String> addTargetChildListener(String path, CuratorWatcher listener) {
try {
return client.getChildren().usingWatcher(listener).forPath(path);
} catch (KeeperException.NoNodeException e) {
return null;
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
protected void removeTargetChildListener(String path, CuratorWatcher listener) {
((CuratorWatcherImpl) listener).unwatch();
}
}
第三步,实现ZookeeperTransporter
如上面所说,Dubbo实际上是通过ZookeeperTransporter
来获取com.alibaba.dubbo.remoting.zookeeper.ZookeeperClient
,从而进行ZooKeeper操作的。 所以,接下来需要实现自己的ZookeeperTransporter
。
Curator2ZookeeperTransporter.java
package com.roc.dubbo.remoting.zookeeper.curator2;
import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.remoting.zookeeper.ZookeeperClient;
import com.alibaba.dubbo.remoting.zookeeper.ZookeeperTransporter;
/**
* Created by roc on 2017/2/27.
*/
public class Curator2ZookeeperTransporter implements ZookeeperTransporter {
@Override
public ZookeeperClient connect(URL url) {
return new Curator2ZookeeperClient(url);
}
}
第四步,编写SPI注册文件
上文已经介绍过了,Dubbo利用SPI方式进行扩展。 按Dubbo的方式,需要提供META-INF/dubbo/{扩展接口的全类名}
。
这里,我们需要的是META-INF/dubbo/com.alibaba.dubbo.remoting.zookeeper.ZookeeperTransporter
文件:
META-INF/dubbo/com.alibaba.dubbo.remoting.zookeeper.ZookeeperTransporter:
curator2=com.roc.dubbo.remoting.zookeeper.curator2.Curator2ZookeeperTransporter
OK,将上面的工程编译打包后,就可以直接在Dubbo的<dubbo:registry ... client="curator2" />
中,直接使用扩展的curator2客户端了。
这里我们使用Spring Boot的Java-Base方式进行注册中心配置:
DubboProperties.java
/**
*
*/
package com.roc.dubbo.boot.configuration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import lombok.Data;
/**
* @author "Ren PengFei (ROC)"
* @date 2016年11月8日 下午6:09:05
* @description
*/
@ConfigurationProperties(prefix = "dubbo")
@Data
public class DubboProperties {
private String applicationName;
private String applicationOrganizatione;
private String applicationOwner;
private String applicationLoggerr;
private int applicationShutdownTimeout;
private String registryProtocol;
private String registryClient="zkclient";
private String registryAddress;
private String registryFile;
private String protocolName;
private int protocolPort;
private String providerLayer;
private String providerGroup;
private int timeout;
private int retries = 1;
private int delay = -1;
private String monitorProtocol = "registry";
private String consumerGroup;
}
@Bean
public RegistryConfig registryConfig() {
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setProtocol(dubboProperties.getRegistryProtocol());
registryConfig.setAddress(dubboProperties.getRegistryAddress());
registryConfig.setFile(dubboProperties.getRegistryFile());
registryConfig.setClient(dubboProperties.getRegistryClient());
return registryConfig;
}
到此为止,就完成了Dubbo的Curator2为Zookeeper客户端的扩展。 只需要在使用curator2的地方,引入上面的工程jar依赖就可以。不需要改动Dubbo代码。
Dubbo通过SPI的方式,可以对传输协议,io层实现,各种Filter,序列化等等进行方便的扩展。具体如下:
- 协议扩展
- 调用拦截扩展
- 引用监听扩展
- 暴露监听扩展
- 集群扩展
- 路由扩展
- 负载均衡扩展
- 合并结果扩展
- 注册中心扩展
- 监控中心扩展
- 扩展点加载扩展
- 动态代理扩展
- 编译器扩展
- 消息派发扩展
- 线程池扩展
- 序列化扩展
- 网络传输扩展
- 信息交换扩展
- 组网扩展
- Telnet命令扩展
- 状态检查扩展
- 容器扩展
- 页面扩展
- 缓存扩展
- 验证扩展
- 日志适配扩展