节点角色说明:
Provider:暴露服务的服务提供方。
Consumer::调用远程服务的服务消费方。
Registry:服务注册与发现的注册中心。
Monitor:统计服务的调用次调和调用时间的监控中心。
Container:服务运行容器。
调用关系说明:
0.服务容器负责启动,加载,运行服务提供者。
1.服务提供者在启动时,向注册中心注册自己提供的服务。
2.服务消费者在启动时,向注册中心订阅自己所需的服务。
3.注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
4.服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
5.服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送次统计数据到监控中心。
暴露流程:
首先,服务器端(服务提供者)在框架启动时,会初始化服务实例,通过Proxy组件调 用具体协议(Protocol ),把服务端要暴露的接口封装成Invoker (真实类型是 AbstractProxylnvoker ,然后转换成Exporter,这个时候框架会打开服务端口等并记录服务实例 到内存中,最后通过Registry把服务元数据注册到注册中心。这就是服务端(服务提供者)整 个接口暴露的过程。
Proxy组件:我们知道,Dubbo中只需要引用一个接口就可以调用远程的服务,并且
只需要像调用本地方法一样调用即可。其实是Dubbo框架为我们生成了代理类,调用
的方法其实是Proxy组件生成的代理方法,会自动发起远程/本地调用,并返回结果,
整个过程对用户完全透明。
• Protocol:顾名思义,协议就是对数据格式的一种约定。它可以把我们对接口的配置,
根据不同的协议转换成不同的Invoker对象。例如:用DubboProtocol可以把XML文
件中一个远程接口的配置转换成一个Dubbolnvoker。
• Exporter:用于暴露到注册中心的对象,它的内部属性持有了 Invoker对象,我们可以
认为它在Invoker上包了 一层。
• Registry:把Exporter注册到注册中心。
使用:
提供者(provider)服务提供者需要提供xxx-api.jar,供消费者引入并使用其中包含的接口,同时自身引入并实现相应的接口
provider暴露服务:
在dubbo.xml中的xsl:schemalocation中加入
http://code.alibabatech.com/schema/dubbo
http://code.allbabatech.com/schema/dubbo/dubbo.xsd">
并注册
在dubbo-service.xml:
消费者:
引入api.jar
address:dubbo注册的zk的集群 protocol:dubbo注册使用的协议 group:dubbo服务的所属组 Interface:所提供的服务 ref:对应的实现类 version:提供的dubbo服务的版本号 timeout:服务提供者的超时时间 cluster:重试机制,常用的有failfast、failover(默认3次,可以配合retries使用自定义重试次数) 服务暴露流程 首先会通过initialize()方法完成初始化,装配各种Config对象,为后续的服务暴露和引用准备好环境。 ServiceConfig对象就是Dubbo对服务的描述对象,服务暴露的逻辑都在ServiceConfig#export()里面,Dubbo暴露服务也就是遍历所有的ServiceConfig,挨个进行暴露。 ServiceConfig描述了对外提供的服务,ref属性引用了具体的服务实现对象,当Provider接收到Consumer发起的RPC调用时,会交给ref执行,但是Dubbo不会直接使用ref,因为不管是Provider还是Consumer,Dubbo都在向Invoker靠拢。 Invoker是本地调用、远程调用、集群调用 Dubbo服务暴露的入口在ServiceConfig#export()方法 配置的校验和更新 其中的checkAndUpdateSubConfigs() 对ServiceConfig对象做一些配置的校验和自动更新。例如使用ProviderConfig的全局默认配置、将protocolIds转换成ProtocolConfig对象、自身的属性按照优先级进行刷新等等。配置更新完了,接下来就是做服务暴露的前置Check,例如注册中心是否有效、ref对象是否符合要求等等。 doExport() 服务暴露的范围可以通过scope属性配置,none代表不暴露、local仅暴露到本地JVM、remote会暴露到远程。Dubbo在服务暴露前会进行判断,默认情况下会同时暴露到本地JVM和远程。 服务注册: Dubbo服务暴露,先将ref封装成Invoker,Invoker会根据Consumer的Invocation参数对ref发起调用,Dubbo默认使用javassist字节码技术动态生成Wrapper类,避免了Java反射带来的性能问题。 version=“1.0.0” timeout=“30000” registry=“tts_smart_pnr” cluster=“failfast”/>
interface="com.qunar.flight.tts.smart.pnr.api.ISmartPnrService"
registry="tts_smart_pnr"
timeout="5000"
check="false"
cluster="failfast"
version="1.0.0">
暴露服务
分发服务暴露事件public synchronized void export() {
// 检查和更新配置
checkAndUpdateSubConfigs();
if (shouldDelay()) {
// 延迟暴露
DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
} else {
// 暴露服务
doExport();
}
// 分发暴露事件
exported();
}
private void checkAndUpdateSubConfigs() {
// 使用ProviderConfig默认配置
completeCompoundConfigs();
// ProviderConfig不存在则自动创建
checkDefault();
// protocolIds转换
checkProtocol();
if (!isOnlyInJvm()) {
// 服务注册,还要检查配置中心
checkRegistry();
}
// 自身属性根据优先级刷新
this.refresh();
checkStubAndLocal(interfaceClass);
ConfigValidationUtils.checkMock(interfaceClass, this);
ConfigValidationUtils.validateServiceConfig(this);
postProcessConfig();
代码有精简...
}
ServiceRepository repository = ApplicationModel.getServiceRepository();
// 注册Service
ServiceDescriptor serviceDescriptor = repository.registerService(getInterfaceClass());
// 注册Provider
repository.registerProvider(getUniqueServiceName(),ref,serviceDescriptor,this,serviceMetadata);
接下来,获取当前服务需要注册到哪些注册中心,加载对应的URL。
// 加载配置中心URL
List<URL> registryURLs = ConfigValidationUtils.loadRegistries(this, true);
在Check那一步,就已经解析了服务需要通过哪些协议进行暴露,所以接下来会遍历protocols,进行单协议多注册中心暴露。
for (ProtocolConfig protocolConfig : protocols) {
String pathKey = URL.buildKey(getContextPath(protocolConfig)
.map(p -> p + "/" + path)
.orElse(path), group, version);
repository.registerService(pathKey, interfaceClass);
serviceMetadata.setServiceKey(pathKey);
// 单协议多注册中心暴露
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
服务暴露需要用到各种参数,用于构建后续的服务暴露URL,这里会使用HashMap存储。Dubbo的配置粒度是到方法级别的,对应的类是MethodConfig,如果有方法级别的配置,也需要解析到Map中。
// 服务暴露的各种参数,用于组装服务暴露URL
Map<String, String> map = new HashMap<String, String>();
map.put(SIDE_KEY, PROVIDER_SIDE);
// 运行时参数
ServiceConfig.appendRuntimeParameters(map);
AbstractConfig.appendParameters(map, getMetrics());
AbstractConfig.appendParameters(map, getApplication());
AbstractConfig.appendParameters(map, getModule());
......
参数组装完毕,解析出服务暴露的host和port,然后构建URL。
// 查找服务暴露的host和port
String host = findConfigedHosts(protocolConfig, registryURLs, map);
Integer port = findConfigedPorts(protocolConfig, name, map);
// 构建URL
URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);
protected Registry getRegistry(final Invoker<?> originInvoker) {
// 注册中心URL
URL registryUrl = getRegistryUrl(originInvoker);
// SPI加载Registry实现
return registryFactory.getRegistry(registryUrl);
}
解析出需要注册到注册中心的URL,然后调用RegistryService#register()完成服务注册。
final Registry registry = getRegistry(originInvoker);
// 注册到注册中心的URL
final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl);
// 是否立即注册
boolean register = providerUrl.getParameter(REGISTER_KEY, true);
if (register) {
register(registryUrl, registeredProviderUrl);
}
最终操作:
public void doRegister(URL url) {
final String serviceName = getServiceName(url);
final Instance instance = createInstance(url);
// 注册服务,最终调用 NamingService#registerInstance()
execute(namingService -> namingService.registerInstance(serviceName,getUrl().getParameter(GROUP_KEY, Constants.DEFAULT_GROUP), instance));
}
有了Invoker就可以通过Protocol根据协议进行服务暴露,如果服务需要注册,Dubbo会改写URL协议为registry,这是个伪协议,只是在原服务暴露的基础上,增加了服务注册的功能。
在根据协议暴露服务前,还需要关注两个包装类:ProtocolFilterWrapper和ProtocolListenerWrapper,前者用于构建Filter链,后者用于服务取消暴露时触发事件。
以dubbo协议为例,除了创建DubboExporter,还会根据服务暴露的address创建ProtocolServer。Transporter是dubbo对网络传输层的抽象接口,以Netty为例,底层其实就是创建了ServerBootstrap,然后bind本地接口监听网络请求。