Dubbo详解

Dubbo从入门到源码

Dubbo的基本应用

什么是Dubbo

Dubbo是阿里巴巴公司开源的一个高性能、轻量级的 Java RPC 框架

致力于提供高性能和透明化的 RPC 远程服务调用方案,以及 SOA 服务治理方案。

Dubbo的发展历史

2011/10/27: 阿里巴巴巴宣布 Dubbo 开源。

2012/10/23: 发布最后一个版本 2.5.3 并停止维护更新。

2017/07/31: 起死回生,官方宣布开启重新更新,并会得到重点维护.

2017/09/07: 发布起死回生的第一个版本:dubbo-2.5.4

2018/01/08:

1、Dubbo 团队透露 Dubbo 3.0 宣布正式开工

2、发布了 dubbo-2.6.0 版本,主要合并了由当当网开源的 dubbox 项目分支。PS:dubbo停止维护期间,当当网基于 dubbo 开源了dubbox。

2018/01/22: Dubbo Spring Boot 版正式发布:dubbo-spring-boot-starter v1.0.0 公测版。

2018/02/09: Dubbo 通过投票正式进入 Apache 基金会孵化器,更新了 Apache 官方域名,也不再仅限于 Java 语言。

2019/05/20: Apache 软件基金会宣布 Dubbo 正式毕业,成为 Apache 的顶级项目。

2021/7月1日 : Dubbo 3.0版本正式发布

Dubbo主要特性

  • 面向接口代理的高性能RPC调用:提供高性能的基于代理的远程调用能力,服务以接口为粒度,屏蔽了远程调用底层细节。
  • 智能负载均衡:内置多种负载均衡策略,智能感知下游节点健康状况,显著减少调用延迟,提高系统吞吐量。
  • 服务自动注册与发现:支持多种注册中心服务,服务实例上下线实时感知。
  • 高度可扩展能力:遵循微内核+插件的设计原则,所有核心能力如Protocol、Transport、Serialization被设计为扩展点,平等对待内置实现和第三方实现。
  • 运行期流量调度:内置条件、脚本等路由策略,通过配置不同的路由规则,轻松实现灰度发布,同机房优先等功能。
  • 可视化的服务治理与运维:提供丰富服务治理、运维工具:随时查询服务元数据、服务健康状态及调用统计,实时下发路由策略、调整配置参数。

Spring Cloud 团队

Spring Cloud Alibaba

HTTP 短连接 重新建立TCP连接

RPC Dubbo Spring Cloud

Dubbo的基本使用

Dubbo支持的注册中心

consul

zk

Eureka

Redis

ectd

nacos

Dubbo Spring Cloud

DubboSpring Cloud
服务注册中心ZookeeperSpring Cloud Netfix Eureka
服务调用方式RPCREST API
服务监控Dubbo-monitorSpring Boot Admin
熔断器不完善Spring Cloud Netflix Hystrix
服务网关Spring Cloud Netflix Zuul
分布式配置Spring Cloud Config
服务跟踪Spring Cloud Sleuth
数据流Spring Cloud Stream
批量任务Spring Cloud Task
信息总线Spring Cloud Bus

Nacos

Dubbo Spring Cloud

Spring Cloud

功能组件Spring CloudDubbo Spring Cloud
分布式配置(Distributed configuration)Git、Zookeeper、Consul、JDBCSpring Cloud 分布式配置 + Dubbo 配置中心
服务注册与发现(Service registration and discovery)Eureka、Zookeeper、ConsulSpring Cloud 原生注册中心 + Dubbo 原生注册中心
负载均衡(Load balancing)Ribbon(随机、轮询等算法)Dubbo 内建实现(随机、轮询等算法 + 权重等特性)
服务熔断(Circuit Breakers)Spring Cloud HystrixSpring Cloud Hystrix + Alibaba Sentinel 等
服务调用(Service-to-service calls)Open Feign、RestTemplateSpring Cloud 服务调用 + Dubbo @Reference
链路跟踪(Tracing)Spring Cloud Sleuth + ZipkinZipkin、opentracing 等

以上对比表格摘自Dubbo Spring Cloud官方文档。

本质上 都是为业务而生

Dubbo多注册中心

Dubbo多协议支持

Dubbo源码

怎么学? spring的2.5之后 ,Spring支持自定义Schema扩展XML配置

1.Xml Schema

<bean id="dubbo-server" class="com.alibaba.dubbo.config.ApplicationConfig"/>  
    <bean id="registryConfig" class="com.alibaba.dubbo.config.RegistryConfig">  
        <property name="address" value="192.168.88.88:2181"/>  
        <property name="protocol" value="zookeeper"/>  
    </bean>  
    <bean id="dubbo" class="com.alibaba.dubbo.config.ProtocolConfig">  
        <property name="port" value="20880"/>  
    </bean>  
    <bean id="com.msb.dubbo.server.IloginService" class="com.alibaba.dubbo.config.spring.ServiceBean">  
        <property name="interface" value="com.msb.dubbo.server.IloginService"/>  
        <property name="ref" ref="loginService"/>  
    </bean>  
    <bean id="loginService" class="com.msb.dubbo.server.IloginServiceImpl" />  
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {

        String name = protocolConfig.getName();
        if (StringUtils.isEmpty(name)) {
            name = DUBBO;
        }
s
        Map<String, String> map = new HashMap<String, String>();
        //("side":"provide")
        map.put(SIDE_KEY, PROVIDER_SIDE);

        ServiceConfig.appendRuntimeParameters(map);
        AbstractConfig.appendParameters(map, getMetrics());
        AbstractConfig.appendParameters(map, getApplication());
        AbstractConfig.appendParameters(map, getModule());
        // remove 'default.' prefix for configs from ProviderConfig
        // appendParameters(map, provider, Constants.DEFAULT_KEY);
        AbstractConfig.appendParameters(map, provider);
        AbstractConfig.appendParameters(map, protocolConfig);
        AbstractConfig.appendParameters(map, this);
        MetadataReportConfig metadataReportConfig = getMetadataReportConfig();

        if (metadataReportConfig != null && metadataReportConfig.isValid()) {
            map.putIfAbsent(METADATA_KEY, REMOTE_METADATA_STORAGE_TYPE);
        }

        if (CollectionUtils.isNotEmpty(getMethods())) {
            for (MethodConfig method : getMethods()) {
                AbstractConfig.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<ArgumentConfig> arguments = method.getArguments();
                if (CollectionUtils.isNotEmpty(arguments)) {
                    for (ArgumentConfig argument : arguments) {
                        // convert argument type
                        if (argument.getType() != null && argument.getType().length() > 0) {
                            Method[] methods = interfaceClass.getMethods();
                            // visit all methods
                            if (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())) {
                                                AbstractConfig.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())) {
                                                    AbstractConfig.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) {
                            AbstractConfig.appendParameters(map, argument, method.getName() + "." + argument.getIndex());
                        } else {
                            throw new IllegalArgumentException("Argument config must set index or type attribute.eg: <dubbo:argument index='0' .../> or <dubbo:argument type=xxx .../>");
                        }

                    }
                }
            } // end of methods for
        }
        //泛化方式
        if (ProtocolUtils.isGeneric(generic)) {
            map.put(GENERIC_KEY, generic);
            map.put(METHODS_KEY, ANY_VALUE);
        } else {
            String revision = Version.getVersion(interfaceClass, version);
            if (revision != null && revision.length() > 0) {
                map.put(REVISION_KEY, revision);
            }
            //获取我们的包装类
            String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
            if (methods.length == 0) {
                logger.warn("No method found in service interface " + interfaceClass.getName());
                map.put(METHODS_KEY, ANY_VALUE);
            } else {
                map.put(METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
            }
        }

        /**
         * Here the token value configured by the provider is used to assign the value to ServiceConfig#token
         */
        if(ConfigUtils.isEmpty(token) && provider != null) {
            token = provider.getToken();
        }

        if (!ConfigUtils.isEmpty(token)) {
            if (ConfigUtils.isDefault(token)) {
                map.put(TOKEN_KEY, UUID.randomUUID().toString());
            } else {
                map.put(TOKEN_KEY, token);
            }
        }
        //init serviceMetadata attachments
        serviceMetadata.getAttachments().putAll(map);

        // export service
        String host = findConfigedHosts(protocolConfig, registryURLs, map);
        Integer port = findConfigedPorts(protocolConfig, name, map);
        URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);

        // You can customize Configurator to append extra parameters
        if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                .hasExtension(url.getProtocol())) {
            url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                    .getExtension(url.getProtocol()).getConfigurator(url).configure(url);
        }

        String scope = url.getParameter(SCOPE_KEY);
        // don't export when none is configured
        if (!SCOPE_NONE.equalsIgnoreCase(scope)) {

            // export to local if the config is not remote (export to remote only when config is remote)
            if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {
                exportLocal(url);
            }
            // export to remote if the config is not local (export to local only when config is local)
            if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
                if (CollectionUtils.isNotEmpty(registryURLs)) {
                    for (URL registryURL : registryURLs) {
                        //if protocol is only injvm ,not register
                        if (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
                            continue;
                        }
                        url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY));
                        URL monitorUrl = ConfigValidationUtils.loadMonitor(this, registryURL);
                        if (monitorUrl != null) {
                            url = url.addParameterAndEncoded(MONITOR_KEY, monitorUrl.toFullString());
                        }
                        if (logger.isInfoEnabled()) {
                            if (url.getParameter(REGISTER_KEY, true)) {
                                logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
                            } else {
                                logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
                            }
                        }

                        // For providers, this is used to enable custom proxy to generate invoker
                        String proxy = url.getParameter(PROXY_KEY);
                        if (StringUtils.isNotEmpty(proxy)) {
                            registryURL = registryURL.addParameter(PROXY_KEY, proxy);
                        }
                        //?
                        Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
                        DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
                        //?
                        Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
                        exporters.add(exporter);
                    }
                } else {
                    if (logger.isInfoEnabled()) {
                        logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
                    }
                    Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url);
                    DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);

                    Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
                    exporters.add(exporter);
                }
                /**
                 * @since 2.7.0
                 * ServiceData Store
                 */
                WritableMetadataService metadataService = WritableMetadataService.getExtension(url.getParameter(METADATA_KEY, DEFAULT_METADATA_STORAGE_TYPE));
                if (metadataService != null) {
                    metadataService.publishServiceDefinition(url);
                }
            }
        }
        this.urls.add(url);
    }

Dubbo扩展点

指定名称的扩展点

ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("name");

自适应的扩展点

ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

激活扩展点

ExtensionLoader.getExtensionLoader(Protocol.class).getExtension();

指定名称的拓展点

或者说你直接从指定文件中找到我们的name

我们如何判定dubbo中哪些类是拓展点呢?

找出拓展点并加载的流程

1.找到路径:在我们的META-INF/dubbo/inertnal下面

random=org.apache.dubbo.rpc.cluster.loadbalance.RandomLoadBalance
roundrobin=org.apache.dubbo.rpc.cluster.loadbalance.RoundRobinLoadBalance
leastactive=org.apache.dubbo.rpc.cluster.loadbalance.LeastActiveLoadBalance
consistenthash=org.apache.dubbo.rpc.cluster.loadbalance.ConsistentHashLoadBalance
shortestresponse=org.apache.dubbo.rpc.cluster.loadbalance.ShortestResponseLoadBalance

3.Package.Class

4.解析文件 properties文件的解析

5.内容放进内存 key(name) value =org.apache.dubbo.rpc.cluster.loadbalance.RandomLoadBalance

6.key(name) value =Class newInstance

自适应拓展点

在运行期间,我们会根据上下文来判断当前你返回哪个拓展点。

getAdaptiveExtension()

标识位:

@Adaptive

  • 声明在我们的方法级别
  • 声明在我们的类级别

实现原理:

  • 如果修饰在方法级别,那么我们会动态的创建一个代理类 (javassist)
  • 如果修饰的是类,那么我们直接返回被修饰的这个类

如果我们当前架子啊的扩展点存在自适应的类,那么我们直接返回,

否则,我们会动态的创建一个字节码,然后进行返回。

激活拓展点

相当于Spring当中的Conditional

@ConditionalOnBean(XXX.class)

只要你的@Activate注解中有value,那么这个时候我们当前的Filter就会被激活。

1.判断是否要发布到远程服务,或者说是否需要发布服务,如果为none,那么我们就不需要发布

2.循环遍历注册中心的配置,实际上这里就是对多注册中心支持的最好体现

Protoco$Adaptive --> export方法

1.进行了Netty服务的启动

2.进行服务注册

protocol = protocol$Adaptive

Protocol = Wrapper(((DubboProtocol)))

Dubbo – Netty4

ServerSocket serverSocket;

20880(默认端口)

RegistryFactoryWrapper(ZookeeperRegistryFactory)

服务注册的总结:

1.生成invoke对象

2.doLocalExport方法发布一个本地服务,实际上就是利用配置去进行一个监听

3.通过Registy去进行服务注册

invoker对象是什么

对象实例 — 保存在Map集合中的

收到客户端的请求,(方法名称,接口的全路径,参数,参数类型)

根据接口的全路径做为key,找到实例方法,反射调用他

Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));

1.如果假设我们的Url中有配置了proxy,我就按照我们Proxy参数的配置来进行查找

但是,如果没有配置,那么我们采用默认的拓展点


public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
        final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf(36) < 0 ? proxy.getClass() : type);
        return new AbstractProxyInvoker<T>(proxy, type, url) {

          //当我们的服务端接受到请求之后,我们接下来调用具体的方法
            protected Object doInvoke(T proxy, String methodName, Class<?>[] parameterTypes, Object[] arguments) throws Throwable {
                return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
            }
        };
    }

invoker本身就是一个调用器

服务本身会以我们invoker的形态存在

服务消费

关键性注解

1.生成远程服务的代理

2.获取目标服务的Url

2.1 建立与注册中心的动态感知

3.网络连接的建立(启动时创建还是通信是创建?) --> Dubbo启动时创建 Netty(nio)

4.服务通信

4.1 filter过滤

4.2 负载均衡

4.3 容错

用到Spring的一系列知识

服务消费端的操作

服务消费端的对象的注入
  • Xml
  • 注解的方式

BeanPostProcessor

  • 19
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一只经常emo的程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值