上面我们分析了服务暴露过程之网络通信的创建,接下来,我们跟踪下dubbo怎么将服务的URL信息注册到注册中心
- dubbo封装了多个注册中心,屏蔽了不同注册中心之间的差异,提供了统一对外的API:
public interface RegistryService {
/**
* 注册数据,比如:提供者地址,消费者地址,路由规则,覆盖规则,等数据。
*/
void register(URL url);
/**
* 取消注册.
*/
void unregister(URL url);
/**
* 订阅符合条件的已注册数据,当有注册数据变更时自动推送.
*/
void subscribe(URL url, NotifyListener listener);
/**
* 取消订阅.
*/
void unsubscribe(URL url, NotifyListener listener);
/**
* 查询符合条件的已注册数据,与订阅的推模式相对应,这里为拉模式,只返回一次结果。
*/
List<URL> lookup(URL url);
- 从API接口中可以看到所有的注册中都提供了 注册(register),取消注册(unregister),订阅(subscribe),取消订阅(unsubscribe),查找(lookup)等几个操作方法。我们主要跟踪下注册方法的调用,从上面通信服务的创建,上次分析DUBBO在spring容器加载完毕后触发监听事件,调用Protocol.export对服务暴露,同时dubbo会先调用RegistryProtocol.export方法:
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
//此处默认是开启通信服务,默认调用的dubboProtocol,很关键invoker也不容易懂的地方
//export
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);
//本地的通信服务开启成功后,将信息注册到注册中心,以供客户端发现
//registry provider
//获取注册中心服务实例信息,同样是通过扩展加载获取
final Registry registry = getRegistry(originInvoker);
//获取要注册服务的URL信息
final URL registedProviderUrl = getRegistedProviderUrl(originInvoker);
//将服务的URL信息注册到注册中心,以供客户端发现(dubbo://192.168.4.82:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&loadbalance=roundrobin&methods=sayHello&owner=william&pid=3996&scope=remote&side=provider×tamp=1504851361655)
//此处调用dubbo的注册组件服务进行服务对外注册
registry.register(registedProviderUrl);
//省略无关代码
........
}
根据RegistryProtocol.export方法可以看到,此方法通过getRegistry(originInvoker)获取Registry(注册中心)实例,然后调用实例的register方法进行注册,那么继续根据获取Registry的代码:
private Registry getRegistry(final Invoker<?> originInvoker) {
URL registryUrl = originInvoker.getUrl();
if (Constants.REGISTRY_PROTOCOL.equals(registryUrl.getProtocol())) {
//此处重置了URL的 protocol参数,因为原先的protocol参数值应该为registry
String protocol = registryUrl.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_DIRECTORY);
//注冊 protocol参数值默认为dubbo,在配置文件中可以动态设置 样例中设置为multicast
registryUrl = registryUrl.setProtocol(protocol).removeParameter(Constants.REGISTRY_KEY);
}
//通过registryFactory自适应实例获取Registry 实际调用的是ExtensionLoader.getExtension(protocol);
//zookeeper 注冊中心在我们实际项目中用的比较多下来我们主要分析下zookeeper注册中心的流程以及代码
return registryFactory.getRegistry(registryUrl);
}
这里的可以看到是通过registryFactory的getRegistry获取Registry实例的,那么registryFactory实例具体是哪个,这个与
Protocol的实例获取一致,是获取了一个动态生成的自适应类RegistryFactory$Adpative的实例,这个类实际调用ExtensionLoader.getExtension(protocol)获取registryFactory,如果是zookeeper的话,URL的protocol参数值应该是zookeeper,这样实际上是调用的ZookeeperRegistryFactory.getRegistry方法。
- 动态创建RegistryFactory$Adpative类具体代码(此代码是动态生成的,dubbo的源码中无此类文件):
public class RegistryFactory$Adpative implements com.alibaba.dubbo.registry.RegistryFactory {
public com.alibaba.dubbo.registry.Registry getRegistry(com.alibaba.dubbo.common.URL arg0) {
if (arg0 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg0;
String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
if(extName == null)
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.registry.RegistryFactory) name from url(" + url.toString() + ") use keys([protocol])");
com.alibaba.dubbo.registry.RegistryFactory extension = (com.alibaba.dubbo.registry.RegistryFactory)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.registry.RegistryFactory.class).getExtension(extName);
return extension.getRegistry(arg0);
}
}
其它的注册中心获取的方式是一样,只是URL的protocol参数值不同,下来我们看下ZookeeperRegistryFactory.getRegistry的代码:
public Registry getRegistry(URL url) {
url = url.setPath(RegistryService.class.getName())
.addParameter(Constants.INTERFACE_KEY, RegistryService.class.getName())
.removeParameters(Constants.EXPORT_KEY, Constants.REFER_KEY);
String key = url.toServiceString();
// 锁定注册中心获取过程,保证注册中心单一实例
LOCK.lock();
try {
//首先在缓存中获取,当缓存中没有时创建,通过锁保证创建过程线程安全,创建单一的实例信息
Registry registry = REGISTRIES.get(key);
if (registry != null) {
return registry;
}
registry = createRegistry(url);
if (registry == null) {
throw new IllegalStateException("Can not create registry " + url);
}
REGISTRIES.put(key, registry);
return registry;
} finally {
// 释放锁
LOCK.unlock();
}
}
public Registry createRegistry(URL url) {
//Zookeeper 注册中心创建,直接new ZookeeperRegistry
return new ZookeeperRegistry(url, zookeeperTransporter);
}
上面这个两个方法getRegistry,createRegistry分别在AbstractRegistryFactory,ZookeeperRegistryFactory类中,AbstractRegistryFactory.getRegistry是所有注册中心通用部分,目的是使创建过程线程安全,保证注册中心单一实例,并将实例缓存到内存中,方便下次使用。ZookeeperRegistryFactory.createRegistry 创建ZookeeperRegistry注册中心
public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) {
super(url);
if (url.isAnyHost()) {
throw new IllegalStateException("registry address == null");
}
String group = url.getParameter(Constants.GROUP_KEY, DEFAULT_ROOT);
if (!group.startsWith(Constants.PATH_SEPARATOR)) {
group = Constants.PATH_SEPARATOR + group;
}
this.root = group;
//创建zookeeper连接客户端
zkClient = zookeeperTransporter.connect(url);
//添加状态监听
zkClient.addStateListener(new StateListener() {
public void stateChanged(int state) {
if (state == RECONNECTED) {
try {
recover();
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
}
});
}
zookeeperTransporter 由ZookeeperRegistryFactory传入,此类是zookeeperTransporter的网络通信统一接口,有两个实现类ZkclientZookeeperTransporter,CuratorZookeeperTransporter,分别创建Curator与ZK类型的客户端。其中ZkclientZookeeperClient是对第三方org.I0Itec.zkclient.ZkClient的封装,CuratorZookeeperClient是对com.netflix.curator.framework.CuratorFramework的封装,两个都是操作zookeeper的客户端。从此可以看到ZookeeperRegistry实例中zkClient引用其实就是对org.I0Itec.zkclient.ZkClient或者对com.netflix.curator.framework.CuratorFramework的引用,ZookeeperRegistry下的注册,取消注册,订阅,取消订阅等方法都是通过zkClient实例来操作zookeeper服务实现的。至于zookeeper服务怎么注册path取消注册path,订阅,取消订阅,可以查看zookeeper的帮助文档学习。
- 总结
通过上面的分析,可以看到注册首先会通过registryFactory获取一个定义的Registry,然后通过Registry操作具体的注册中心。本文中分析的zookeeper作为注册中心,它的流程如下:
RegistryFactory$Adpative.getRegistry()->ZookeeperRegistryFactory.getRegistry()->ZookeeperRegistryFactory.createRegistry()->new ZookeeperRegistry()---new ZkClient()
- 备注
zookeeperTransporter 获取 ZkclientZookeeperTransporter,CuratorZookeeperTransporter的哪一个是由SPI机制实现的,后续SPI源码解析再细述。