dubbo服务提供者由dubbo:service来定义,从前面可以看到,Spring把dubbo:service解析成一个ServiceBean,ServiceBean实现了ApplicationListener和InitializingBean接口,ServiceBean有个核心方法export,在这个方法中初始化服务提供者并且暴露远程服务。这个方法在bean初始化或容器中所有bean刷新完毕时被调用,根据provider的延迟设置决定,如果设置了延迟(delay属性)在bean初始化结束之后调用否则容易刷新事件中被调用,默认会延迟export,即在所有bean的刷新结束时间中被调用。
在com.alibaba.dubbo.config.spring.ServiceBean类下
public void onApplicationEvent(ApplicationEvent event) {
if (ContextRefreshedEvent.class.getName().equals(event.getClass().getName())) {
if (isDelay() && ! isExported() && ! isUnexported()) {
if (logger.isInfoEnabled()) {
logger.info("The service ready on spring started. service: " + getInterface());
}
export();
}
}
}
在export方法中,总体上export方法暴露服务时主要做了下列这些步骤:
服务需要一个端口来进行服务调用侦听,框架默认会从20880开始选定一个未被占用的端口来提供服务,也可以在配置中的port参数指定服务的端口:
com.alibaba.dubbo.config.ServiceConfig.doExportUrlsFor1Protocol(ProtocolConfig, List)
final int defaultPort = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(name).getDefaultPort();
if (port == null || port == 0) {
port = defaultPort;
}
if (port == null || port <= 0) {
port = getRandomPort(name);
if (port == null || port < 0) {
port = NetUtils.getAvailablePort(defaultPort);
putRandomPort(name, port);
}
logger.warn("Use random available port(" + port + ") for protocol " + name);
}
URL对象是dubbo框架中的核心模型,它携带了调用链路中注册中心、消费端配置、提供端配置等全部信息。无论是消费端代理还是提供端代理都需持有一个URL对象携带服务调用时的上下文信息。
在服务初始化时需要对注册中心进行访问,所以需要一个URL来跟注册中心通信,根据应用的注册中心配置来生成这个URL,URL协议是registry,包含了注册中心地址、端口,path部分是com.alibaba.dubbo.registry.RegistryService类名,其中还有dubbo版本、应用名称等信息放在URL的参数中,格式化之后是这种形式:registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=…
除了注册中心URL,还需要生成一个URL来携带服务本身的信息,协议由应用侧指定,在我们的示例配置中是dubbo协议,host是本机ip,端口是上面选中的随机或配置中指定的端口,path是服务对应的接口class全名(含包路径),添加side参数provider,dubbo版本号、服务方法名称、group等等,格式化之后是这种形式:
dubbo://127.0.0.1:20880/com.netease.haitao.mykaola.generic.api.InternalStaffServiceFacade?…&createInternalStaffOperDetail.timeout=10000&…&default.group=hzchenyunyun1&…&dubbo=3.0.0&…&methods=…&…&side=provider&…
这个url会被设置到registry URL的export属性中,这点很重要,后面的初始化过程是围绕registry URL,需要从这个export属性中拿到提供者服务URL。
如果scope属性没有被设置成remote,服务同时会在本地暴露,生成一个本地服务代理对象,这里会生成一个新的URL,协议是代表本地服务的injvm,host是127.0.0.1端口是0,生成一个InjvmExporter,这时本地调用dubbo接口时直接调用本地代理不走网络请求。生成Exporter的过程和生成远程Exporter的过程类似,在后面详细描述。
com.alibaba.dubbo.config.ServiceConfig.exportLocal(URL)
private void exportLocal(URL url) {
if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
URL local = URL.valueOf(url.toFullString())
.setProtocol(Constants.LOCAL_PROTOCOL)
.setHost(NetUtils.LOCALHOST)
.setPort(0);
Exporter> exporter = protocol.export(
proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
exporters.add(exporter);
logger.info("Export dubbo service " + interfaceClass.getName() +" to local registry");
}
}
如果scope属性没有被设置成local,生成远程服务代理,框架提供了ProxyFactory生成服务代理(dubbo提供JDK动态代理和Javaassist代理,默认使用Javaassist,使用者也可以替换成其他的代理,这里不展开),它会生成一个Invoker,该Invoker是服务ref的一个代理包含了携带服务信息的URL对象。这里的ref就是在dubbo:service中配置的ref属性,指定服务的具体实现类,Invoker的invoke方法被调用时,最终会调用到ref指定的服务实现。这里的ProxyFactory使用ExtensionLoader的自适应扩展点加载,如应用侧没有特别指定,默认的是JavassistProxyFactory。
com.alibaba.dubbo.config.ServiceConfig.doExportUrlsFor1Protocol(ProtocolConfig, List)
//如果配置不是local则暴露为远程服务.(配置为local,则表示只暴露远程服务)
if (! Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope) ){
if (logger.isInfoEnabled()) {
logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
}
if (registryURLs != null && registryURLs.size() > 0
&& url.getParameter("register", true)) {
for (URL registryURL : registryURLs) {
url = url.addParameterIfAbsent("dynamic", registryURL.getParameter("dynamic"));
URL monitorUrl = loadMonitor(registryURL);
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()));
Exporter> exporter = protocol.export(invoker);
exporters.add(exporter);
}
} else {
Invoker> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
Exporter> exporter = protocol.export(invoker);
exporters.add(exporter);
}
}
public class JavassistProxyFactory extends AbstractProxyFactory {
@SuppressWarnings("unchecked")
public T getProxy(Invoker invoker, Class>[] interfaces) {
return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}
public Invoker getInvoker(T proxy, Class type, URL url) {
// TODO Wrapper类不能正确处理带$的类名
final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
return new AbstractProxyInvoker(proxy, type, url) {
@Override
protected Object doInvoke(T proxy, String methodName,
Class>[] parameterTypes,
Object[] arguments) throws Throwable {
return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
}
};
}
}
从上面的代码里面看到,创建Invoker之后接下来就是创建一个Exporter,由Protocol组件来创建。这里的Protocol扩展点也是自适应加载,而当前的Url的协议是registry,自适应protocol最终会调用ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(“registry”), Protocol定义了ProtocolListenerWrapper和ListenerExporterWrapper两个Wrapper扩展点,而当前的Url的协议是registry,根据ExtensionLoader的加载规则,它会返回ProtocolListenerWrapper->ListenerExporterWrapper->RegistryProtocol对象链,对于registry协议,两个Wrapper都不会做任何处理,会直接调到RegistryProtocol.export方法。
com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper
public Exporter export(Invoker invoker) throws RpcException {
if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
return protocol.export(invoker);
}
return new ListenerExporterWrapper(protocol.export(invoker),
Collections.unmodifiableList(ExtensionLoader.getExtensionLoader(ExporterListener.class)
.getActivateExtension(invoker.getUrl(), Constants.EXPORTER_LISTENER_KEY)));
}
com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper
public Exporter export(Invoker invoker) throws RpcException {
if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
return protocol.export(invoker);
}
return protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER));
}
RegistryProtocol.export会调用doLocalExport方法,执行服务暴露逻辑:
com.alibaba.dubbo.registry.integration.RegistryProtocol.doLocalExport(Invoker)
@SuppressWarnings("unchecked")
private ExporterChangeableWrapper doLocalExport(final Invoker originInvoker){
String key = getCacheKey(originInvoker);
ExporterChangeableWrapper exporter = (ExporterChangeableWrapper) bounds.get(key);
if (exporter == null) {
synchronized (bounds) {
exporter = (ExporterChangeableWrapper) bounds.get(key);
if (exporter == null) {
final Invoker> invokerDelegete = new InvokerDelegete(originInvoker, getProviderUrl(originInvoker));
exporter = new ExporterChangeableWrapper((Exporter)protocol.export(invokerDelegete), originInvoker);
bounds.put(key, exporter);
}
}
}
return (ExporterChangeableWrapper) exporter;
}
接着调用protocol.export,这时候url发生了变化不再是registryUrl了,而是存放在registryUrl的export参数中的providerUrl,根据不同协议会定位到不同的扩展,示例配置是dubbo协议,对应的实现是DubboProtocol,同样地根据Wrapper扩展点加载机制会加载出ProtocolListenerWrapper和 ListenerExporterWrapper两个Wrapper,然后依次调用ProtocolListenerWrapper->ListenerExporterWrapper->DubboProtocol的export方法。从上面的代码可以看到,ProtocolListenerWrapper.export会创建一个ListenerExporterWrapper实例,并添加所有激活的ExporterListener到ListenerExporterWrapper实例中,再经过ProtocolFilterWrapper处理,加载所有激活的Filter,并且构建Invoker-Filter链包装成新的Invoker。接着会调用DubboProtocol.export方法生成一个DubboExporter对象,Exporter中持有上面包装Filter链后的Invoker对象引用和url对应的key(key的生成规则在com.alibaba.dubbo.rpc.support.ProtocolUtils.serviceKey(URL)方法中),该Exporter对象会注册到DubboProtocol的exporterMap中,服务器监听到服务调用之后会在这个map中查找Exporter,并封装的Invoker。
com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
public Exporter export(Invoker invoker) throws RpcException {
URL url = invoker.getUrl();
// export service.
String key = serviceKey(url);
DubboExporter exporter = new DubboExporter(invoker, key, exporterMap);
exporterMap.put(key, exporter);
//export an stub service for dispaching event
Boolean isStubSupportEvent = url.getParameter(Constants.STUB_EVENT_KEY,Constants.DEFAULT_STUB_EVENT);
Boolean isCallbackservice = url.getParameter(Constants.IS_CALLBACK_SERVICE, false);
if (isStubSupportEvent && !isCallbackservice){
String stubServiceMethods = url.getParameter(Constants.STUB_EVENT_METHODS_KEY);
if (stubServiceMethods == null || stubServiceMethods.length() == 0 ){
if (logger.isWarnEnabled()){
logger.warn(new IllegalStateException("consumer [" +url.getParameter(Constants.INTERFACE_KEY) +
"], has set stubproxy support event ,but no stub methods founded."));
}
} else {
stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods);
}
}
openServer(url);
return exporter;
}
上面已经生成了服务代理,为了能收到消费端的服务请求,还要在上面选中的端口上监听消费端的请求,调用DubboProtocol的openServer方法,dubbo使用了Netty、grizzly、Mina三种网络框架,框架默认使用Netty,示例配置没有特殊配置,主要看一下Netty。启动服务时组件的调用顺序:Exchanger.bind->Transporter.bind->Server,Exchanger和Transporter也是通过ExtensionLoader来加载,会加载到NettyTransporter实现,它会创建生成一个NettyServer实例,在构造NettyServer实例时调用doOpen打开端口监听, 并添加三个IO事件处理器,NettyCodecAdapter负责请求解码、响应编码,nettyHandler处理请求,把NettyCodecAdapter.decoder->NettyCodecAdapter.encoder->NettyHandler IO事件处理器链到Netty框架中,服务调用消息由多层框架内部的Handler转发最终会转发到DubboProcotol的requestHandler中处理,服务的调用流程后面再详细分析。
com.alibaba.dubbo.remoting.transport.netty.NettyServer.doOpen()
@Override
protected void doOpen() throws Throwable {
NettyHelper.setNettyLoggerFactory();
ExecutorService boss = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerBoss", true));
ExecutorService worker = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerWorker", true));
ChannelFactory channelFactory = new NioServerSocketChannelFactory(boss, worker, getUrl().getPositiveParameter(Constants.IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS));
bootstrap = new ServerBootstrap(channelFactory);
final NettyHandler nettyHandler = new NettyHandler(getUrl(), this);
channels = nettyHandler.getChannels();
// https://issues.jboss.org/browse/NETTY-365
// https://issues.jboss.org/browse/NETTY-379
// final Timer timer = new HashedWheelTimer(new NamedThreadFactory("NettyIdleTimer", true));
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
public ChannelPipeline getPipeline() {
NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec() ,getUrl(), NettyServer.this);
ChannelPipeline pipeline = Channels.pipeline();
/*int idleTimeout = getIdleTimeout();
if (idleTimeout > 10000) {
pipeline.addLast("timer", new IdleStateHandler(timer, idleTimeout / 1000, 0, 0));
}*/
pipeline.addLast("decoder", adapter.getDecoder());
pipeline.addLast("encoder", adapter.getEncoder());
pipeline.addLast("handler", nettyHandler);
return pipeline;
}
});
// bind
channel = bootstrap.bind(getBindAddress());
}
服务起好之后,接下来是把提供者注册到注册中心,和注册中心建立联系。回到RegistryProtocol.export方法,框架由RegistryFactory加载出一个Registry组件来处理注册中心相关的逻辑。
/**
* 根据invoker的地址获取registry实例
* @param originInvoker
* @return
*/
private Registry getRegistry(final Invoker> originInvoker){
URL registryUrl = originInvoker.getUrl();
if (Constants.REGISTRY_PROTOCOL.equals(registryUrl.getProtocol())) {
String protocol = registryUrl.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_DIRECTORY);
registryUrl = registryUrl.setProtocol(protocol).removeParameter(Constants.REGISTRY_KEY);
}
return registryFactory.getRegistry(registryUrl);
}
registryFactory是啥时候设置进来的?dubbo框架在创建组件实例的时候,它会扫描所有的set方法,如果set方法的参数类型是可扩展的组件类型,会加载对应的扩展点实例并设置进去。
com.alibaba.dubbo.common.extension.ExtensionLoader
private T injectExtension(T instance) {
try {
if (objectFactory != null) {
for (Method method : instance.getClass().getMethods()) {
if (method.getName().startsWith("set")
&& method.getParameterTypes().length == 1
&& Modifier.isPublic(method.getModifiers())) {
Class> pt = method.getParameterTypes()[0];
try {
String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
method.invoke(instance, object);
}
} catch (Exception e) {
logger.error("fail to inject via method " + method.getName()
+ " of interface " + type.getName() + ": " + e.getMessage(), e);
}
}
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return instance;
}
Dubbo内置支持zookeeper、redis、广播注册中心。实例配置的zookeeper注册中心,对应ZookeeperRegistry。提供者会在zookeeper注册中心生成一个节点,节点路径为/dubbo/interfaceClass/providers/interfaceClass/providers/{providerUrl},存储了服务提供方ip、端口、group、接口名称、版本、应用名称(redis注册中心通过set命令把这些信息缓存到redis服务器)。
com.alibaba.dubbo.registry.zookeeper.ZookeeperRegistry.doRegister(URL)
protected void doRegister(URL url) {
try {
zkClient.create(toUrlPath(url), url.getParameter(Constants.DYNAMIC_KEY, true));
} catch (Throwable e) {
throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
}
}
为了感知注册中心的一些配置变化,提供者会监听注册中心路径/dubbo/${interfaceClass}/configurators的节点,监听该节点在注册中心的一些配置信息变更。zookeeper注册中心通过zookeeper框架的监听回调接口进行监听(redis注册中心通过订阅命令(subscribe)监听),服务器缓存注册中心的配置,当配置发生变更时,服务会刷新本地缓存,代码在ZookeeperRegistry的doSubscribe方法。
protected void doSubscribe(final URL url, final NotifyListener listener) {
try {
if (Constants.ANY_VALUE.equals(url.getServiceInterface())) {
String root = toRootPath();
ConcurrentMap listeners = zkListeners.get(url);
if (listeners == null) {
zkListeners.putIfAbsent(url, new ConcurrentHashMap());
listeners = zkListeners.get(url);
}
ChildListener zkListener = listeners.get(listener);
if (zkListener == null) {
listeners.putIfAbsent(listener, new ChildListener() {
public void childChanged(String parentPath, List currentChilds) {
for (String child : currentChilds) {
if (! anyServices.contains(child)) {
anyServices.add(child);
subscribe(url.setPath(child).addParameters(Constants.INTERFACE_KEY, child,
Constants.CHECK_KEY, String.valueOf(false)), listener);
}
}
}
});
zkListener = listeners.get(listener);
}
zkClient.create(root, false);
List services = zkClient.addChildListener(root, zkListener);
if (services != null && services.size() > 0) {
anyServices.addAll(services);
for (String service : services) {
subscribe(url.setPath(service).addParameters(Constants.INTERFACE_KEY, service,
Constants.CHECK_KEY, String.valueOf(false)), listener);
}
}
} else {
List urls = new ArrayList();
for (String path : toCategoriesPath(url)) {
ConcurrentMap listeners = zkListeners.get(url);
if (listeners == null) {
zkListeners.putIfAbsent(url, new ConcurrentHashMap());
listeners = zkListeners.get(url);
}
ChildListener zkListener = listeners.get(listener);
if (zkListener == null) {
listeners.putIfAbsent(listener, new ChildListener() {
public void childChanged(String parentPath, List currentChilds) {
ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url, parentPath, currentChilds));
}
});
zkListener = listeners.get(listener);
}
zkClient.create(path, false);
List children = zkClient.addChildListener(path, zkListener);
if (children != null) {
urls.addAll(toUrlsWithEmpty(url, path, children));
}
}
notify(url, listener, urls);
}
} catch (Throwable e) {
throw new RpcException("Failed to subscribe " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
}
}
上面是服务提供者在初始化时做的一些主要动作,其实框架做的事远比这多了,先暂时先忽略一些细节,专项功能放到后面再逐个击破。
spring容器启动时会解析dubbo的自定义标签,解析的最终目的是返回 RootBeanDefinition
对象,RootBeanDefinition包含了解析出来的关于bean的所有信息,注意,在bean的解析完后其实只是spring将其转化成spring内部的一种抽象的数据对象结构,bean的创建(实例化)是第一次调用 getBean 时创建的。
例如dubbo:service标签被解析成一个ServiceBean,ServiceBean实现了ApplicationListener和InitializingBean接口,ServiceBean有个核心方法export,在这个方法中初始化服务提供者并且暴露远程服务。这个方法在bean初始化或容器中所有bean刷新完毕时被调用,根据provider的延迟设置决定,如果设置了延迟(delay属性)在bean初始化结束之后调用否则容易刷新事件中被调用,默认会延迟export,即在所有bean的刷新结束时间中被调用。
生成url,
a.注册中心的url:在服务初始化时需要对注册中心进行访问,所以需要一个URL来跟注册中心通信,根据应用的注册中心配置来生成这个URL,URL协议是registry,包含了注册中心地址、端口,path部分是com.alibaba.dubbo.registry.RegistryService类名,其中还有dubbo版本、应用名称等信息放在URL的参数中,格式化之后是这种形式:registry://10.165.124.205:2181/com.alibaba.dubbo.registry.RegistryService?application=…
b.服务本身的url:还需要生成一个URL来携带服务本身的信息,协议由应用侧指定,在我们的示例配置中是dubbo协议,host是本机ip,端口是上面选中的随机或配置中指定的端口,path是服务对应的接口class全名(含包路径),添加side参数provider,dubbo版本号、服务方法名称、group等等,格式化之后是这种形式:
dubbo://10.240.176.159:20880/com.netease.haitao.mykaola.generic.api.InternalStaffServiceFacade?…&createInternalStaffOperDetail.timeout=10000&…&default.group=hzchenyunyun1&…&dubbo=3.0.0&…&methods=…&…&side=provider&…
生成远程服务代理,生成代理的目的是你调用 invoker 的相关函数后,就会等于是调用 DubboInvoker 中的相关函数;也就是将本地调用转为网络调用并获得结果;
启动服务监听,框架默认使用Netty,打开端口监听,
并添加三个IO事件处理器,NettyCodecAdapter负责请求解码、响应编码,nettyHandler处理请求。
暴露服务(将服务注册到注册中心),提供者会在zookeeper注册中心生成一个节点,此节点存储了服务提供方ip、端口、group、接口名称、版本、应用名称(redis注册中心通过set命令把这些信息缓存到redis服务器)。
为了感知注册中心的一些配置变化,提供者会监听注册中心路径/dubbo/${interfaceClass}/configurators的节点,监听该节点在注册中心的一些配置信息变更。zookeeper注册中心通过zookeeper框架的监听回调接口进行监听(redis注册中心通过订阅命令(subscribe)监听),服务器缓存注册中心的配置,当配置发生变更时,服务会刷新本地缓存,代码在ZookeeperRegistry的doSubscribe方法。