1.什么是分布式系统?

1>.分布式系统是若干个独立计算机的集合,这些计算机对于用户来说就像单个相关系统; --<<分布式系统原理与范式>>

2>.分布式系统是建立在网络之上的软件系统;

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


2.Dubbo简介

1>.Apache Dubbo(incubating)是一款高性能的,轻量级的开源Java RPC框架,它提供了三大核心能力;面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现;

2>.Apache Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,同时他也是一个SOA服务治理方案;


3.Dubbo能做什么?

1>.透明化的远程方法调用,就像调用本地方法一样调用远程方法,只需要简单配置,没有任何API侵入;

2>.软负载均衡及其容错机制,可在内网替代F5等硬件负载均衡器,降低成本,减少单点;

软负载均衡:在分布式环境中,为了保证高可用性,通常同一个应用或同一个服务的提供方都会部署多份,达到对等服务(接受请求的几率是一样的).而消费者就须要在这些对等的服务器中选择一个来执行相关的业务逻辑;

3>.服务自动注册与发现,不再需要将服务提供方地址写死,注册中心基于接口名查询服务提供者的ip地址,并且能够平滑添加或者删除服务提供者;

4>.dubbo采用全spring配置方式,透明化接入应用,对应用没有任何API侵入,只需要用spring加载dubbo的配置即可;


3.Dubbo的核心组件

1>.如图:
Dubbo相关知识
2>.说明:

①.Registry: 注册中心(如zookeeper,redis);注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者;

②.Provider: 服务提供方; 暴露服务的服务提供方,服务提供者在启动时,向注册中心注册自己提供的服务;

③.Consumer: 服务消费方; 调用远程服务的服务消费方,服务消费者在启动时,向注册中心订阅自己所需的服务,服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用;

④.Monitor: 监控中心,统计服务的调用次数和调用时间;服务消费者和提供者会在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心;

⑤.Container: 提供服务的容器;


4.Dubbo运行流程

1>.如图:
Dubbo相关知识
2>.流程说明:

①.服务容器container负责启动,加载,运行服务提供者;

②.服务提供者provider在启动时,向注册中心注册自己要提供的服务,同时dubbo会生成对应的代理对象,这些代理对象会监听网络请求(看看是否有网络请求过来);

③.服务消费者consumer在启动时,向注册中心订阅自己所需要的服务,同时dubbo会生成对应的代理对象;

④.注册中心返回服务提供者注册的服务地址列表给消费者,如果注册中心里面的服务有变更,注册中心将基于长连接推送变更数据给消费者;

⑤.服务消费者通过代理对象从服务提供者注册的服务地址列表中基于软负载均衡算法选取一台服务提供者进行rpc远程调用,如果调用失败,再选取另一台调用;

⑥.服务消费者和提供者在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心;


5.Dubbo原理

5.1.Dubbo的设计架构

1>.如图:
Dubbo相关知识
2>.各层说明:

①.Service层:provider和consumer及相关接口,留给开发人员自己去实现的!

②.Config配置层:对外配置接口,以ServiceConfig,ReferenceConfig为中心,可以直接初始化配置类,也可以通过spring解析配置生成配置类;

③.Proxy服务代理层:服务接口透明代理,生成服务的客户端Stub和服务器端Skeleton,以ServiceProxy为中心,扩展接口为ProxyFactory;

④.Registry注册中心层:封装服务地址的注册与发现,以服务URL 为中心,扩展接口为RegistryFactory, Registry, RegistryService;

⑤.Cluster路由层:封装多个提供者的路由及负载均衡,并桥接注册中心,以Invoker 为中心,扩展接口为Cluster, Directory, Router, LoadBalance;

⑥.Monitor监控层:RPC 调用次数和调用时间监控,以Statistics 为中心,扩展接口为MonitorFactory, Monitor, MonitorService;

⑦.Protocol远程调用层:封装 RPC 调用,以Invocation, Result 为中心,扩展接口为Protocol, Invoker, Exporter;

⑧.Exchange信息交换层:封装请求响应模式,同步转异步,以Request,Response为中心,扩展接口为Exchanger,ExchangeChannel,ExchangeClient,ExchangeServer;

⑨.Transport网络传输层:抽象mina和netty为统一接口,以Message 为中心,扩展接口为Channel,Transporter,Client,Server,Codec;

⑩.Serialize数据序列化层:可复用的一些工具,扩展接口Serialization,ObjectInput,ObjectOutput,ThreadPool;


5.2.Dubbo标签解析流程

1>.如图:
Dubbo相关知识
2>.说明:

①.当spring容器启动时候,会加载配置文件,然后dubbo的名称空间处理器DubboNameSpaceHandler对象就会创建出dubbo的很多标签解析器(DubboBeanDefinitionParser);

②.标签解析器(DubboBeanDefinitionParser)会挨个解析dubbo的每一个标签,由于每一个标签都有自己对应的xxxcofig组件对象(例如,"application"标签对应着ApplicationConfig对象),所以会在对应的xxxcofig组件对象中(生成)装配一个对应的config对象;但是如果是"service"标签或者"reference"标签,则会在ServiceBean或者ReferenceBean组件对象中(生成)装配一个ServiceBean或者ReferenceBean对象;

注意:解析之后(生成)装配的对象会注入到spring容器中,由spring进行管理;


5.3.服务暴露流程

1>.如图:
Dubbo相关知识
2>.说明:

①.当spring容器启动时候,会加载配置文件,然后dubbo的解析器会解析配置文件中dubbo标签,由于配置文件中配置了"service"标签指定要暴露的服务,因此会在对应的ServiceBean组件对象中一个对应的ServiceBean对象;

②.当ServiceBean组件对象创建(/装配)完对象,所有的属性初始化完成之后会回调InitializingBean类中的afterpropertiesset()方法,将读取到的服务提供者service标签中的属性设置到ServiceBean对象中;

③.当ServiceBean组件对象将服务提供者的信息(/属性)都初始化完成,IOC容器被刷新之后会回调onApplicationEvent()方法调用ServiceConfig对象中的export()方法暴露服务(封装服务提供者的信息);

④.在ServiceConfig对象的export()方法中,调用doExport()方法执行暴露服务的业务逻辑.其中在方法中再通过doExportUrls()方法里面的doExportUrlsForProtocal()方法将用户配置的通信协议,注册中心地址信息,服务接口,服务接口的实现类等信息利用代理工厂包装成一个Invoker对象(wrapperInvoker),然后利用[用户配置的协议对应的protocol对象(例如dubboProtocol)]将invoker对象暴露出去;

⑤.在协议对应的protocol对象(如:RegistryProtocol)的在export()方法里面,先调用doLocalExport()方法做本地暴露,然后再调用ProviderConsumerRegTable.registerProvider()方法将服务提供者信息(包括服务提供者信息包装的invoker,注册中心地址信息,服务提供者地址信息等)注册到ProviderConsumerRegTable中,最后调用RegistryProtocol.register()方法根据注册中心地址信息,服务提供者地址信息将服务提供者信息注册到注册中心;

⑥.在doLocalExport()方法中:

I).先根据服务提供者信息包装的invoker创建一个invoker执行器(InvokerDelegete);

II).然后再调用[用户配置的协议对应的protocol对象(如:dubboProtocol)]的export(创建好的Invoker执行器)方法来初始化一个exporter对象实例,放入容器中,方便下次使用;

III).在[用户配置的协议对应的protocol对象(如:dubboProtocol)]的export(创建好的Invoker执行器)方法里面,先将传入的Invoker执行器的信息包装成一个DubboExporter对象,然后再调用openSever()方法,根据Invoker执行器中的服务提供者信息(服务提供者地址信息)开启/创建服务器(NettyServer),监听指定的端口,等待客户端的请求;

⑦.在ProviderConsumerRegTable类的registerProvider()方法中:

I).先根据传入的服务提供者信息(包括服务提供者信息包装的invoker,注册中心地址信息,服务提供者地址信息等)创建一个ProviderInvokerWrapper执行器对象;

II).再将这个ProviderInvokerWrapper执行器对象保存到一个set<>集合中,这个set集合是根据服务名称从一个全局的ConcurrentHashMap容器中获取的;

III).然后再把这个set<>集合保存到全局的ConcurrentHashMap容器中,将服务提供者信息缓存起来,当客户端要远程调用服务提供者的服务,就可以根据url地址从ProviderConsumerRegTable的ConcurrentHashMap容器中找到对应的服务的执行器,然后就可以调用了;


5.4.服务引用流程

1>.如图:
Dubbo相关知识
2>.说明:

①.当spring容器启动时候,会加载配置文件,然后dubbo的解析器会解析配置文件中dubbo标签,由于配置文件中配置了"reference"标签指定要引用的服务,因此会在对应的ReferenceBean组件对象中一个对应的ReferenceBean对象;

②.当ReferenceBean组件对象创建(/装配)完对象,所有的属性初始化完成之后会回调InitializingBean类中的afterpropertiesset()方法,将读取到的服务提供者"reference"标签中的属性设置到ReferenceBean对象中;

③.当在项目中通过"@Autowired"注解自动注入服务提供者暴露的服务(接口)时,spring就要从容器中查找对应的对象实例,由于ReferenceBean组件对象实现了FactoryBean接口,在内部他会调用ReferenceBean对象中的getObject()方法通过调用ReferenceConfig类的get()方法获取或者初始化对象引用:

I).如果是初始化操作,那么就会通过ReferenceConfig类的createProxy()方法去创建一个对应的远程服务接口代理对象;

④.在createProxy()方法的内部:

I).先根据配置文件中配置的信息(包括通信协议,注册中心地址,要调用的服务提供者接口等信息),利用Protocol协议对象(如:RegistryProtocol)的远程引用方法refer()从注册中心获取要引用的远程服务接口信息.在refer()中,通过doRefer()方法根据注册中心地址,远程引用的服务类型(即接口信息),服务提供者地址等信息去注册中心订阅服务提供者提供(暴露)的服务;

II).同时调用Protocol协议对象(如:DubboProtocol)的远程引用方法refer()根据要远程引用的服务类型(即接口信息),从注册中心获取到的远程引用的服务的地址信息等信息封装一个DubboInvoker执行器对象;在创建DubboInvoker执行器对象的过程中需要通过一个getClients(从注册中心获取到的远程引用的服务的地址信息)方法获取一个远程服务引用的客户端,通过这个客户端(客户端内部使用NettyClient进行通信)去和远程服务建立连接,进行通信;然后对这个DubboInvoker执行器对象进行多次包装,返回,最后在RegistryProtocol协议对象的doRefer()方法中被获取到,之后将包装之后的Invoker对象,远程注册中心里面对应的服务(接口)信息,远程引用的服务(接口)地址信息等信息注册到ProviderConsumerRegTable中,最终将包装之后的Invoker对象返回;

⑤.最后回到ReferenceConfig类的createProxy()方法,拿到Invoker对象,通过ProxyFactory.getProxy(Invoker)方法创建远程服务接口代理对象,最终把这个远程服务接口代理对象赋值给对象引用;这个代理对象中封装了要调用的远程服务对应的Invoker对象,而Invoker对象中又封装了远程服务的调用信息,包括Invoker的代理对象;


5.5.服务调用流程

1>.如图:
Dubbo相关知识
2>.说明:

①.通过服务引用最终会得到一个(远程服务的)代理对象,当执行远程服务接口中的方法时:

I).先调用InvokerInvocationHandler.invoke()方法,获取到要执行的方法,参数等信息,然后将方法,参数等信息封装成一个RpcInvocation对象;

II).然后调用MockClusterInvoker.invoke(RpcInvocation)方法;

III).再调用invoker父类AbstractClusterInvoker.invoke(invocation)方法,在invoke(invocation)方法内部,调用List(invocation)方法,根据invocation对象从注册中心中获取最新的invoker列表(即有多少个可以执行的Invoker对象)以及负载均衡策略;

IV).再调用FailoverClusterInvoke.doInvoke(invocation,Invoker,loadbalance)方法,在doInvoke()方法内部,根据负载均衡机制选择一个真正要执行的Invoker对象,然后调用InvokerWrapper.invoke(invocation)方法…(跳过中间的组件)

V).来到AbstractInvoker.invoke(invocation)方法,在invoke()方法内部,调用DubboInvoker.doInvoke(invocation)方法,在方法里面执行真正的远程调用的逻辑;

VI).在DubboInvoker的doInvoke(invocation)方法中,首先获取到方法名称等信息,然后获取到在服务引用阶段封装到DubboInvoker执行器对象中的一个远程服务引用的客户端,通过这个客户端(客户端内部使用NettyClient进行通信)去和远程服务建立连接,发起请求,获取结果,然后进行解码,最终响应给客户端;

注意:在请求过程中如果请求某一台服务器上的服务失败了,那么他会重新请求另一台服务器上的服务;如果请求超时了,会根据配置进行重试!


扩展:注册中心挂了,还可以继续通信吗?

答:可以,因为刚开始初始化的时候,消费者会从注册中心提供者的地址等信息拉取到本地进行缓存,如果注册中心挂了,还可以从本地缓存中找到对应服务的地址信息,继续进行通信;

相关文章: