前言
敖丙是我非常喜欢的技术博主,2020年开始关注的,慢慢的看着他成为2020年度博客之星Top1,对比2020年的自己,还是那个小菜鸡,所以开始学这些技术大牛,写写博客总结经验,平时有空反复打磨,也欢迎各位看官挑挑刺,互相学习
服务暴露
先贴一张Dubbo官方的架构图,我们今天涉及的内容是第0步和第1步
服务启动后,会调用spring容器的refresh(),后续会出文章讲讲refresh里的13个方法步骤1:spring容器启动,解析dubbo自定义标签,扫描dubbo.xsd文件,
步骤2:将Dubbo的ServiceBean注册到Spring容器中,ServiceBean实现了ApplicationListener接口
步骤3:spring容器刷新完,触发事件监听器,执行onApplicationEvent里的重写方法,执行export()——今天的主角
因为dubbo的源码中很多地方都涉及到spi机制,所以可以先看一下敖丙的阿里面试真题:Dubbo的SPI机制,理解了dubbo的spi机制之后,回来看服务暴露的源码才不至于一头雾水
大致的流程图
上图根据色块,主要分成了三个大步骤
- 检查配置,组装URL
- 根据协议和注册中心暴露服务
- 如果有注册中心则注册服务
我会在每个小节后面加一个指向上面这个流程图的链接,方便跳回来看看自己走到哪一步了,不至于迷失在源码的世界里
代码1:doExportUrlsFor1Protocol
- URL是dubbo中比较重要的一个概念,URL拼接参数,这些参数在之后的dubbo spi调用中起到很重要的作用,根据参数调用具体的实现类
- 配置文件中的服务提供者信息
protocol://username:password@host:port/path?key=value&key=value
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
String name = protocolConfig.getName();
if (name == null || name.length() == 0) {
name = "dubbo";
}
//处理host
//处理port
//设置参数到map,组装成URL
// 导出服务
String contextPath = protocolConfig.getContextpath();
if ((contextPath == null || contextPath.length() == 0) && provider != null) {
contextPath = provider.getContextpath();
}
URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);
if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.hasExtension(url.getProtocol())) {
url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.getExtension(url.getProtocol()).getConfigurator(url).configure(url);
}
//此处省略:服务暴露(详见代码2)
this.urls.add(url);
}
大致的流程图
代码2:判断scope
- scope=none时,不暴露
- scope为null或者scope!=remote时,本地暴露
- scope为null或者scope=remote时,远程暴露
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
//代码1,见上
String scope = url.getParameter(Constants.SCOPE_KEY);
// 当配置了scope = none时不暴露
if (!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {
// 如果不是remote则本地暴露 (只有remote才会进行远程暴露)
if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
//代码3:injvm
exportLocal(url);
}
// export to remote if the config is not local (export to local only when config is local)
if (!Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) {
... ...
}
}
}
大致的流程图
代码3:injvm协议
本地暴露时,采用的是injvm协议,之所以有本地暴露,是因为有些服务会存在自己调用自己的情况,有了本地暴露,就不需要走网络通信那一part了
private void exportLocal(URL url) {
//injvm
if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
URL local = URL.valueOf(url.toFullString())
.setProtocol(Constants.LOCAL_PROTOCOL)
.setHost(LOCALHOST)
.setPort(0);
StaticContext.getContext(Constants.SERVICE_IMPL_CLASS).put(url.getServiceKey(), getServiceClass(ref));
Exporter<?> exporter = protocol.export(
proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
exporters.add(exporter);
logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry");
}
}
最主要的是这一行代码,由于配置了protocol等于injvm,所以dubbo的spi机制会选InjvmProtocol,调用export生成一个InjvmExporter,放入Map中。
Exporter<?> exporter = protocol.export(
proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
大致的流程图
代码4:判断注册中心
if(registryURLs!=null&&!registryURLs.isEmpty()){
//如果一个服务有多个注册中心,则生成多个Exporter
for(URL registryURL:registryURLs){
... ...
}
}else{
//没有注册中心,则默认采用dubbo协议,进入DubboProtocol
Invoker<?> invoker=proxyFactory.getInvoker(ref,(Class)interfaceClass,url);
DelegateProviderMetaDataInvoker wrapperInvoker=new DelegateProviderMetaDataInvoker(invoker,this);
Exporter<?> exporter=protocol.export(wrapperInvoker);
exporters.add(exporter);
}
没有注册中心,则默认采用dubbo协议,进入DubboProtocol
大致的流程图
代码5:openServer
代码4中的protocol.export(),最终会进入到DubboProtocol的export方法中,
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
... ...
openServer(url);
optimizeSerialization(url);
return exporter;
}
private void openServer(URL url) {
// 寻找server
String key = url.getAddress();
//client也可以暴露一个只有server可以调用的服务
boolean isServer = url.getParameter(Constants.IS_SERVER_KEY, true);
if (isServer) {
ExchangeServer server = serverMap.get(key);
if (server == null) {
serverMap.put(key, createServer(url));
} else {
//server支持reset,配合override功能使用
server.reset(url);
}
}
}
大致的流程图
代码6:createServer
远程暴露的时候,需要创建Server进行网络请求的处理,默认是创建NettyServer
private ExchangeServer createServer(URL url) {
//默认开启server关闭时发送readonly事件
//拼接URL
... ...
if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str))
throw new RpcException("Unsupported server type: " + str + ", url: " + url);
url = url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME);
ExchangeServer server;
try {
server = Exchangers.bind(url, requestHandler);
} catch (RemotingException e) {
throw new RpcException("Fail to start server(url: " + url + ") " + e.getMessage(), e);
}
str = url.getParameter(Constants.CLIENT_KEY);
if (str != null && str.length() > 0) {
Set<String> supportedTypes = ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions();
if (!supportedTypes.contains(str)) {
throw new RpcException("Unsupported client type: " + str);
}
}
return server;
}
大致的流程图
代码7:Exchanger
代码6中涉及的Exchangers.bind(url, requestHandler),同样也采用了dubbo的spi机制,默认使用HeaderExchanger,封装请求响应模式,同步转异步(这里涉及另一个知识点,下次再单独出一篇说明)
@SPI(HeaderExchanger.NAME)
public interface Exchanger {
... ...
}
public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
}
大致的流程图
代码8:Transport
getTransporter也是使用dubbo的spi机制,最后会调用到NettyTransporter.bind(),返回NettyServer
public static Server bind(URL url, ChannelHandler... handlers) throws RemotingException {
if (url == null) {
throw new IllegalArgumentException("url == null");
}
if (handlers == null || handlers.length == 0) {
throw new IllegalArgumentException("handlers == null");
}
ChannelHandler handler;
if (handlers.length == 1) {
handler = handlers[0];
} else {
handler = new ChannelHandlerDispatcher(handlers);
}
return getTransporter().bind(url, handler);
}
public Server bind(URL url, ChannelHandler listener) throws RemotingException {
return new NettyServer(url, listener);
}
大致的流程图
代码9:NettyServer
基于远程协议的暴露,需要NettyServer进行连接处理,也需要开启服务监听,处理其他进程发来的rpc请求
public Server bind(URL url, ChannelHandler listener) throws RemotingException {
return new NettyServer(url, listener);
}
public AbstractServer(URL url, ChannelHandler handler) throws RemotingException {
super(url, handler);
localAddress = getUrl().toInetSocketAddress();
String bindIp = getUrl().getParameter(Constants.BIND_IP_KEY, getUrl().getHost());
int bindPort = getUrl().getParameter(Constants.BIND_PORT_KEY, getUrl().getPort());
if (url.getParameter(Constants.ANYHOST_KEY, false) || NetUtils.isInvalidLocalHost(bindIp)) {
bindIp = NetUtils.ANYHOST;
}
bindAddress = new InetSocketAddress(bindIp, bindPort);
this.accepts = url.getParameter(Constants.ACCEPTS_KEY, Constants.DEFAULT_ACCEPTS);
this.idleTimeout = url.getParameter(Constants.IDLE_TIMEOUT_KEY, Constants.DEFAULT_IDLE_TIMEOUT);
try {
doOpen();
if (logger.isInfoEnabled()) {
logger.info("Start " + getClass().getSimpleName() + " bind " + getBindAddress() + ", export " + getLocalAddress());
}
} catch (Throwable t) {
throw new RemotingException(url.toInetSocketAddress(), null, "Failed to bind " + getClass().getSimpleName()
+ " on " + getLocalAddress() + ", cause: " + t.getMessage(), t);
}
//fixme replace this with better method
DataStore dataStore = ExtensionLoader.getExtensionLoader(DataStore.class).getDefaultExtension();
executor = (ExecutorService) dataStore.get(Constants.EXECUTOR_SERVICE_COMPONENT_KEY, Integer.toString(url.getPort()));
}
大致的流程图
总结
结语
学习就应该经常做减法,这样学起来才不至于被劝退,至于细节方面,肯定是要更多地接触后,才能理解的透彻。有些知识点可能没有讲透,我自己也是在摸索阶段,等之后有了更深地理解后,再回来补充吧~