一般一个服务有可能即是服务端又是消费端。服务启动的时候会去像注册中心(一般是zk)暴露或者订阅自己的或者自己需要的服务。我们来看下dubbo是如何把本地服务注册到注册中心的。
我们来看下ServiceBean.java这个类。
public class ServiceBean extends ServiceConfig implements InitializingBean,
DisposableBean, ApplicationContextAware, ApplicationListener, BeanNameAware
ServiceBean实现了Spring提供的接口InitializingBean,当在完成Bean初始化和注入的时候程序会自动执行InitializingBean接口中的afterPropertiesSet方法。
在这个方法中,会对dubbo的配置做初始化赋值,即我们在配置文件中配置的:
provider,application,module,registries,monitor,protocols,path(beanName)
在方法的最后会调用父类ServiceConfig中的export方法。
if (! isDelay()) { // 这里配置了delay会返回false,逻辑有点怪
export();
}
export方法判断是否延迟注册,延迟注册使用新的守护线程去执行暴露服务,否则直接暴露服务
public synchronized void export() {
if (provider != null) {
if (export == null) {
export = provider.getExport();
}
if (delay == null) {
delay = provider.getDelay();
}
}
if (export != null && ! export.booleanValue()) { // 判断服务是否暴露
return;
}
// 判断是否延迟注册,如果延迟注册则启动一个新的线程去执行,不影响主线程的运行
if (delay != null && delay > 0) {
Thread thread = new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(delay);
} catch (Throwable e) {
}
doExport();
}
});
thread.setDaemon(true);
thread.setName("DelayExportServiceThread");
thread.start();
} else {
doExport();
}
}
在doExport方法中,对要暴露的服务进行了一系列的检查,检查provider,application,module,registries,monitor这些参数是否为空,是否是GenericService类型的服务,检查要注册的bean的引用和方法等。在方法的最后会调用doExportUrls方法。
private void doExportUrls() {
List registryURLs = loadRegistries(true); // 加载所有的注册中心,服务有可能注册在多个注册中心
for (ProtocolConfig protocolConfig : protocols) { // 不同协议的注册。dubbo/hessian...
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
doExportUrlsFor1Protocol方法比较长,有些地方简写代替。
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List registryURLs) {
String name = protocolConfig.getName();
if (name == null || name.length() == 0) {
name = "dubbo";
}
// 先从配置中取host,
// 如果非法则通过InetAddress获取localHost,
// 如果非法则继续通过Socket取当前localHost。
String host = getHost();
// 先从配置中取端口,取不到然后取默认端口,默认端口为空则取一可用端口。
Integer port = getPort();
// paramsMap,存放所有配置参数,下面生成url用。
Map<String, String> map = new HashMap<String, String>();
if (anyhost) {
map.put(Constants.ANYHOST_KEY, "true");
}
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);
// method子标签配置规则解析,retry次数,参数等。没有使用过,不做解释
if (methods != null && methods.size() > 0) {
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.size() > 0) {
...... // 设置参数
}
} // end of methods for
}
// 获取所有的methods方法
if (generic) { // 如果是泛化实现,generic=true,method=*表示任意方法
map.put("generic", String.valueOf(true));
map.put("methods", 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("methods", Constants.ANY_VALUE);
}
else {
map.put("methods", StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
}
}
// 有需要配置token,默认随机UUID,否则使用配置中的token,作令牌验证用
if (! ConfigUtils.isEmpty(token)) {
if (ConfigUtils.isDefault(token)) {
map.put("token", UUID.randomUUID().toString());
} else {
map.put("token", token);
}
}
// injvm不需要暴露服务,标注notify=false
if ("injvm".equals(protocolConfig.getName())) {
protocolConfig.setRegister(false);
map.put("notify", "false");
}
// 导出服务
String contextPath = protocolConfig.getContextpath();
if ((contextPath == null || contextPath.length() == 0) && provider != null) {
contextPath = provider.getContextpath();
}
// 根据参数创建url对象
URL url = new URL(name, host, port,
(contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);
// 如果url使用的协议存在扩展,调用对应的扩展来修改原url。目前扩展有override,absent
if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.hasExtension(url.getProtocol())) {
url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.getExtension(url.getProtocol()).getConfigurator(url).configure(url);
}
String scope = url.getParameter(Constants.SCOPE_KEY);
//配置为none不暴露
if (! Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {
//配置不是remote的情况下做本地暴露 (配置为remote,则表示只暴露远程服务)
if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
// 只有当url协议不是injvm的时候才会在本地暴露服务,如果是injvm则什么都不做
exportLocal(url);
}
//如果配置不是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);
// 如果有monitor信息,则在url上增加monitor配置
if (monitorUrl != null) {
url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());
}
// 通过代理工厂将url转化成invoker对象,proxyFactory的实现是JavassistProxyFactory
Invoker> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass,
registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
// 这里invoker对象协议是registry,protocol根据协议找到RegisterProtocol实现类,注1。
// 在调用RegisterProtocol类的export方法之前会先调用ProtocolListenerWrapper类的export方法
// protocol实例转化为ProtocolFilterWrapper,包了一层RegistryProtocol
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);
}
}
}
this.urls.add(url);
}
注1:protocol对象是如何知道在执行的时候是使用的哪个实现类?如何根据url的协议来创建对应的Protocol。 在ServiceConfig初始化的时候,protocol初始化为
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
getAdaptiveExtension方法说明是在调用的时候再决定使用什么扩展。
下面为生成的protocol字节码Protocol$Adpative
最终的注册还是在RegistryProtocol类中的export方法中完成的
public Exporter export(final Invoker originInvoker) throws RpcException {
//export invoker
// 在本地暴露的时候会开启Netty服务
final ExporterChangeableWrapper exporter = doLocalExport(originInvoker);
}
我们先来看下本地暴露服务。在本地暴露时,protocol.export(invokerDelegete)会转化为DubboProtocol
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;
}
因为不是Registry协议,在ProtocolFilterWrapper执行的时候就会走下面的逻辑,在这里有一个方法buildInvokerChain,会创建Dubbo调用过程中的过滤器链,具体类似tomcat中的过滤器链规则。
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));
}
DubboProtocol中的export方法会缓存生成的exporter对象,然后方法最后开启Netty服务
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);
// 这里会判断是否是stub服务(不是太懂,默认是false的)
//export an stub service for dispaching event
Boolean isStubSupportEvent = url.getParameter(Constants.STUB_EVENT_KEY,Constants.DEFAULT_STUB_EVENT);
// 判断是否是callback服务
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 ){
// log
} else {
stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods);
}
}
// 开启Netty服务
openServer(url);
return exporter;
}
openServer方法会根据key(ip+port)判断server是否存在,不存在则调用createServer(URL url)方法创建一个链接。
private ExchangeServer createServer(URL url) {
//默认开启heartbeat
url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));
...
ExchangeServer server = Exchangers.bind(url, requestHandler);
...
return server;
}
Exchangers的bind方法getExchanger(url)得到的默认为HeaderExchanger,HeaderExchanger的bind方法又调用了Transporters中的bind方法,看getTransporter方法又碰到了熟悉的适配加载机制,Transporter类的注解为@SPI("netty"),默认会调用NettyTransporter的bind方法,NettyTransporter的bind方法直接new了一个NettyServer,NettyServer初始化的时候调用父类的构造方法,父类的构造方法中调用了NettyServer的doOpen方法,NettyServer的链接创建在doOpen方法中完成。
Exchangers
public static ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
// 断点调到这里codec为dubbo,应该是序列化用。
url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
return getExchanger(url).bind(url, handler);
}
HeaderExchanger
public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
return new HeaderExchangeServer(Transporters.bind(url,
new DecodeHandler(new HeaderExchangeHandler(handler))));
// 这里应当是适配者模式
}
Transporters
public static Server bind(URL url, ChannelHandler... handlers) throws RemotingException {
return getTransporter().bind(url, handler);
}
public static Transporter getTransporter() {
// 适配扩展点加载
return ExtensionLoader.getExtensionLoader(Transporter.class).getAdaptiveExtension();
}
在NettyServer的doOpen方法中可以看到这里绑定了netty的端口和链接。方法到这里本地的Server端口都已经暴露完毕。
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中,来看下在zk中暴露服务。首先先获取zk的配置信息,然后获取需要暴露的url,然后调用registry.register方法将url注册到zookeeper上去。
public Exporter export(final Invoker originInvoker) throws RpcException {
//export invoker
// 在本地暴露服务
final ExporterChangeableWrapper exporter = doLocalExport(originInvoker);
//registry provider
// 拿到zookeeper的注册信息
final Registry registry = getRegistry(originInvoker);
// 获取需要暴露provider的url对象,dubbo的注册订阅通信都是以url作为参数传递的
final URL registedProviderUrl = getRegistedProviderUrl(originInvoker);
registry.register(registedProviderUrl); // 注册服务
// 订阅override数据
// FIXME 提供者订阅时,会影响同一JVM即暴露服务,又引用同一服务的的场景,
// 因为subscribed以服务名为缓存的key,导致订阅信息覆盖。
final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registedProviderUrl);
final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl);
overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
// 暴露的同时订阅服务,另外会在zk上创建configurators节点信息
registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
//保证每次export都返回一个新的exporter实例
return new Exporter() {
...
};
}
调试代码可以发现,这里的registry对象为ZookeeperRegistry,ZookeeperRegistry继承了FailbackRegistry,默认调用父类的register方法,然后调用ZookeeperRegistry的doRegister方法,这里就比较简单了。直接调用zkClient在zookeeper上创建一个节点信息,这样就把服务丢到zk上去了
protected void doRegister(URL url) {
zkClient.create(toUrlPath(url), url.getParameter(Constants.DYNAMIC_KEY, true));
}
暴露完服务之后,还会调用registry的subscribe方法,这里主要是加了注册订阅Listener,在创建出其他节点之后会调用notify方法。notify方法会做两件事,1. 会将url改动更新到缓存的配置文件中。2. 会通知listener变动,此通知为全量通知。