本节将详细分析Dubbo服务提供者的启动流程,请带着如下几个疑问进行本节的阅读,因为这几个问题将是接下来几篇文章分析的重点内容。
从上文中我们得知,服务提供者启动的核心入口为ServiceBean,本节将从源码级别详细剖析ServcieBean的实现原理,即Dubbo服务提供者的启动流程,ServiceBean的继承层次如图所示,dubbo:service标签的所有属性都被封装在此类图结构中。
1、源码分析ServiceBean#afterPropertiesSet
ServiceBean#afterPropertiesSet
if (getProvider() == null) { // @1
Map provide
ConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ProviderConfig.class, false, false); // @2
// ...... 具体解析代码省略。
}
}
Step1:如果provider为空,说明dubbo:service标签未设置provider属性,如果一个dubbo:provider标签,则取该实例,如果存在多个dubbo:provider配置则provider属性不能为空,否则抛出异常:“Duplicate provider configs”。
ServiceBean#afterPropertiesSet
if (getApplication() == null
&& (getProvider() == null || getProvider().getApplication() == null)) {
Map applicationConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext,
ApplicationConfig.class, false, false);
// ...省略
}
Step2:如果application为空,则尝试从BeanFactory中查询dubbo:application实例,如果存在多个dubbo:application配置,则抛出异常:“Duplicate application configs”。
Step3:如果ServiceBean的module为空,则尝试从BeanFactory中查询dubbo:module实例,如果存在多个dubbo:module,则抛出异常:"Duplicate module configs: "。
Step4:尝试从BeanFactory中加载所有的注册中心,注意ServiceBean的List< RegistryConfig> registries属性,为注册中心集合。
Step5:尝试从BeanFacotry中加载一个监控中心,填充ServiceBean的MonitorConfig monitor属性,如果存在多个dubbo:monitor配置,则抛出"Duplicate monitor configs: "。
Step6:尝试从BeanFactory中加载所有的协议,注意:ServiceBean的List< ProtocolConfig> protocols是一个集合,也即一个服务可以通过多种协议暴露给消费者。
ServiceBean#afterPropertiesSet
if (getPath() == null || getPath().length() == 0) {
if (beanName != null && beanName.length() > 0 && getInterface() != null && getInterface().length() > 0 && beanName.startsWith(getInterface())) {
setPath(beanName);
}
}
Step7:设置ServiceBean的path属性,path属性存放的是dubbo:service的beanName(dubbo:service id)。
ServiceBean#afterPropertiesSet
if (!isDelay()) {
export();
}
Step8:如果为启用延迟暴露机制,则调用export暴露服务。首先看一下isDelay的实现,然后重点分析export的实现原理(服务暴露的整个实现原理)。
ServiceBean#isDelay
private boolean isDelay() {
Integer delay = getDelay();
ProviderConfig provider = getProvider();
if (delay == null && provider != null) {
delay = provider.getDelay();
}
return supportedApplicationListener && (delay == null || delay == -1);
}
如果有设置dubbo:service或dubbo:provider的属性delay,或配置delay为-1,都表示启用延迟机制,单位为毫秒,设置为-1,表示等到Spring容器初始化后再暴露服务。从这里也可以看出,Dubbo暴露服务的处理入口为ServiceBean#export—》ServiceConfig#export。
1.1 源码分析ServiceConfig#export 暴露服务
调用链:ServiceBean#afterPropertiesSet------>ServiceConfig#export
public synchronized void export() {
if (provider != null) {
if (export == null) {
export = provider.getExport();
}
if (delay == null) {
delay = provider.getDelay();
}
}
if (export != null && !export) { // @1
return;
}
if (delay != null && delay > 0) { // @2
delayExportExecutor.schedule(new Runnable() {
@Override
public void run() {
doExport();
}
}, delay, TimeUnit.MILLISECONDS);
} else {
doExport(); //@3
}
}
代码@1:判断是否暴露服务,由dubbo:service export="true|false"来指定。
代码@2:如果启用了delay机制,如果delay大于0,表示延迟多少毫秒后暴露服务,使用ScheduledExecutorService延迟调度,最终调用doExport方法。
代码@3:执行具体的暴露逻辑doExport,需要大家留意:delay=-1的处理逻辑(基于Spring事件机制触发)。
1.2 源码分析ServiceConfig#doExport暴露服务
调用链:ServiceBean#afterPropertiesSet—调用------>ServiceConfig#export------>ServiceConfig#doExport
ServiceConfig#checkDefault
private void checkDefault() {
if (provider == null) {
provider = new ProviderConfig();
}
appendProperties(provider);
}
Step1:如果dubbo:servce标签也就是ServiceBean的provider属性为空,调用appendProperties方法,填充默认属性,其具体加载顺序:
ServiceConfig#doExport
if (ref instanceof GenericService) {
interfaceClass = GenericService.class;
if (StringUtils.isEmpty(generic)) {
generic = Boolean.TRUE.toString();
}
} else {
try {
interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
.getContextClassLoader());
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
checkInterfaceAndMethods(interfaceClass, methods);
checkRef();
generic = Boolean.FALSE.toString();
}
Step2:校验ref与interface属性。如果ref是GenericService,则为dubbo的泛化实现,然后验证interface接口与ref引用的类型是否一致。
ServiceConfig#doExport
if (local != null) {
if ("true".equals(local)) {
local = interfaceName + "Local";
}
Class> localClass;
try {
localClass = ClassHelper.forNameWithThreadContextClassLoader(local);
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
if (!interfaceClass.isAssignableFrom(localClass)) {
throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceName);
}
}
Step3:dubbo:service local机制,已经废弃,被stub属性所替换。
Step4:处理本地存根Stub,
ServiceConfig#doExport
checkApplication();
checkRegistry();
checkProtocol();
appendProperties(this);
Step5:校验ServiceBean的application、registry、protocol是否为空,并从系统属性(优先)、资源文件中填充其属性。
系统属性、资源文件属性的配置如下:
application dubbo.application.属性名,例如 dubbo.application.name
registry dubbo.registry.属性名,例如 dubbo.registry.address
protocol dubbo.protocol.属性名,例如 dubbo.protocol.port
service dubbo.service.属性名,例如 dubbo.service.stub
ServiceConfig#doExport
checkStubAndMock(interfaceClass);
Step6:校验stub、mock类的合理性,是否是interface的实现类。
ServiceConfig#doExport
doExportUrls();
Step7:执行doExportUrls()方法暴露服务,接下来会重点分析该方法。
ServiceConfig#doExport
ProviderModel providerModel = new ProviderModel(getUniqueServiceName(), this, ref);
ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel);
Step8:将服务提供者信息注册到ApplicationModel实例中。
1.3 源码分析ServiceConfig#doExportUrls暴露服务具体实现逻辑
调用链:ServiceBean#afterPropertiesSet------>ServiceConfig#export------>ServiceConfig#doExport
private void doExportUrls() {
List registryURLs = loadRegistries(true); // @1
for (ProtocolConfig protocolConfig : protocols) {
doExportUrlsFor1Protocol(protocolConfig, registryURLs); // @2
}
}
代码@1:首先遍历ServiceBean的List< RegistryConfig> registries(所有注册中心的配置信息),然后将地址封装成URL对象,关于注册中心的所有配置属性,最终转换成url的属性(?属性名=属性值),loadRegistries(true),参数的意思:true,代表服务提供者,false:代表服务消费者,如果是服务提供者,则检测注册中心的配置,如果配置了register=“false”,则忽略该地址,如果是服务消费者,并配置了subscribe="false"则表示不从该注册中心订阅服务,故也不返回,一个注册中心URL示例:
registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.0&pid=7072&qos.port=22222®istry=zookeeper×tamp=1527308268041
代码@2:然后遍历配置的所有协议,根据每个协议,向注册中心暴露服务,接下来重点分析doExportUrlsFor1Protocol方法的实现细节。
1.4 源码分析doExportUrlsFor1Protocol
调用链:ServiceBean#afterPropertiesSet------>ServiceConfig#export------>ServiceConfig#doExport------>ServiceConfig#doExportUrlsFor1Protocol
ServiceConfig#doExportUrlsFor1Protocol
String name = protocolConfig.getName();
if (name == null || name.length() == 0) {
name = "dubbo";
}
Map map = new HashMap();
map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);
map.put(Constants.DUBBO_VERSION_KEY, Version.getVersion());
map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
if (ConfigUtils.getPid() > 0) {
map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
}
appendParameters(map, application);
appendParameters(map, module);
appendParameters(map, provider, Constants.DEFAULT_KEY);
appendParameters(map, protocolConfig);
appendParameters(map, this);
Step1:用Map存储该协议的所有配置参数,包括协议名称、dubbo版本、当前系统时间戳、进程ID、application配置、module配置、默认服务提供者参数(ProviderConfig)、协议配置、服务提供Dubbo:service的属性。
ServiceConfig#doExportUrlsFor1Protocol
if (methods != null && !methods.isEmpty()) {
for (MethodConfig method : methods) {
appendParameters(map, method, method.getName());
String retryKey = method.getName() + ".retry";
if (map.containsKey(retryKey)) {
String retryValue = map.remove(retryKey);
if ("false".equals(retryValue)) {
map.put(method.getName() + ".retries", "0");
}
}
List arguments = method.getArguments();
if (arguments != null && !arguments.isEmpty()) {
for (ArgumentConfig argument : arguments) {
// convert argument type
if (argument.getType() != null && argument.getType().length() > 0) {
Method[] methods = interfaceClass.getMethods();
// visit all methods
if (methods != null && methods.length > 0) {
for (int i = 0; i < methods.length; i++) {
String methodName = methods[i].getName();
// target the method, and get its signature
if (methodName.equals(method.getName())) {
Class>[] argtypes = methods[i].getParameterTypes();
// one callback in the method
if (argument.getIndex() != -1) {
if (argtypes[argument.getIndex()].getName().equals(argument.getType())) {
appendParameters(map, argument, method.getName() + "." + argument.getIndex());
} else {
throw new IllegalArgumentException("argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" +
argument.getType());
}
} else {
// multiple callbacks in the method
for (int j = 0; j < argtypes.length; j++) {
Class> argclazz = argtypes[j];
if (argclazz.getName().equals(argument.getType())) {
appendParameters(map, argument, method.getName() + "." + j);
if (argument.getIndex() != -1 && argument.getIndex() != j) {
throw new IllegalArgumentException("argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ",
type:" + argument.getType());
}
}
}
}
}
}
}
} else if (argument.getIndex() != -1) {
appendParameters(map, argument, method.getName() + "." + argument.getIndex());
} else {
throw new IllegalArgumentException("argument config must set index or type attribute.eg: or ");
}
}
}
} // end of methods for
}
Step2:如果dubbo:service有dubbo:method子标签,则dubbo:method以及其子标签的配置属性,都存入到Map中,属性名称加上对应的方法名作为前缀。dubbo:method的子标签dubbo:argument,其键为方法名.参数序号。
ServiceConfig#doExportUrlsFor1Protocol
if (ProtocolUtils.isGeneric(generic)) {
map.put(Constants.GENERIC_KEY, generic);
map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
} else {
String revision = Version.getVersion(interfaceClass, version);
if (revision != null && revision.length() > 0) {
map.put("revision", revision);
}
String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
if (methods.length == 0) {
logger.warn("NO method found in service interface " + interfaceClass.getName());
map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
} else {
map.put(Constants.METHODS_KEY, StringUtils.join(new HashSet(Arrays.asList(methods)), ","));
}
}
Step3:添加methods键值对,存放dubbo:service的所有方法名,多个方法名用,隔开,如果是泛化实现,填充genric=true,methods为"*";
ServiceConfig#doExportUrlsFor1Protocol
if (!ConfigUtils.isEmpty(token)) {
if (ConfigUtils.isDefault(token)) {
map.put(Constants.TOKEN_KEY, UUID.randomUUID().toString());
} else {
map.put(Constants.TOKEN_KEY, token);
}
}
Step4:根据是否开启令牌机制,如果开启,设置token键,值为静态值或uuid。
ServiceConfig#doExportUrlsFor1Protocol
if (Constants.LOCAL_PROTOCOL.equals(protocolConfig.getName())) {
protocolConfig.setRegister(false);
map.put("notify", "false");
}
Step5:如果协议为本地协议(injvm),则设置protocolConfig#register属性为false,表示不向注册中心注册服务,在map中存储键为notify,值为false,表示当注册中心监听到服务提供者发送变化(服务提供者增加、服务提供者减少等事件时不通知。
ServiceConfig#doExportUrlsFor1Protocol
// export service
String contextPath = protocolConfig.getContextpath();
if ((contextPath == null || contextPath.length() == 0) && provider != null) {
contextPath = provider.getContextpath();
}
Step6:设置协议的contextPath,如果未配置,默认为/interfacename
ServiceConfig#doExportUrlsFor1Protocol
String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
Integer port = this.findConfigedPorts(protocolConfig, name, map);
Step7:解析服务提供者的IP地址与端口。
服务IP地址解析顺序:(序号越小越优先)
判断IP地址是否符合要求的标准是:
public static boolean isInvalidLocalHost(String host) {
return host == null
|| host.length() == 0
|| host.equalsIgnoreCase("localhost")
|| host.equals("0.0.0.0")
|| (LOCAL_IP_PATTERN.matcher(host).matches());
}
Socket socket = new Socket();
try {
SocketAddress addr = new InetSocketAddress(registryURL.getHost(), registryURL.getPort());
socket.connect(addr, 1000);
hostToBind = socket.getLocalAddress().getHostAddress();
break;
} finally {
try {
socket.close();
} catch (Throwable e) {
}
}
服务提供者端口解析顺序:(序号越小越优先)
ServiceConfig#doExportUrlsFor1Protocol
URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);
Step8:根据协议名称、协议host、协议端口、contextPath、相关配置属性(application、module、provider、protocolConfig、service及其子标签)构建服务提供者URI。
URL运行效果图:
以dubbo协议为例,展示最终服务提供者的URL信息如下:dubbo://192.168.56.1:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&bind.ip=192.168.56.1&bind.port=20880&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=5916&qos.port=22222&side=provider×tamp=1527168070857
ServiceConfig#doExportUrlsFor1Protocol
String scope = url.getParameter(Constants.SCOPE_KEY);
// don't export when none is configured
if (!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {
// 接口暴露实现逻辑
}
Step9:获取dubbo:service标签的scope属性,其可选值为none(不暴露)、local(本地)、remote(远程),如果配置为none,则不暴露。默认为local。
ServiceConfig#doExportUrlsFor1Protocol
// export to local if the config is not remote (export to remote only when config is remote)
if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) { // @1
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)) { // @2
if (logger.isInfoEnabled()) {
logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
}
if (registryURLs != null && !registryURLs.isEmpty()) { // @3
for (URL registryURL : registryURLs) {
url = url.addParameterIfAbsent(Constants.DYNAMIC_KEY, registryURL.getParameter(Constants.DYNAMIC_KEY)); // @4
URL monitorUrl = loadMonitor(registryURL); // @5
if (monitorUrl != null) {
url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());
}
if (logger.isInfoEnabled()) {
logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
}
Invoker> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString())); // @6
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
Exporter> exporter = protocol.export(wrapperInvoker); // @7
exporters.add(exporter);
}
} else {
Invoker> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
Exporter> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter);
}
}
Step10:根据scope来暴露服务,如果scope不配置,则默认本地与远程都会暴露,如果配置成local或remote,那就只能是二选一。
代码@1:如果scope不为remote,则先在本地暴露(injvm):,具体暴露服务的具体实现,将在remote 模式中详细分析。
代码@2:如果scope不为local,则将服务暴露在远程。
代码@3:remote方式,检测当前配置的所有注册中心,如果注册中心不为空,则遍历注册中心,将服务依次在不同的注册中心进行注册。
代码@4:如果dubbo:service的dynamic属性未配置, 尝试取dubbo:registry的dynamic属性,该属性的作用是否启用动态注册,如果设置为false,服务注册后,其状态显示为disable,需要人工启用,当服务不可用时,也不会自动移除,同样需要人工处理,此属性不要在生产环境上配置。
代码@5:根据注册中心url(注册中心url),构建监控中心的URL,如果监控中心URL不为空,则在服务提供者URL上追加monitor,其值为监控中心url(已编码)。
代码@6:通过动态代理机制创建Invoker,dubbo的远程调用实现类。
Dubbo远程调用器如何构建,这里不详细深入,重点关注WrapperInvoker的url为:registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.0&export=dubbo%3A%2F%2F192.168.56.1%3A20880%2Fcom.alibaba.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddemo-provider%26bind.ip%3D192.168.56.1%26bind.port%3D20880%26dubbo%3D2.0.0%26generic%3Dfalse%26interface%3Dcom.alibaba.dubbo.demo.DemoService%26methods%3DsayHello%26pid%3D6328%26qos.port%3D22222%26side%3Dprovider%26timestamp%3D1527255510215&pid=6328&qos.port=22222®istry=zookeeper×tamp=1527255510202,这里有两个重点值得关注:
1.5 源码分析RegistryProtocol#export方法
调用链:ServiceBean#afterPropertiesSet------>ServiceConfig#export------>ServiceConfig#doExport------>ServiceConfig#doExportUrlsFor1Protocol------>RegistryProtocol#export
RegistryProtocol#export
@Override
public Exporter export(final Invoker originInvoker) throws RpcException {
//export invoker
final ExporterChangeableWrapper exporter = doLocalExport(originInvoker); // @1
URL registryUrl = getRegistryUrl(originInvoker); // @2
//registry provider
final Registry registry = getRegistry(originInvoker); // @3
final URL registedProviderUrl = getRegistedProviderUrl(originInvoker); // @4start
//to judge to delay publish whether or not
boolean register = registedProviderUrl.getParameter("register", true);
ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registedProviderUrl);
if (register) {
register(registryUrl, registedProviderUrl); // @4 end
ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true);
}
// Subscribe the override data
// FIXME When the provider subscribes, it will affect the scene : a certain JVM exposes the service and call the same service. Because the subscribed is cached key with the name of the service, it causes the subscription information to cover.
final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registedProviderUrl); // @5 start
final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener); // @5 end
//Ensure that a new exporter instance is returned every time export
return new DestroyableExporter(exporter, originInvoker, overrideSubscribeUrl, registedProviderUrl);
}
代码@1:启动服务提供者服务,监听指定端口,准备服务消费者的请求,这里其实就是从WrapperInvoker中的url(注册中心url)中提取export属性,描述服务提供者的url,然后启动服务提供者。
从上图中,可以看出,将调用DubboProtocol#export完成dubbo服务的启动,利用netty构建一个微型服务端,监听端口,准备接受服务消费者的网络请求,本节旨在梳理其启动流程,具体实现细节,将在后续章节中详解,这里我们只要知道,< dubbo:protocol name=“dubbo” port=“20880” />,会再此次监听该端口,然后将dubbo:service的服务handler加入到命令处理器中,当有消息消费者连接该端口时,通过网络解包,将需要调用的服务和参数等信息解析处理后,转交给对应的服务实现类处理即可。
代码@2:获取真实注册中心的URL,例如zookeeper注册中心的URL:zookeeper://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.0&export=dubbo%3A%2F%2F192.168.56.1%3A20880%2Fcom.alibaba.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddemo-provider%26bind.ip%3D192.168.56.1%26bind.port%3D20880%26dubbo%3D2.0.0%26generic%3Dfalse%26interface%3Dcom.alibaba.dubbo.demo.DemoService%26methods%3DsayHello%26pid%3D10252%26qos.port%3D22222%26side%3Dprovider%26timestamp%3D1527263060882&pid=10252&qos.port=22222×tamp=1527263060867
代码@3:根据注册中心URL,从注册中心工厂中获取指定的注册中心实现类:zookeeper注册中心的实现类为:ZookeeperRegistry
代码@4:获取服务提供者URL中的register属性,如果为true,则调用注册中心的ZookeeperRegistry#register方法向注册中心注册服务(实际由其父类FailbackRegistry实现)。
代码@5:服务提供者向注册中心订阅自己,主要是为了服务提供者URL发送变化后重新暴露服务,当然,会将dubbo:reference的check属性设置为false。
到这里就对文章开头提到的问题1,问题2做了一个解答,其与注册中心的心跳机制等将在后续章节中详细分析。
文字看起来可能不是很直观,现整理一下Dubbo服务提供者启动流程图如下:
本文重点梳理了Dubbo服务提供者启动流程,其中Dubbo服务提供者在指定端口监听服务的启动流程将在下一节中详细分析。
欢迎加笔者微信号(dingwpmz),加群探讨,笔者优质专栏目录:
1、源码分析RocketMQ专栏(40篇+)
2、源码分析Sentinel专栏(12篇+)
3、源码分析Dubbo专栏(28篇+)
4、源码分析Mybatis专栏
5、源码分析Netty专栏(18篇+)
6、源码分析JUC专栏
7、源码分析Elasticjob专栏
8、Elasticsearch专栏(20篇+)
9、源码分析MyCat专栏