Dubbo是一个分布式服务框架,以及SOA治理方案。其功能主要包括:高性能NIO通讯及多协议集成,服务动态寻址与路由,软负载均衡与容错,依赖分析与降级等。
当网站变大后,不可避免的需要拆分应用进行服务化,以提高开发效率,调优性能,节省关键竞争资源等。
当服务越来越多时,服务的URL地址信息就会爆炸式增长,配置管理变得非常困难,F5硬件负载均衡器的单点压力也越来越大。
当进一步发展,服务间依赖关系变得错踪复杂,甚至分不清哪个应用要在哪个应用之前启动,架构师都不能完整的描述应用的架构关系。
接着,服务的调用量越来越大,服务的容量问题就暴露出来,这个服务需要多少机器支撑?什么时候该加机器?等等……
在遇到这些问题时,都可以用Dubbo来解决。
前两段摘自作者梁飞iteye采访
文档基于dubbo版本2.5.3
- 读取目录下type名称文件,如:com.alibaba.dubbo.common.extension.ExtensionFactory
- 读取文件内容的实现,如果class存在Adaptive注解,缓存至cachedAdaptiveClass
- 如果class不存在Adaptive注解,如果存在以绑定类型为入参的构造器函数,则缓存至cachedWrapperClasses
- 如果不是包装类型,如果存在"="分隔的kv,则以key为name属性,否则以class实现的类名称去除绑定类型名称的剩余部分的小写作为name属性,如:AdaptiveExtensionFactory去除ExtensionFactory,name为:adaptive
- 逗号分隔name属性如果不为空,如果实现类存在Activate注解,将Activate注解以names[0]为key缓存至cachedActivates;遍历names,将name以class为key缓存至cachedNames,将class以name为key放入extensionClasses返回,即缓存至:cachedClasses
- 生成class适配类规则
- 走到自动生成模块说明不存在Adaptive注解的实现类,遍历绑定接口的方法
- 如果方法不存在adaptive注解,则抛出异常
- 如果存在,则生成代码逻辑,如果Adaptive不存在values,即没有设置Key,则使用接口类名称驼峰转点分隔的方式作为Key,例如:ProtocolTest转换后为protocol.test
- 如果Adaptive注解存在values,遍历,如果value是"protocol",则使用url.getProtocol作为extName(后面自动生成的代码中会根据extName获取扩展实现类:getExtension(extName));如果不是protocol,如果参数存在Invocation类型,则使用url.getMethodParameter(methodName,value,defaultName)方式获取值作为extName,method,否则使用url.getParameter(value,defaultName)作为extName
- 生成方法实现,使用ExtensionLoader.getExtension(extName)获取扩展实现,作为接口方法的委托实现
package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol {
public void destroy() {
throw new UnsupportedOperationException(
"method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
}
public int getDefaultPort() {
throw new UnsupportedOperationException(
"method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
}
public com.alibaba.dubbo.rpc.Exporter export(
com.alibaba.dubbo.rpc.Invoker arg0)
throws com.alibaba.dubbo.rpc.RpcException {
if (arg0 == null) {
throw new IllegalArgumentException(
"com.alibaba.dubbo.rpc.Invoker argument == null");
}
if (arg0.getUrl() == null) {
throw new IllegalArgumentException(
"com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
}
com.alibaba.dubbo.common.URL url = arg0.getUrl();
String extName = ((url.getProtocol() == null) ? "dubbo"
: url.getProtocol());
if (extName == null) {
throw new IllegalStateException(
"Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" +
url.toString() + ") use keys([protocol])");
}
com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class)
.getExtension(extName);
return extension.export(arg0);
}
public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0,
com.alibaba.dubbo.common.URL arg1)
throws com.alibaba.dubbo.rpc.RpcException {
if (arg1 == null) {
throw new IllegalArgumentException("url == null");
}
com.alibaba.dubbo.common.URL url = arg1;
String extName = ((url.getProtocol() == null) ? "dubbo"
: url.getProtocol());
if (extName == null) {
throw new IllegalStateException(
"Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" +
url.toString() + ") use keys([protocol])");
}
com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class)
.getExtension(extName);
return extension.refer(arg0, arg1);
}
}
public List getActivateExtension(URL url, String key) {
return getActivateExtension(url, key, null);
}
public List getActivateExtension(URL url, String[] values) {
return getActivateExtension(url, values, null);
}
public List getActivateExtension(URL url, String key, String group) {
String value = url.getParameter(key);
return getActivateExtension(url, value == null || value.length() == 0 ? null : Constants.COMMA_SPLIT_PATTERN.split(value), group);
}
- appendProperties开始
- 注入方法使用配置属性的set方法,并且必须是public,入参个数为1并且是原始类型
- 从System中获取配置赋值给本地value,参数命名规则:dubbo.类名称去除Config,Bean后小写,例如:ProviderConfig=provider.[如果有配置id则增加id].方法名称去除set并小写
- 如果本地value依然为空,则尝试从实例中调用get或is方法获取属性判断属性是否获取的值为空,如果是,则再次尝试从System中按照前一步那样获取配置属性,如果为空则尝试读取:dubbo.properties.file配置指定的配置文件中的配置,如果没有配置则使用默认配置文件:dubbo.properties
- 如果本地value依然为空,判断当前属性是否属于legacyProperties集合中,如果是将其对应的key按照前两步步骤获取并赋值给本地value
- appendProperties完成
- appendParameters开始
- 如果是get,is方法,不包含getClass方法,public修饰,无参方法,返回类型为原始类型
- 获取Parameter注解,如果为excluded表示不需要收集该属性至map集合
- parameters map的key使用Parameter注解的key属性,如果不存在则使用方法名称小写,根据key从parameters
- 如果Parameter注解为escape则调用URLEncode.encode编译,如果Parameter注解为append,则从parameters map中获取之前的属性,如果存在使用逗号拼接,如果存在prefix,则添加前缀
- 将处理后的value属性放入parameters map 使用 key
- 如果方法名为getParameters,public修饰,无参方法,返回map,调用方法返回map,遍历map,将之前存在的key与新map的key使用"."拼接,value使用新map的value
- appendParameters完成
map.put("path", RegistryService.class.getName());
map.put("dubbo", Version.getVersion());
map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
if (ConfigUtils.getPid() > 0) {
map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
}
dubbo=com.alibaba.dubbo.registry.dubbo.DubboRegistryFactory
multicast=com.alibaba.dubbo.registry.multicast.MulticastRegistryFactory
zookeeper=com.alibaba.dubbo.registry.zookeeper.ZookeeperRegistryFactory
redis=com.alibaba.dubbo.registry.redis.RedisRegistryFactory
- parseURL开始
- 如果address包含"/"字符,则直接赋值给url
- 如果address不包含"/"字符,则使用逗号切分,第一个作为url,其余的如果存在则作为backup参数
- 从map集合中获取protocol协议,不存在则使用dubbo协议
- 根据url字符串获取URL对象,如果解析的URL对象的协议为空则使用default集合中的默认配置,用户名,密码,端口,path路径,参数集合同样如果为空使用默认值
- parseURL结束
appendParameters(map, application);
appendParameters(map, module);
appendParameters(map, provider, Constants.DEFAULT_KEY);
appendParameters(map, protocolConfig);
appendParameters(map, this);
override=com.alibaba.dubbo.rpc.cluster.configurator.override.OverrideConfiguratorFactory
absent=com.alibaba.dubbo.rpc.cluster.configurator.absent.AbsentConfiguratorFactory
url:registry://192.168.11.29:2181/com.alibaba.dubbo.registry.RegistryService?application=dispatch-inverse-monitor&backup=192.168.11.32:2181,192.168.11.20:2181&dubbo=2.5.3&export=dubbo://192.168.103.186:15030/com.dianwoba.pt.goodjob.remote.service.JobExecuteService?accesslog=true&anyhost=true&application=dispatch-inverse-monitor&dubbo=2.5.3&group=dispatch-inverse-monitor_analyzeService&interface=com.dianwoba.pt.goodjob.remote.service.JobExecuteService&logger=slf4j&methods=execute&pid=9892&retries=0&revision=1.1.0-SNAPSHOT&side=provider&timeout=5000×tamp=1544673080385&logger=slf4j&pid=9892®istry=zookeeper×tamp=1544673080385
public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol {
...
public com.alibaba.dubbo.rpc.Exporter export(
com.alibaba.dubbo.rpc.Invoker arg0)
throws com.alibaba.dubbo.rpc.RpcException {
...
com.alibaba.dubbo.common.URL url = arg0.getUrl();
String extName = ((url.getProtocol() == null) ? "dubbo"
: url.getProtocol());
...
com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.export(arg0);
}
public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0,
com.alibaba.dubbo.common.URL arg1)
throws com.alibaba.dubbo.rpc.RpcException {
...
com.alibaba.dubbo.common.URL url = arg1;
String extName = ((url.getProtocol() == null) ? "dubbo"
: url.getProtocol());
...
com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.refer(arg0, arg1);
}
}
graph LR
ProtocolListenerWrapper-->ProtocolFilterWrapper
ProtocolFilterWrapper-->RegistryProtocol
- doLocalExport 开始
- 从bouds缓存中获取,如果没有则导出并缓存
- 将originInvoker封装为InvokerDelegete委派对象,并将export服务url与委派对象绑定
- protocol.export 导出服务,此时的协议是用ExtensionLoader注入的适配类型,导出的invoker绑定的url是ProtocolConfig协议,如:DubboProtocol,导出后的包装链
graph LR
ExporterChangeableWrapper-->ListenerExporterWrapper
ListenerExporterWrapper-->DubboExporter
DubboExporter-->ProtocolFilterWrapper.Invoker
ProtocolFilterWrapper.Invoker-->InvokerDelegete
InvokerDelegete-->DubboInvoker
- doLocalExport 完成
public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
}
public Server bind(URL url, ChannelHandler listener) throws RemotingException {
return new NettyServer(url, listener);
}
public NettyServer(URL url, ChannelHandler handler) throws RemotingException{
super(url, ChannelHandlers.wrap(handler, ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME)));
}
graph LR
MultiMessageHandler-->HeartbeatHandler
HeartbeatHandler-->AllChannelHandler
AllChannelHandler-->DecodeHandler
DecodeHandler-->HeaderExchangeHandler
HeaderExchangeHandler-->DubboProtocol.ExchangeHandlerAdapter
invoker = refprotocol.refer(interfaceClass, urls.get(0));
url:
registry://192.168.11.29:2181/com.alibaba.dubbo.registry.RegistryService?application=dispatch-inverse-monitor&backup=192.168.11.32:2181,192.168.11.20:2181&dubbo=2.5.3&logger=slf4j&pid=1908&refer=application=dispatch-inverse-monitor&check=false&default.check=false&default.retries=0&default.timeout=1000&dubbo=2.5.3&interface=com.dianwoba.dw.schedule.service.ByTheWayDriftService&logger=slf4j&methods=acceptOrderRateAndOvertimeRateListByCityIdList,acceptOrderRateAndOvertimeRateByCityId,queryGeoHashMonthSale&pid=1908&revision=1.0.0-20181204.071850-923&side=consumer&timeout=100×tamp=1544767680215®istry=zookeeper×tamp=1544767680267
graph LR
ProtocolListenerWrapper-->ProtocolFilterWrapper.Invoker
ProtocolFilterWrapper.Invoker-->RegistryProtocol
拼装后的url:zookeeper://192.168.11.29:2181/com.alibaba.dubbo.registry.RegistryService?application=dispatch-inverse-monitor&backup=192.168.11.32:2181,192.168.11.20:2181&dubbo=2.5.3&logger=slf4j&pid=16340&refer=application=dispatch-inverse-monitor&check=false&default.check=false&default.retries=0&default.timeout=1000&dubbo=2.5.3&interface=com.dianwoba.dw.schedule.service.ByTheWayDriftService&logger=slf4j&methods=acceptOrderRateAndOvertimeRateListByCityIdList,acceptOrderRateAndOvertimeRateByCityId,queryGeoHashMonthSale&pid=16340&revision=1.0.0-20181204.071850-923&side=consumer&timeout=100×tamp=1544702578695×tamp=1544702578729
consumer://192.168.103.186/com.dianwoba.dw.schedule.service.ByTheWayDriftService?application=dispatch-inverse-monitor&check=false&default.check=false&default.retries=0&default.timeout=1000&dubbo=2.5.3&interface=com.dianwoba.dw.schedule.service.ByTheWayDriftService&logger=slf4j&methods=acceptOrderRateAndOvertimeRateListByCityIdList,queryGeoHashMonthSale,acceptOrderRateAndOvertimeRateByCityId&pid=15244&revision=1.0.0-20181204.071850-923&side=consumer&timeout=100×tamp=1544703002095
目录:
/dubbo/com.dianwoba.dw.schedule.service.ByTheWayDriftService/consumers/consumer://192.168.103.186/com.dianwoba.dw.schedule.service.ByTheWayDriftService?application=dispatch-inverse-monitor&category=consumers&check=false&default.check=false&default.retries=0&default.timeout=1000&dubbo=2.5.3&interface=com.dianwoba.dw.schedule.service.ByTheWayDriftService&logger=slf4j&methods=acceptOrderRateAndOvertimeRateListByCityIdList,queryGeoHashMonthSale,acceptOrderRateAndOvertimeRateByCityId&pid=2872&revision=1.0.0-20181204.071850-923&side=consumer&timeout=100×tamp=1544703323067
url:
consumer://192.168.103.186/com.dianwoba.dw.schedule.service.ByTheWayDriftService?application=dispatch-inverse-monitor&category=providers,configurators,routers&check=false&default.check=false&default.retries=0&default.timeout=1000&dubbo=2.5.3&interface=com.dianwoba.dw.schedule.service.ByTheWayDriftService&logger=slf4j&methods=acceptOrderRateAndOvertimeRateListByCityIdList,queryGeoHashMonthSale,acceptOrderRateAndOvertimeRateByCityId&pid=13596&revision=1.0.0-20181204.071850-923&side=consumer&timeout=100×tamp=1544703743811
- 如果监听目录非空(即存在提供者)
- 如果没有提供者则使用empty协议,消费者与提供者匹配上,host使用消费者的host,side=consumer,放入urls
- 如果有提供者,消费者与提供者匹配上,放入urls
- notify(url:消费者, listener, urls:提供者) 通知
zk创建节点目录:path属性
提供者目录:/dubbo/com.dianwoba.dw.schedule.service.ByTheWayDriftService/providers
配置目录:/dubbo/com.dianwoba.dw.schedule.service.ByTheWayDriftService/configurators
路由目录:/dubbo/com.dianwoba.dw.schedule.service.ByTheWayDriftService/routers
返回目录下子文件,提供者URL:children属性
urls:
empty://192.168.103.186/com.dianwoba.dw.schedule.service.ByTheWayDriftService?application=dispatch-inverse-monitor&category=providers&check=false&default.check=false&default.retries=0&default.timeout=1000&dubbo=2.5.3&interface=com.dianwoba.dw.schedule.service.ByTheWayDriftService&logger=slf4j&methods=acceptOrderRateAndOvertimeRateListByCityIdList,acceptOrderRateAndOvertimeRateByCityId,queryGeoHashMonthSale&pid=15120&revision=1.0.0-20181204.071850-923&side=consumer&timeout=100×tamp=1544705297452
empty://192.168.103.186/com.dianwoba.dw.schedule.service.ByTheWayDriftService?application=dispatch-inverse-monitor&category=configurators&check=false&default.check=false&default.retries=0&default.timeout=1000&dubbo=2.5.3&interface=com.dianwoba.dw.schedule.service.ByTheWayDriftService&logger=slf4j&methods=acceptOrderRateAndOvertimeRateListByCityIdList,acceptOrderRateAndOvertimeRateByCityId,queryGeoHashMonthSale&pid=15120&revision=1.0.0-20181204.071850-923&side=consumer&timeout=100×tamp=1544705297452
empty://192.168.103.186/com.dianwoba.dw.schedule.service.ByTheWayDriftService?application=dispatch-inverse-monitor&category=routers&check=false&default.check=false&default.retries=0&default.timeout=1000&dubbo=2.5.3&interface=com.dianwoba.dw.schedule.service.ByTheWayDriftService&logger=slf4j&methods=acceptOrderRateAndOvertimeRateListByCityIdList,acceptOrderRateAndOvertimeRateByCityId,queryGeoHashMonthSale&pid=15120&revision=1.0.0-20181204.071850-923&side=consumer&timeout=100×tamp=1544705297452
dubbo://10.201.1.195:13540/com.dianwoba.rider.contract.provider.RiderContractProvider?accesslog=false&anyhost=true&application=rider-contract-unit-service&default.accesslog=false&default.retries=0&default.service.filter=-exception,asyncTransmitFilter,responseFilter&default.threadpool=monitorPool&dispatcher=message&dubbo=2.0.1&findByTime.timeout=500&generic=false&interface=com.dianwoba.rider.contract.provider.RiderContractProvider&logger=slf4j&methods=findByDateRange,findAllCainiaoByTime,findAllExpressByTime,findAllNonExpressRiders,findCityAllContractRidersByTime,findByTime,findAllByTime,findWithoutRecruitByDateRange&pid=1&side=provider&threadpool=monitorPool&threads=1000&timeout=2000×tamp=1544508939139
消费者URL与提供者URL匹配:
return (Constants.ANY_VALUE.equals(consumerGroup) || StringUtils.isEquals(consumerGroup, providerGroup) || StringUtils.isContains(consumerGroup, providerGroup))
&& (Constants.ANY_VALUE.equals(consumerVersion) || StringUtils.isEquals(consumerVersion, providerVersion))
&& (consumerClassifier == null || Constants.ANY_VALUE.equals(consumerClassifier) || StringUtils.isEquals(consumerClassifier, providerClassifier));
按照提供者URL封装Invoker
invoker = new InvokerDelegete(protocol.refer(serviceType, url), url, providerUrl);
graph LR
InvokerDelegete-->ListenerInvokerWrapper
ListenerInvokerWrapper-->ProtocolFilterWrapper
ProtocolFilterWrapper-->DubboInvoker
public Invoker refer(Class serviceType, URL url) throws RpcException {
// create rpc invoker.
DubboInvoker invoker = new DubboInvoker(serviceType, url, getClients(url), invokers);
invokers.add(invoker);
return invoker;
}
- getSharedClient缓存中获取,以ip:port为key缓存,获取如果不存在,则新建:initClient
- initClient 新建连接
public ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
}
graph LR
DecodeHandler-->HeaderExchangeHandler
HeaderExchangeHandler-->ExchangeHandlerAdapter
public NettyClient(final URL url, final ChannelHandler handler) throws RemotingException{
super(url, wrapChannelHandler(url, handler));
}
graph LR
MultiMessageHandler-->HeartbeatHandler
HeartbeatHandler-->AllChannelHandler
AllChannelHandler-->DecodeHandler
protected void doOpen() throws Throwable {
NettyHelper.setNettyLoggerFactory();
bootstrap = new ClientBootstrap(channelFactory);
// config
// @see org.jboss.netty.channel.socket.SocketChannelConfig
bootstrap.setOption("keepAlive", true);
bootstrap.setOption("tcpNoDelay", true);
bootstrap.setOption("connectTimeoutMillis", getTimeout());
final NettyHandler nettyHandler = new NettyHandler(getUrl(), this);
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
public ChannelPipeline getPipeline() {
NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyClient.this);
ChannelPipeline pipeline = Channels.pipeline();
pipeline.addLast("decoder", adapter.getDecoder());
pipeline.addLast("encoder", adapter.getEncoder());
pipeline.addLast("handler", nettyHandler);
return pipeline;
}
});
}
graph LR
MockClusterWrapper-->FailoverCluster
graph LR
MockClusterInvoker-->FailoverClusterInvoker
FailoverClusterInvoker-->InvokerDelegete
ExchangeClient currentClient;
if (clients.length == 1) {
currentClient = clients[0];
} else {
currentClient = clients[index.getAndIncrement() % clients.length];
}
按照非懒惰方式客户端梳理
protected com.alibaba.dubbo.remoting.Channel getChannel() {
Channel c = channel;
if (c == null || ! c.isConnected())
return null;
return NettyChannel.getOrAddChannel(c, getUrl(), this);
}
ChannelFuture future = channel.write(message);
if (sent) {
timeout = getUrl().getPositiveParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
success = future.await(timeout);
}
Throwable cause = future.getCause();
假定以netty协议为通信进行梳理
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
NettyChannel channel = NettyChannel.getOrAddChannel(ctx.getChannel(), url, handler);
try {
handler.received(channel, e.getMessage());
} finally {
NettyChannel.removeChannelIfDisconnected(ctx.getChannel());
}
}
graph LR
MultiMessageHandler-->HeartbeatHandler
HeartbeatHandler-->AllChannelHandler
AllChannelHandler-->DecodeHandler
DecodeHandler-->HeaderExchangeHandler
HeaderExchangeHandler-->DubboProtocol.ExchangeHandlerAdapter
- 如果是Request类型,如果是事件消息,则handle事件(判断是否READONLY事件,如果是,为Channel设置READONLY,否则,如果是twoway,判断请求data是否为空或者异常,是则直接响应Response,调用下一节点并响应Response;如果不是twoway直接调用下一节点
- 如果是Response类型,如果是心跳,则处理心跳消息
- 如果是String类型,则调用下一节点的telnet方法处理
- 兜底直接调用下一节点received
public void received(Channel channel, Object message) throws RemotingException {
if (message instanceof Invocation) {
reply((ExchangeChannel) channel, message);
} else {
super.received(channel, message);
}
}