说完本地暴露之后,我们说远程暴露,也是面试喜欢问的。
我们引用下官网的说法,与导出服务到本地相比,导出服务到远程的过程要复杂不少,其包含了服务导出与服务注册两个过程
那我们就分开两部分开始叙述
服务导出
我们debug
生成Invoker,我们不说了。自己debug进去,分析就好了,我们主要说这个
Exporter<?> exporter = protocol.export(wrapperInvoker),debug走进去
我们主要看这两个实现类,分别打上断点,然后放行,会发现首先进去RegistryProtocol这个类中,看类名,我们猜测是和注册有关,但是我们不是先说导出,在说服务注册吗?其实服务注册就是RegistryProtocol方法中,OK,我们看RegistryProtocol中的实现
@Override
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
// 导出服务
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);
// 获取注册中心 URL,以 zookeeper 注册中心为例,得到的示例 URL 如下:
// zookeeper://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.2&export=dubbo%3A%2F%2F172.17.48.52%3A20880%2Fcom.alibaba.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddemo-provider
URL registryUrl = getRegistryUrl(originInvoker);
// 根据 URL 加载 Registry 实现类,比如 ZookeeperRegistry
final Registry registry = getRegistry(originInvoker);
// 获取已注册的服务提供者 URL,比如:
// dubbo://172.17.48.52:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.2&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello
final URL registeredProviderUrl = getRegisteredProviderUrl(originInvoker);
boolean register = registedProviderUrl.getParameter("register", true);
// 向服务提供者与消费者注册表中注册服务提供者
ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registedProviderUrl);
//进行注册
if (register) {
register(registryUrl, registedProviderUrl);
ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true);
}
....
}
我们看源码,知道了,先导出服务,然后在想注册中心注册服务。我们先到导出服务源码:
还记得我们前面说的,有两个和protocol有关,一个是RegistryProtocol,一个是DubboProtocol,我们发现这次调用的是DubboProtocol的实现。
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
URL url = invoker.getUrl();
// export service.
String key = serviceKey(url);
DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
exporterMap.put(key, exporter);
....
//我们说URL是dubbo的载体,很多模块之间都是靠url来进行传递的,所以我们看这个方法对url做了什么
openServer(url);
optimizeSerialization(url);
return exporter;
}
看一下我们的url是什么
这是和我们暴露的服务有关的一些信息
如果服务没有开启,我们需要先创建服务,继续createService(url)
服务器的创建在bind中
我们知道dubbo底层是用netty的,所以直接进入nettyTransporter实现类
OK,其实到这里我们发现,openServer(url),其实就是创建netty服务器,如果你不了解netty。需要看了,因为面试官,问你远程服务暴露的时候,就会接着问netty的一些知识了。
自此,服务远程导出就完成了,我们开始注册
当我们开启完netty服务,然后返回一个export,我们回到RegistryProtocol.export方法中,
到注册这个地方,它传递了两个参数,registryUrl(zookeeper注册中心地址)和registedProviderUrl(服务暴露地址)
我们继续跟进:
创建注册中心:getRegistry():
public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) {
...
// 创建 Zookeeper 客户端,默认为 CuratorZookeeperTransporter
zkClient = zookeeperTransporter.connect(url);
// 添加状态监听器
zkClient.addStateListener(new StateListener() {
@Override
public void stateChanged(int state) {
if (state == RECONNECTED) {
try {
recover();
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
}
});
}
我们完成了创建 Zookeeper 客户端。接下来就是把服务注册到zk上
dubbo默认的是FailbackRegistry
不同的注册中心,进入不同的方法实现中,我们用的是zk,进入zookeeperRegistry实现类
在zk上,创建节点:
@Override
public void create(String path, boolean ephemeral) {
int i = path.lastIndexOf('/');
if (i > 0) {
String parentPath = path.substring(0, i);
if (!checkExists(parentPath)) {
create(parentPath, false);
}
}
//创建临时或持久节点
if (ephemeral) {
createEphemeral(path);
} else {
createPersistent(path);
}
}
到此关于服务注册的过程就分析完了。整个过程可简单总结为:先创建注册中心实例,之后再通过注册中心实例注册服务
总结
远程服务暴露过程,都做了哪些事情?
- 开启一个netty服务器
- 创建zk客户端
- 在zk上,创建节点
我只是做了一个简单的大概的总结,还有很多细节,需要读者debug,一步步分析。
到这里,dubbo服务暴露就简单的分析完了,源码的分析和理解,需要不断的去看,最权威的还是需要看文档dubbo服务导出
有不对的地方,望海涵,希望读者指出。
最后我们回到刚开始我们的面试题在想想:
服务暴露都做了哪些事
本地暴露和远程暴露的区别
为什么需要本地暴露
其实在远程暴露中,还能延伸很多提问:
服务提供者能实现失效踢出是根据什么原理?
dubbo中zookeeper做注册中心,如果注册中心集群都挂掉,那发布者和订阅者还能通信吗?
zookeeper实现分布式锁
zookeeper选举机制
BIO、NIO、AIO分别是什么?
说说你对Netty的了解?
同步与异步、阻塞与非阻塞的区别