Dubbo框架原理剖析

Dubbo框架原理剖析

文章目录

  • 背景
  • 简单介绍
    • 核心功能
  • 架构原理
    • 节点角色说明
    • 调用关系说明
  • 服务发布与引用
    • spring配置声明服务
    • spring解析配置
    • 如何暴露服务
    • 服务引用
  • 注册中心
  • 集群容错
  • 负载均衡策略
  • 管理控制台
  • 引入dubbo带来的一些问题
  • dubbo/springcloud比较






背景

随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进。

Dubbo框架原理剖析_第1张图片

单一应用架构

当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。

垂直应用架构

当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。

分布式服务架构

当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。

流动计算架构

当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键。




简单介绍

Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,SOA服务治理方案。

简单的说,dubbo就是个服务框架,如果没有分布式的需求,其实是不需要用的,只有在分布式的时候,才有dubbo这样的分布式服务框架的需求,并且本质上还是服务调用,远程服务调用的分布式框架。

核心功能

  • 远程通讯: 提供对多种基于长连接的NIO框架抽象封装,包括多种线程模型,序列化,以及“请求-响应”模式的信息交换方式。
    RPC的亮点在于将远程调用的细节隐藏起来,使得调用远程服务就像调用本地服务一样简单,而实现的方式就是代理

  • 集群容错:提供基于接口方法的透明远程过程调用,包括多协议支持,以及软负载均衡,失败容错,地址路由,动态配置等集群支持。

  • 自动发现:基于注册中心目录服务,使服务消费方能动态的查找服务提供方,使地址透明,使服务提供方可以平滑增加或减少机器。







架构原理

Dubbo框架原理剖析_第2张图片

节点角色说明

节点 角色说明
Provider 暴露服务的服务提供方
Consumer 调用远程服务的服务消费方
Registry 服务注册与发现的注册中心
Monitor 统计服务的调用次数和调用时间的监控中心
Container 服务运行容器

调用关系说明

  1. 服务容器负责启动,加载,运行服务提供者。
  2. 服务提供者在启动时,向注册中心注册自己提供的服务。
  3. 服务消费者在启动时,向注册中心订阅自己所需的服务。
  4. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
  5. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
  6. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。




服务发布与引用

spring配置声明服务

dubbo-provider.xml


<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
	http://code.alibabatech.com/schema/dubbo
	http://code.alibabatech.com/schema/dubbo/dubbo.xsd">

	
	<dubbo:application name="demo-provider"  />
	
	<dubbo:registry address="zookeeper://127.0.0.1:2181" register="false"/>
	
	<dubbo:protocol name="dubbo" port="20880" />
	
	<bean id="demoService" class="cn.demo.dubbo.demo.provider.DemoServiceImpl" />
	
	<dubbo:service interface="cn.demo.dubbo.demo.api.DemoService" ref="demoService" />
	
beans>

dubbo-consumer.xml


<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
	http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">

	<dubbo:application name="demo-consumer"  />
	<dubbo:registry address="zookeeper://127.0.0.1:2181" />
	<dubbo:protocol name="dubbo" port="20880" />
	<dubbo:reference id="demoService" interface="cn.demo.dubbo.demo.api.DemoService" check="false"/>
beans>

spring解析配置

spring.handlers文件中

http://code.alibabatech.com/schema/dubbo=
com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler

DefaultBeanDefinitionDocumentReader

	protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
		if (delegate.isDefaultNamespace(root)) {
			NodeList nl = root.getChildNodes();
			for (int i = 0; i < nl.getLength(); i++) {
				Node node = nl.item(i);
				if (node instanceof Element) {
					Element ele = (Element) node;
					if (delegate.isDefaultNamespace(ele)) {
						parseDefaultElement(ele, delegate);
					}else {
						delegate.parseCustomElement(ele);
					}
				}
			}
		}else {
			delegate.parseCustomElement(root);
		}
	}

DubboNamespaceHandler

public class DubboNamespaceHandler extends NamespaceHandlerSupport {
    static {
        Version.checkDuplicate(DubboNamespaceHandler.class);
    }
    public void init() {
        registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
        registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
        registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
        registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
        registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
        registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
        registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
        registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
        registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
        registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());
    }
}

如何暴露服务

Dubbo框架原理剖析_第3张图片

ServiceBean继承了ServiceConfig,在ServiceConfig方法里有段代码, 找到所有的注册中心,再遍历所有的协议注册

private void doExportUrls() {
        List<URL> registryURLs = loadRegistries(true);
        for (ProtocolConfig protocolConfig : protocols) {
            doExportUrlsFor1Protocol(protocolConfig, registryURLs);
        }
    }

registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&check=false&dubbo=2.0.1&pid=10772®istry=zookeeper×tamp=1546226393659

registry://127.0.0.1:6379/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&check=false&dubbo=2.0.1&pid=10772®istry=redis×tamp=1546226408125

Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
Exporter<?> exporter = protocol.export(wrapperInvoker);

1、所有的服务都封装成Invoker模型,在服务提供端,以及服务引用端均会出现 Invoker。Dubbo 官方文档中对 Invoker 进行了说明,这里引用一下。

Invoker 是实体域,它是 Dubbo 的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起 invoke 调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。

public interface Node {
    URL getUrl(); 
    boolean isAvailable();
    void destroy();
}

public interface Invoker<T> extends Node {
    Class<T> getInterface();
    /**
     * invoke.
     * @param invocation
     * @return result
     * @throws RpcException
     */
    Result invoke(Invocation invocation) throws RpcException;
}

2、RegistryProtocol暴露服务,开启Netty链接,保持对20881端口的通信,
同时向zk上注册临时节点,所谓的服务注册,本质上是将服务配置数据写入到 Zookeeper 的某个路径的节点下。
节点名:
/dubbo/cn.demo.dubbo.demo.api.DemoService/providers/dubbo://192.168.2.150:20881/cn.demo.dubbo.demo.api.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.1&generic=false&interface=cn.demo.dubbo.demo.api.DemoService&methods=sayHello,login&pid=10128&revision=0.0.1-SNAPSHOT&side=provider×tamp=1546236231866

Dubbo框架原理剖析_第4张图片

服务引用

服务引用是服务的消费方向注册中心订阅服务提供方提供的服务地址后向服务提供方引用服务的过程。

<dubbo:reference id="demoService" interface="cn.demo.dubbo.demo.api.DemoService" check="false"/>

spring在容器启动的时候会解析自定义的schema元素转换成dubbo内部数据结构ReferenceBean
Dubbo框架原理剖析_第5张图片
竟然实现了FactoryBean接口,
Dubbo框架原理剖析_第6张图片
为什么要实现FactoryBean,因为我们是要引用远程的服务,而并不是要ReferenceBean本身这个对象,get()方法里面肯定封装了返回接口的实际代理
ReferenceConfig.java

 private T createProxy(Map<String, String> map) {
 	....
 	// create service proxy
    return (T) proxyFactory.getProxy(invoker);
 }

JavassistProxyFactory.java javassist生成代理的工厂类

public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
        // 生成 Proxy 子类(Proxy 是抽象类)。并调用 Proxy 子类的 newInstance 方法创建 Proxy 实例
        return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
    }

InvokerInvocationHandler.java 拦截方法

public class InvokerInvocationHandler implements InvocationHandler {
    private final Invoker<?> invoker;
    public InvokerInvocationHandler(Invoker<?> handler) {
        this.invoker = handler;
    }
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        Class<?>[] parameterTypes = method.getParameterTypes();
        if (method.getDeclaringClass() == Object.class) {
            return method.invoke(invoker, args);
        }
        .....
        return invoker.invoke(new RpcInvocation(method, args)).recreate();
    }
}

最终调用DubboInvoker.java中doInvoke方法与提供方通信并等待返回执行结果

@Override
    protected Result doInvoke(final Invocation invocation) throws Throwable {
        RpcInvocation inv = (RpcInvocation) invocation;
        final String methodName = RpcUtils.getMethodName(invocation);
        inv.setAttachment(Constants.PATH_KEY, getUrl().getPath());
        inv.setAttachment(Constants.VERSION_KEY, version);

        ExchangeClient currentClient;
        if (clients.length == 1) {
            currentClient = clients[0];
        } else {
            currentClient = clients[index.getAndIncrement() % clients.length];
        }
        try {
            boolean isAsync = RpcUtils.isAsync(getUrl(), invocation);
            boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);
            int timeout = getUrl().getMethodParameter(methodName, Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
            if (isOneway) {
                boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);
                currentClient.send(inv, isSent);
                RpcContext.getContext().setFuture(null);
                return new RpcResult();
            } else if (isAsync) {
                ResponseFuture future = currentClient.request(inv, timeout);
                RpcContext.getContext().setFuture(new FutureAdapter<Object>(future));
                return new RpcResult();
            } else {
                RpcContext.getContext().setFuture(null);
                return (Result) currentClient.request(inv, timeout).get();
            }
        } catch (TimeoutException e) {
            throw new RpcException(RpcException.TIMEOUT_EXCEPTION, "Invoke remote method timeout. method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
        } catch (RemotingException e) {
            throw new RpcException(RpcException.NETWORK_EXCEPTION, "Failed to invoke remote method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
        }
    }





注册中心

注册中心承担着服务的注册与发现,服务的提供者将服务发布到注册中心,服务的使用者到注册中引用服务。
配置样例如下


<dubbo:registry address="zookeeper://10.20.153.10:2181" register="true" />

<dubbo:registry address="multicast://224.5.6.7:1234" />

<dubbo:registry address="redis://10.20.153.10:6379" />

简单介绍一下zk注册中心

Zookeeper 是 Apacahe Hadoop 的子项目,是一个树型的目录服务,支持变更推送,适合作为 Dubbo 服务的注册中心,工业强度较高,可用于生产环境,并推荐使用 。

Dubbo框架原理剖析_第7张图片

流程说明:

  • 服务提供者启动时: 向 /dubbo/com.foo.BarService/providers 目录下写入自己的 URL 地址
  • 服务消费者启动时: 订阅 /dubbo/com.foo.BarService/providers 目录下的提供者 URL 地址。并向 /dubbo/com.foo.BarService/consumers 目录下写入自己的 URL 地址
  • 监控中心启动时: 订阅 /dubbo/com.foo.BarService 目录下的所有提供者和消费者 URL 地址。

支持以下功能:

  • 当提供者出现断电等异常停机时,注册中心能自动删除提供者信息
  • 当注册中心重启时,能自动恢复注册数据,以及订阅请求
  • 当会话过期时,能自动恢复注册数据,以及订阅请求
  • 当设置 时,记录失败注册和订阅请求,后台定时重试
  • 可通过 设置 zookeeper 登录信息
  • 可通过 设置 zookeeper 的根节点,不设置将使用无根树
  • 支持 * 号通配符 ,可订阅服务的所有分组和所有版本的提供者





集群容错

作为一个分布式的框架,同一个服务肯定存在于多个提供方,当一个服务调用失败之后,就涉及到容错
在集群调用失败时,Dubbo 提供了多种容错方案,缺省为 failover 重试。

Dubbo框架原理剖析_第8张图片

将Directory中的多个Invoker伪装成一个Invoker, 对上层透明,包含集群的容错机制
Cluster 接口定义

public interface Cluster {
    <T> Invoker<T> join(Directory<T> directory) throws RpcException;
}

下图只列出了部分容错实现
在这里插入图片描述

Failover Cluster
失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过 retries="2" 来设置重试次数(不含第一次)。

重试次数配置如下:

<dubbo:reference retries="2" />

AvailableCluster
获取可用的调用。遍历所有Invokers判断Invoker.isAvalible,只要一个有为true直接调用返回,不管成不成功

Failfast Cluster

快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。

Failsafe Cluster

失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。

Failback Cluster

失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。

Forking Cluster

并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks="2" 来设置最大并行数。

Broadcast Cluster

广播调用所有提供者,逐个调用,任意一台报错则报错 。通常用于通知所有提供者更新缓存或日志等本地资源信息。





负载均衡策略

负载均衡负责从多个 Invokers中选出具体的一个Invoker用于本次调用,调用过程中包含了负载均衡的算法,调用失败后需要重新选择

LoadBalance接口定义

public interface LoadBalance {
    <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;
}

Dubbo框架原理剖析_第9张图片

在集群负载均衡时,Dubbo 提供了多种均衡策略,缺省为 random 随机调用。

Random LoadBalance

  • 随机,按权重设置随机概率。
  • 在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。

RoundRobin LoadBalance

  • 轮询,按公约后的权重设置轮询比率。
  • 存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。

LeastActive LoadBalance

  • 最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。
  • 使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。

ConsistentHash LoadBalance

  • 一致性 Hash,相同参数的请求总是发到同一提供者。
  • 当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。
  • 缺省只对第一个参数 Hash,如果要修改,请配置
  • 缺省用 160 份虚拟节点,如果要修改,请配置

    在这里插入图片描述



管理控制台

dubbo自带了一个简单的监控管理后台, dubbo-admin web 应用配置 应用配置 属性文件路径在dubbo-admin\WEB-INF\dubbo.properties中

dubbo.registry.address=zookeeper://127.0.0.1:2181
dubbo.admin.root.password=123456
dubbo.admin.guest.password=123456

这里需要指定注册中心地址, 因为对于服务的治理从获取相关信息这里需要指定注册中心地址, 因为对于服务的治理从获取相关信息如提供者、消费路由信息权重等

管理控制台演示地址 http://192.168.2.150:8090/dubbo-admin/



引入dubbo带来的一些问题

1、提供方和调用方接口依赖严重,大型工程很难协同
2、需要解决分布式事务

服务接口尽可能大粒度,每个服务方法应代表一个功能,而不是某功能的一个步骤,否则将面临分布式事务问题,Dubbo 暂未提供分布式事务支持。

3、支持的功能多,但也有很多鸡肋,比如支持的协议有:dubbo,rmi,http,webservice,redis,memcache,rest
4、太专注于服务治理,如果我们需要使用配置中心、分布式跟踪这些内容都需要自己去集成,这样无形中增加了使用 Dubbo 的难度,也减少了选择。
5、只支持JAVA
6、、、大家一起想想



dubbo/springcloud比较

1、背景 http://dubbo.apache.org/zh-cn/index.html
2、社区活跃度
3、功能支持
Dubbo框架原理剖析_第10张图片
4、学习成本,吃透难度
5、springcloud来搭建微服务体系对研发成员的要求更高
6、、、、

你可能感兴趣的:(JAVA)