转载:Dubbo架构设计详解
转载:架构设计:系统间通信(15)——服务治理与Dubbo 上篇
转载:架构设计:系统间通信(16)——服务治理与Dubbo 中篇(预热)
转载:架构设计:系统间通信(17)——服务治理与Dubbo 中篇(分析)
转载:架构设计:系统间通信(18)——服务治理与Dubbo 下篇(继续分析)
一、Dubbo
DUBBO是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,是阿里巴巴SOA服务化治理方案的核心框架,并被广泛应用于阿里巴巴集团的各成员站点。
Dubbo最大的特点是按照分层的方式来架构,使用这种方式可以使各个层之间解耦合(或者最大限度地松耦合)。从服务模型的角度来看,Dubbo采用的是一种非常简单的模型,要么是提供方提供服务,要么是消费方消费服务,所以基于这一点可以抽象出服务提供方(Provider)和服务消费方(Consumer)两个角色。关于注册中心、协议支持、服务监控等内容,详见后面描述。
二、Dubbo能做到的事情
除了RPC能做到的服务调用和服务管理注册等基本功能之外,Dubbo还能做到以下事情:
1. 访问权限:在整个系统生态环境中,不是任何用户都可以随意访问任何的接口。那么除了访问接口的用户组、用户和密码管理以外(或者是公私钥文件),还需要限制用户的访问权限。例如规定用户组A下所有的用户才能够访问接口X。
2. 版本控制:就像上篇文中描述的问题一样。在比较大的系统中,某一个子系统一般都是使用集群环境运行(有多个工作节点),且必须24小时连续工作不允许中断。那么要进行服务接口升级,一般是先升级一部分服务节点,其间让另一部分服务节点继续提供服务,造成了在一个时间段同一个子系统提供的某一个服务可能出现服务接口版本的不兼容。为了子系统能够保证24小时连续提供服务,就需要标注服务接口的版本号,让之前没有完成升级的服务节点提供/访问老版本的服务接口;已经完成升级的服务节点提供/访问新版本的服务接口。
3. 服务时效控制、次数控制:各个子系统提供的服务本身也是具有时效性的。比如某一个服务Y只在每天早上10:00——11:00才能提供访问服务,且对于某个用户来说每天只能调用100次。
4. 性能措施:性能是评价一款服务治理框架是否优秀的重要标准。在服务治理框架的各层都应该有足够的性能优化措施。例如在服务注册层/服务仓库层,注册通知是如何实现的、“能够提供的”服务列表是否进行本地缓存、缓存何时更新、访问令牌如何实现、如何确定访问节点等等问题都直接影响性能;在RPC层/服务实现层,如何支持非阻塞异步网络IO、是采用现成的网络通信框架(Netty、MINA、Grizzly等等)还是自行编写网络通信框架、如何进行信息的序列化等等问题也会直接影响性能。
5. 其他细节问题:能够应用到生产环境的服务治理框架还需要关注很多细节问题,例如多个子系统所注册服务可能出现的服务名称重复的问题,类似的这种问题如何解决、服务治理框架本身的稳定性如何进行保证、是否需要支持跨语言通信的问题等等。
三、Dubbo的使用方式
1. Maven配置
在工程中引入maven的支持。并且导入相关的依赖:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>yinwenjie.test</groupId>
<artifactId>dubboService</artifactId>
<version>0.0.1</version>
<packaging>jar</packaging>
<name>dubboService</name>
<url>http://maven.apache.org</url>
<properties>
<version.log4j>1.2.14</version.log4j>
<version.slf4j.api>1.7.5</version.slf4j.api>
<version.slf4j.log4j12>1.7.5</version.slf4j.log4j12>
<version.slf4j.jcl.over.slf4j>1.7.5</version.slf4j.jcl.over.slf4j>
<!-- jackson -->
<jackson.version>1.9.13</jackson.version>
</properties>
<dependencies>
<!-- apache commons -->
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.5</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${version.slf4j.api}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${version.slf4j.jcl.over.slf4j}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${version.slf4j.log4j12}</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${version.log4j}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.7.4</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.7.4</version>
</dependency>
<!-- dubbo -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.4.10</version>
</dependency>
<!-- zookeeper -->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.3.4</version>
</dependency>
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.3</version>
</dependency>
</dependencies>
</project>
zookeeper可以作为DUBBO服务管理仓库使用,但是并非只有Zookeeper才可以作为服务管理仓库使用,Redis也可以作为服务管理仓库使用。
2. Spring配置
由于DUBBO和Spring是无缝集成的,所以当您通过maven引入DUBBO组件时,Spring的基础组件就会被自动引入。DUBBO V2.4.10 版本使用的是Spring 2.5.6的版本。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>yinwenjie.test</groupId>
<artifactId>dubboService</artifactId>
<version>0.0.1</version>
<packaging>jar</packaging>
<name>dubboService</name>
<url>http://maven.apache.org</url>
<properties>
<version.log4j>1.2.14</version.log4j>
<version.slf4j.api>1.7.5</version.slf4j.api>
<version.slf4j.log4j12>1.7.5</version.slf4j.log4j12>
<version.slf4j.jcl.over.slf4j>1.7.5</version.slf4j.jcl.over.slf4j>
<!-- jackson -->
<jackson.version>1.9.13</jackson.version>
</properties>
<dependencies>
<!-- apache commons -->
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.5</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${version.slf4j.api}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${version.slf4j.jcl.over.slf4j}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${version.slf4j.log4j12}</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${version.log4j}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.7.4</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.7.4</version>
</dependency>
<!-- dubbo -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.4.10</version>
</dependency>
<!-- zookeeper -->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.3.4</version>
</dependency>
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.3</version>
</dependency>
</dependencies>
</project>
3. 编写服务接口和实现
服务接口类如下:
package yinwenjie.test.dubboService.iface;
/**
* 这是服务接口
* @author yinwenjie
*/
public interface MyService {
/**
* 这是一个测试接口
* @param field1 参数1
* @param field2 参数2
* @return 相加后返回
*/
public String doMyTest(String field1 , String field2);
}
服务接口类的实现如下:
package yinwenjie.test.dubboService.impl;
import org.springframework.stereotype.Component;
import yinwenjie.test.dubboService.iface.MyService;
@Component("MyServiceImpl")
public class MyServiceImpl implements MyService {
/* (non-Javadoc)
* @see yinwenjie.test.dubboService.iface.MyService#doMyTest(java.lang.String, java.lang.String)
*/
@Override
public String doMyTest(String field1, String field2) {
return field1 + field2;
}
}
补充两个注意事项:
1. 可以看出 服务接口和服务接口的实现本身并没有引入和RPC结束任何相关的类,也就是说 服务接口和服务接口的实现甚至自己都不知道将被某个RPC框架所调用。
2. dubbo:protocol标签中,我们指定的协议名称为 dubbo(小写)。这里的dubbo是指的DUBBO服务治理框架中自带的一种 RPC协议。就像前文说到的那样,从官网给出的资料来看,DUBBO服务治理框架还支持 rmi、 http、 webService、 Hessian、Thrift、memcached和 Redis协议。
4. DUBBO-RPC服务端
package yinwenjie.test.dubboService;
import org.apache.log4j.BasicConfigurator;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class ServiceMainProcessor {
//Log4j的日志基本配置(要使用这种方式首先引入log4j的支持)
static {
BasicConfigurator.configure();
}
private static Object WAITOBJECT = new Object();
public static void main(String[] args) throws Exception {
new ClassPathXmlApplicationContext(new String[]{"application-service.xml"});
/*
* 这里锁定这个应用程序,和DUBBO框架本身的工作原理没有任何关系,只是为了让其不退出
* 当然您也可以使用ClassPathXmlApplicationContext中的start方法,效果一样。
*
* 个人习惯
* */
synchronized (ServiceMainProcessor.WAITOBJECT) {
ServiceMainProcessor.WAITOBJECT.wait();
}
}
}
5. DUBBO-RPC客户端
以下是RPC服务消费方(RPC客户端)的xml配置。在我们的测试工程中,客户端和服务端是放在一个工程里面的。放在一个工程里面是为了节约演示程序在工程设置方面不必要的工作量,但是这不影响它们在两个独立的JVM上面运行,也不影响DUBBO服务治理框架工作原理的演示。下面的XML是DUBBO服务治理框架RPC客户端的spring配置演示:
<?xml version="1.0" encoding="UTF-8"?>
<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.xsd
http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<!-- 客户端应用程称呼名称 -->
<dubbo:application name="ws-demo-client" />
<!-- 注册仓库地址:zookeeper组件,192.168.61.128:2181 -->
<dubbo:registry address="zookeeper://192.168.61.128:2181" />
<!-- 引用的服务,那个interface一定是一个被引入到DUBBO仓库的接口定义 -->
<dubbo:reference id="myService" interface="yinwenjie.test.dubboService.iface.MyService"/>
</beans>
对于DUBBO-RPC框架来说,它其中的服务消费者并不需要进行多余的任何定义。只需要知道要访问的服务接口(和定义的服务版本,如果没有定义服务版本默认为0.0.0)。下面的代码将启动DUBBO-RPC客户端,并且完成RPC调用:
package yinwenjie.test.dubboClient;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.log4j.BasicConfigurator;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import yinwenjie.test.dubboService.iface.MyService;
public class ClientMainProcessor {
//Log4j的日志基本配置(要使用这种方式首先引入log4j的支持)
static {
BasicConfigurator.configure();
}
/**
* 日志
*/
private static final Log LOGGER = LogFactory.getLog(ClientMainProcessor.class);
/**
* 锁定用
*/
private static Object WAITOBJECT = new Object();
public static void main(String[] args) throws Exception {
ApplicationContext app = new ClassPathXmlApplicationContext(new String[]{"application-client.xml"});
// 开始RPC调用
MyService myService = (MyService)app.getBean("myService");
LOGGER.info("myService = " + myService.doMyTest("1234", "abcde"));
// 这里锁定这个应用程序,和DUBBO框架本身的工作原理没有任何关系,只是为了让其不退出
synchronized (ClientMainProcessor.WAITOBJECT) {
ClientMainProcessor.WAITOBJECT.wait();
}
}
}
运行之后可以在打印中观察到服务端注册的服务被成功调用(转载:
架构设计:系统间通信(15)——服务治理与Dubbo 上篇)
四、Dubbo的实现原理分析
Dubbo的技术框架如下图所示:
DUBBO官方文档中,对于上图中各层的功能描述:
1. config:配置层,对外配置接口,以ServiceConfig,ReferenceConfig为中心,可以直接new配置类,也可以通过spring解析配置生成配置类。
2. proxy:服务代理层,服务接口透明代理,生成服务的客户端Stub和服务器端Skeleton,以ServiceProxy为中心,扩展接口为ProxyFactory。
3. registry:注册中心层,封装服务地址的注册与发现,以服务URL为中心,扩展接口为RegistryFactory,Registry,RegistryService。
4. cluster:路由层,封装多个提供者的路由及负载均衡,并桥接注册中心,以Invoker为中心,扩展接口为Cluster,Directory,Router,LoadBalance。
5. monitor:监控层,RPC调用次数和调用时间监控,以Statistics为中心,扩展接口为MonitorFactory,Monitor,MonitorService 。
6. protocol:远程调用层,封将RPC调用,以Invocation,Result为中心,扩展接口为Protocol, Invoker, Exporter。
7. exchange:信息交换层,封装请求响应模式,同步转异步,以Request, Response为中心,扩展接口为Exchanger,ExchangeChannel,ExchangeClient,ExchangeServer。
8. transport:网络传输层,抽象mina和netty为统一接口,以Message为中心,扩展接口为Channel,Transporter,Client,Server,Codec。
9. serialize:数据序列化层,可复用的一些工具,扩展接口为Serialization,ObjectInput,ObjectOutput,ThreadPool。
1. 扩展机制的实现
在前文我们已经提到过,一个接口如果存在多个实现,那么我们必须依靠new关键字来告诉调用者这个接口的具体实现;用new关键的位置和时机都是非常重要的,因为这代表者调用者需要了解‘具体实现’;前文还提到了Spring框架使用‘bean’配置关键字的形式帮我们解决了new关键字的问题,让调用者本身不需要关注所调用接口的具体实现。但是在和Spring框架相对独立的DUBBO框架中,如何达到这样的效果呢?
DUBBO框架扩展了(或者说另外实现了)基于标准JAVA的“服务自动发现”机制;为了说清楚DUBBO是如何找到某个内部接口的实现类的,我们首先就要讲清楚JAVA的SPI机制,并且再对DUBBO进行了哪些扩展进行一些必要的说明。
1.1 JAVA自带的SPI
对于JAVA中的接口和实现,我们一般情况下,会采用如下的方式进行定义和使用:
业务接口定义(BusinessInterface):
public interface HelloService{
public void hello(String username);
}
业务接口的真实实现类(DefaultHelloService):
public class DefaultHelloService implements HelloService {
public void hello(String username) {
System.out.println("正在为用户:" + username + ",进行真实的业务处理。。。");
}
public static void main(String[] args) throws RuntimeException {
BusinessInterface realBusiness = new RealBusinessImpl();
realBusiness.hello("DefaultHelloService");
}
}
实际上,从JDK1.5版本开始,您无需使用new关键字指定具体的实现类。您可以在META-INF/searvices文件夹下建立一个名叫xxxx.HelloService的文件(注意xxxx代表您的包名,整个文件名与HelloService接口的完整类名相同),然后在文件内容中书写“xxxxx.DefaultHelloService”(注意是完整HelloService接口实现类的名字)。保存这个文件后,您就可以通过JDK提供的java.util.ServiceLoader工具类实例化这个接口了。
其中META-INF/services/文件中的内容如下所示:
com.demo.dubbo.demo.spi.service.impl.DefaultHelloService
com.demo.dubbo.demo.spi.service.impl.CustomHelloService
代码片段如下:
ServiceLoader<HelloService> helloServiceLoader=ServiceLoader.load(HelloService.class);
for(HelloService item:helloServiceLoader){
item.hello();
}
1.2 ServiceLoader的源码分析
在SPI机制中ServiceLoader是整个实现的核心,其核心对象有:
private Class<S> service;
// The class loader used to locate, load, and instantiate providers
private ClassLoader loader;
// 用于缓存已经加载的接口实现类,其中key为实现类的完整类名
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// 用于延迟加载接口的实现类
private LazyIterator lookupIterator;
第一步:获取一个ServiceLoader<HelloService> helloServiceLoader实例,此时还没有进行任何接口实现类的加载操作,属于延迟加载类型的。只是创建了
LazyIterator lookupIterator对象而已。
第二步:ServiceLoader实现了 Iterable接口,即实现了该接口的 iterator()方法,实现内容如下:
public Iterator<S> iterator() {
return new Iterator<S>() {
Iterator<Map.Entry<String,S>> knownProviders
= providers.entrySet().iterator();
public boolean hasNext() {
if (knownProviders.hasNext())
return true;
return lookupIterator.hasNext();
}
public S next() {
if (knownProviders.hasNext())
return knownProviders.next().getValue();
return lookupIterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
for循环遍历ServiceLoader的过程其实就是调用上述
hasNext()和
next()方法的过程,第一次循环遍历会使用
lookupIterator去查找,之后就缓存到
providers中。LazyIterator会去加载类路径下/META-INF/services/接口全称 文件的url地址,使用如下代码来加载:
String fullName = "META-INF/services/" + service.getName();
loader.getResources(fullName)
文件加载并解析完成之后,得到一系列的接口实现类的完整类名,调用next()方法时才回去真正执行接口实现类的加载操作,并根据无参构造器创建出一个实例,存到providers中;之后再次遍历ServiceLoader,就直接遍历providers中的数据
ServiceLoader缺点分析
1. 虽然ServiceLoader也算是使用的 延迟加载,但是基本只能通过遍历全部获取,也就是接口的实现类全部加载并实例化一遍。如果你并不想用某些实现类,它也被加载并实例化了,这就造成了浪费。
2. 获取某个实现类的方式不够灵活,只能通过 Iterator形式获取,不能根据某个参数来获取对应的实现类
1.3 dubbo的SPI实现
dubbo的扩展机制和java的SPI机制非常相似,但是又增加了如下功能:
1. 可以方便的获取某一个想要的扩展实现,java的SPI机制就没有提供这样的功能(遍历全部获取)
2. 对于扩展实现IOC依赖注入功能:
举例来说:接口A,实现者A1、A2。接口B,实现者B1、B2。现在实现者A1含有setB()方法,会自动注入一个接口B的实现者,此时注入B1还是B2呢?都不是,而是注入一个动态生成的接口B的实现者B$Adpative,该实现者能够根据参数的不同,自动引用B1或者B2来完成相应的功能。
3. 对扩展采用装饰器模式进行功能增强,类似AOP实现的功能。
还是以上面的例子,接口A的另一个实现者AWrapper1。大体内容如下:
private A a; AWrapper1(A a){
this.a=a;
}
因此,我们在获取某一个接口A的实现者A1的时候,已经自动被AWrapper1包装了。
DUBBO框架中对“扩展点”功能支持在com.alibaba.dubbo.common.extension包中,主要的类为ExtensionLoader。下面的例子为例来分析下:
ExtensionLoader<Protocol> protocolLoader=ExtensionLoader.getExtensionLoader(Protocol.class);
Protocol protocol=protocolLoader.getAdaptiveExtension();
其中Protocol接口定义如下:
@Extension("dubbo")
public interface Protocol{
int getDefaultPort();
@Adaptive
<T> Exporter<T> export(Invoker<T> invoker)throws RpcException;
@Adaptive
<T> Invoker<T> refer(Class<T> type, URL url)throws RpcException;
void destroy();
}
对应的实现者如下:
第一步:根据要加载的接口创建出一个ExtensionLoader实例
ExtensionLoader中含有一个静态属性:
ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();
用于缓存所有的
扩展加载实例,这里加载Protocol.class,就以Protocol.class为key,创建的ExtensionLoader为value存储到上述EXTENSION_LOADERS中
这里 没有进行任何的加载操作。
我们先来看下, ExtensionLoader实例是如何来加载Protocol的实现类的:
1 先解析Protocol上的Extension注解的name,存至String cachedDefaultName属性中,作为默认的实现
2 到类路径下的加载 META-INF/services/com.alibaba.dubbo.rpc.Protocol文件
该文件的内容如下:
com.alibaba.dubbo.registry.support.RegistryProtocol
com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper
com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper
com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol
com.alibaba.dubbo.rpc.protocol.rmi.RmiProtocol
com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol
然后就是读取每一行内容,加载对应的class。
3 对于上述class分成三种情况来处理
对于一个接口的实现者,ExtensionLoader分三种情况来分别存储对应的实现者,属性分别如下:
Class<?> cachedAdaptiveClass; Set<Class<?>> cachedWrapperClasses; Reference<Map<String, Class<?>>> cachedClasses;
情况1: 如果这个class含有
Adaptive注解,则将这个class设置为
Set<Class<?>> cachedAdaptiveClass。
情况2: 尝试获取 带对应接口参数的构造器,如果能够获取到,则说明这个class是一个装饰类即,需要存到 Set<Class<?>> cachedWrapperClasses中
情况3: 如果没有上述构造器。则获取class上的 Extension注解,根据该注解的定义的name作为key,存至 Reference<Map<String, Class<?>>> cachedClasses结构中
dubbo的ExtensionLoader获取扩展的过程
以获取DubboProtocol为例
ExtensionLoader<Protocol> protocolLoader=ExtensionLoader.getExtensionLoader(Protocol.class);
Protocol dubboProtocol=protocolLoader.getExtension(DubboProtocol.NAME);
获取过程如下:
private T createExtension(String name) {
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
T instance = injectExtension((T) clazz.newInstance());
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && wrapperClasses.size() > 0) {
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
type + ") could not be instantiated: " + t.getMessage(), t);
}
}
大致分成4步:
1. 根据name获取对应的class,
首先获取ExtensionLoader<Protocol>对象的Reference<Map<String, Class<?>>> cachedClasses属性,如果为空则表示还没有进行解析,则开始进行上面的解析。解析完成之后,根据name获取对应的 class,这里便获取到 了DubboProtocol.class
2. 根据获取到的class 创建一个实例,
3. 对获取到的实例,进行 依赖注入,
4. 对于上述经过依赖注入的实例,再次进行 包装。
即遍历Set<Class<?>> cachedWrapperClasses中每一个包装类,分别调用带Protocol参数的构造函数创建出实例,然后同样进行依赖注入
以Protocol为例,cachedWrapperClasses中存着上述提到过的ProtocolFilterWrapper、ProtocolListenerWrapper。分别会对DubboProtocol实例进行包装
2. 服务发布过程
2.1 发布过程
Dubbo利用Spring的解析(或自己实现)收集到很多一些配置,然后将这些配置都存至ServiceConfig中,然后调用ServiceConfig的export()方法来进行服务的发布与注册。
服务的发布过程如下图所示。首先ServiceConfig类拿到对外提供服务的实际类ref(如:HelloWorldImpl),然后通过ProxyFactory类的getInvoker方法使用ref生成一个AbstractProxyInvoker实例,到这一步就完成具体服务到Invoker的转化。接下来就是Invoker转换到Exporter的过程。
Dubbo处理服务暴露的关键就在Invoker转换到Exporter的过程(如下图中的红色部分),Dubbo协议的Invoker转为Exporter发生在DubboProtocol类的export方法,它主要是打开socket侦听服务,并接收客户端发来的各种请求,通讯细节由Dubbo自己实现。
先看一个简单的服务端例子,dubbo配置如下:
<dubbo:application name="helloService-app" />
<dubbo:registry protocol="zookeeper" address="127.0.0.1:2181" />
<dubbo:service interface="com.demo.dubbo.service.HelloService" ref="helloService" />
<bean id="helloService" class="com.demo.dubbo.server.serviceimpl.HelloServiceImpl"/>
有一个服务接口,HelloService,以及它对应的实现类HelloServiceImpl,将HelloService标记为dubbo服务,使用HelloServiceImpl对象来提供具体的服务,使用zooKeeper作为注册中心。则该注册过程的伪代码如下所示:
List<URL> registryURLs = loadRegistries();
for (ProtocolConfig protocolConfig : protocols) {
//根据每一个协议配置构建一个URL
URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);
for (URL registryURL : registryURLs) {
String providerURL = url.toFullString();
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(RpcConstants.EXPORT_KEY, providerURL));
Exporter<?> exporter = protocol.export(invoker);
}
}
2.2 Invoker
Invoker: 一个可执行的对象,能够根据方法名称、参数得到相应的执行结果。接口内容简略如下:
public interface Invoker<T> {
Class<T> getInterface();
URL getUrl();
Result invoke(Invocation invocation) throws RpcException;
void destroy();
}
而Invocation则包含了需要执行的方法、参数等信息,接口定义简略如下:
public interface Invocation {
URL getUrl();
String getMethodName();
Class<?>[] getParameterTypes();
Object[] getArguments();
}
目前其实现类只有一个RpcInvocation,仅仅提供了Invocation所需要的参数而已。内容大致如下:
public class RpcInvocation implements Invocation, Serializable {
private String methodName;
private Class<?>[] parameterTypes;
private Object[] arguments;
private transient URL url;
}
Invoker可执行对象的执行过程分成三种类型:
类型1:本地执行类的Invoker
类型2:远程通信执行类的Invoker
类型3:多个类型2的Invoker聚合成的 集群版的Invoker
以HelloService接口方法为例:
1. 本地执行类的Invoker: server端,含有对应的HelloServiceImpl实现,要执行该接口方法,仅仅只需要通过 反射执行HelloServiceImpl对应的方法即可
2. 远程通信执行类的Invoker: client端,要想执行该接口方法,需要需要进行远程通信,发送要执行的参数信息给server端,server端利用上述本地执行的Invoker执行相应的方法,然后将返回的结果发送给client端。这整个过程算是该类Invoker的典型的执行过程
3. 集群版的Invoker:client端,拥有某个服务的多个Invoker,此时client端需要做的就是 将这个多个Invoker聚合成一个集群版的Invoker,client端使用的时候,仅仅通过集群版的Invoker来进行操作。集群版的Invoker会从众多的远程通信类型的Invoker中选择一个来执行(从中加入负载均衡策略),还可以采用一些失败转移策略等。
2.3 ProxyFactory
proxy代理层按照DUBBO官方文档的解释,是用来生成RPC调用的Stub和Skeleton,这样做的目的是让您在DUBBO服务端定义的具体业务实现不需要关心“它将被怎样调用”,也是您定义的服务接口“与RPC框架脱耦”。
对于server端,ProxyFactory主要负责将服务如HelloServiceImpl统一进行包装成一个Invoker,这些Invoker通过反射来执行具体的HelloServiceImpl对象的方法。接口定义如下:
@Extension("javassist")
public interface ProxyFactory {
//针对client端,创建出代理对象
@Adaptive({Constants.PROXY_KEY})
<T> T getProxy(Invoker<T> invoker) throws RpcException;
//针对server端,将服务对象如HelloServiceImpl包装成一个Invoker对象
@Adaptive({Constants.PROXY_KEY})
<T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) throws RpcException;
}
ProxyFactory的接口实现有JdkProxyFactory、JavassistProxyFactory,默认是JavassistProxyFactory,
JdkProxyFactory内容如下:
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
return new AbstractProxyInvoker<T>(proxy, type, url) {
@Override
protected Object doInvoke(T proxy, String methodName,
Class<?>[] parameterTypes,
Object[] arguments) throws Throwable {
Method method = proxy.getClass().getMethod(methodName, parameterTypes);
return method.invoke(proxy, arguments);
}
};
}
可以看到是创建了一个
AbstractProxyInvoker(这类就是本地执行的Invoker),它对Invoker的Result invoke(Invocation invocation)实现如下:
public Result invoke(Invocation invocation) throws RpcException {
try {
return new RpcResult(doInvoke(proxy, invocation.getMethodName(), invocation.getParameterTypes(), invocation.getArguments()));
} catch (InvocationTargetException e) {
return new RpcResult(e.getTargetException());
} catch (Throwable e) {
throw new RpcException("Failed to invoke remote proxy " + invocation + " to " + getUrl() + ", cause: " + e.getMessage(), e);
}
}
2.4 Protocol
从上面得知服务发布的第一个过程就是:使用ProxyFactory将HelloServiceImpl封装成一个本地执行的Invoker。
执行这个服务,即执行这个本地Invoker,即调用这个本地Invoker的invoke(Invocation invocation)方法,方法的执行过程就是通过反射执行了HelloServiceImpl的内容。现在的问题是:这个方法的参数Invocation invocation的来源问题。
针对server端来说,Protocol要解决的问题就是:根据指定协议对外公布这个HelloService服务,当客户端根据协议调用这个服务时,将客户端传递过来的Invocation参数交给上述的Invoker来执行。所以Protocol加入了远程通信协议的这一块,根据客户端的请求来获取参数Invocation invocation。
先来看下Protocol的接口定义:
@Extension("dubbo")
public interface Protocol {
int getDefaultPort();
//针对server端来说,将本地执行类的Invoker通过协议暴漏给外部。这样外部就可以通过协议发送执行参数Invocation,然后交给本地Invoker来执行
@Adaptive
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
//这个是针对客户端的,客户端从注册中心获取服务器端发布的服务信息
//通过服务信息得知服务器端使用的协议,然后客户端仍然使用该协议构造一个Invoker。这个Invoker是远程通信类的Invoker。
//执行时,需要将执行信息通过指定协议发送给服务器端,服务器端接收到参数Invocation,然后交给服务器端的本地Invoker来执行
@Adaptive
<T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
void destroy();
}
我们再来详细看看服务发布的第二步:
Exporter<?> exporter = protocol.export(invoker);
protocol的来历是:
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
最终的Protocol的实现代码:
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)com.alibaba.dubbo.common.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)com.alibaba.dubbo.common.ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.refer(arg0, arg1);
}
export(Invoker invoker)的过程即根据Invoker中url的配置信息来
最终选择Protocol的实现,默认实现是"dubbo"的扩展实现即DubboProtocol,然后再对DubboProtocol进行依赖注入,进行wrap包装。
可以看到在返回DubboProtocol之前,经过了ProtocolFilterWrapper、ProtocolListenerWrapper、RegistryProtocol的包装。
所谓的包装就是如下类似的内容:
package com.alibaba.xxx;
import com.alibaba.dubbo.rpc.Protocol;
public class XxxProtocolWrapper implemenets Protocol {
Protocol impl;
public XxxProtocol(Protocol protocol) { impl = protocol; }
// 接口方法做一个操作后,再调用extension的方法
public Exporter<T> export(final Invoker<T> invoker) {
//... 一些操作
impl .export(invoker);
// ... 一些操作
}
// ...
}
使用装饰器模式,类似AOP的功能。
下面主要讲解 RegistryProtocol和 DubboProtocol,先暂时忽略ProtocolFilterWrapper、ProtocolListenerWrapper
所以上述服务发布的过程
Exporter<?> exporter = protocol.export(invoker);
会先经过RegistryProtocol,它干了哪些事呢?
1. 利用内部的Protocol即DubboProtocol,将服务进行导出,如下
exporter = protocol.export(new InvokerWrapper<T>(invoker, url));
2. 根据注册中心的registryUrl获取注册服务Registry,然后将
serviceUrl注册到注册中心上,供客户端订阅
Registry registry = registryFactory.getRegistry(registryUrl);
registry.register(serviceUrl)
来详细看看上述DubboProtocol的服务导出功能:
1. 首先根据Invoker的url获取ExchangeServer通信对象(负责与客户端的通信模块),以url中的host和port作为key存至Map<String, ExchangeServer> serverMap中。即可以采用全部服务的通信交给这一个ExchangeServer通信对象,也可以某些服务单独使用新的ExchangeServer通信对象。
String key = url.getAddress();
//client 也可以暴露一个只有server可以调用的服务。
boolean isServer = url.getParameter(RpcConstants.IS_SERVER_KEY,true);
if (isServer && ! serverMap.containsKey(key)) {
serverMap.put(key, getServer(url));
}
2. 创建一个
DubboExporter,封装invoker。然后根据url的port、path(接口的名称)、版本号、分组号作为key,将DubboExporter存至Map<String, Exporter<?>> exporterMap中
key = serviceKey(url);
DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
exporterMap.put(key, exporter);
现在我们要搞清楚我们的目的:通过通信对象获取客户端传来的Invocation invocation参数,然后找到对应的
DubboExporter(即能够获取到本地Invoker)就可以执行服务了。
上述每一个ExchangeServer通信对象都绑定了一个 ExchangeHandler requestHandler对象,内容简略如下:
private ExchangeHandler requestHandler = new ExchangeHandlerAdapter() {
public Object reply(ExchangeChannel channel, Object message) throws RemotingException {
if (message instanceof Invocation) {
Invocation inv = (Invocation) message;
Invoker<?> invoker = getInvoker(channel, inv);
RpcContext.getContext().setRemoteAddress(channel.getRemoteAddress());
return invoker.invoke(inv);
}
throw new RemotingException(channel, "Unsupported request: " + message == null ? null : (message.getClass().getName() + ": " + message) + ", channel: consumer: " + channel.getRemoteAddress() + " --> provider: " + channel.getLocalAddress());
}
};
可以看到在获取到Invocation参数后,调用
getInvoker(channel, inv)来获取本地Invoker。获取过程就是根据channel获取port,根据Invocation inv信息获取要调用的服务接口、版本号、分组号等,以此组装成key,从上述Map<String, Exporter<?>> exporterMap中获取Exporter,然后就可以找到对应的Invoker了,就可以顺利的调用服务了。
2.5 Exporter
Exporter负责维护invoker的生命周期。接口定义如下:
public interface Exporter<T> {
Invoker<T> getInvoker();
void unexport();
}
包含了一个Invoker对象。一旦想撤销该服务,就会调用Invoker的
destroy()方法,同时清理上述exporterMap中的数据。对于RegistryProtocol来说就需要向注册中心撤销该服务。
3. 服务调用过程
下图是服务消费的主过程:
首先ReferenceConfig类的init方法调用Protocol的refer方法生成Invoker实例(如上图中的红色部分),这是服务消费的关键。接下来把Invoker通过ProxyFactory代理工厂转换为客户端需要的接口(如:HelloWorld)。
对于一个简单的客户端引用服务的例子,dubbo配置如下:
<dubbo:application name="consumer-of-helloService" />
<dubbo:registry protocol="zookeeper" address="127.0.0.1:2181" />
<dubbo:reference id="helloService" interface="com.demo.dubbo.service.HelloService" />
使用zooKeeper作为注册中心,引用远程的HelloService接口服务
HelloService接口内容如下:
public interface HelloService {
public String hello(String msg);
}
利用Spring的xml配置创建出一系列的配置对象,存至Spring容器中,配置相关的对象不依赖Spring,也就是说你可以手动去创建上述对象。
为了在Spring启动的时候,也相应的启动provider发布服务注册服务的过程:又加入了一个和Spring相关联的 ServiceBean,继承了erviceConfig,为了在Spring启动的时候,也相应的启动consumer发现服务的过程:又加入了一个和Spring相关联的 ReferenceBean,继承了ReferenceConfig,利用Spring就做了上述过程,得到相应的配置数据,然后启动相应的服务。如果想剥离Spring,我们就可以手动来创建上述配置对象,通过 ServiceConfig和 ReferenceConfig的API来启动相应的服务。
服务引用过程
将ReferenceConfig.init()中的内容拆成具体的步骤,如下第一步:收集配置参数
从Spring或者手动创建的配置对象中获得收集到的配置参数,举例如下:
methods=hello,
timestamp=1443695417847,
dubbo=2.5.3
application=consumer-of-helloService
side=consumer
pid=7748
interface=com.demo.dubbo.service.HelloService
第二步:从注册中心获取服务地址,返回Invoker对象
如果是单个注册中心,代码如下:
Protocol refprotocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
invoker = refprotocol.refer(interfaceClass, url);
上述url的内容如下:
registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?
application=consumer-of-helloService&
dubbo=2.5.6&
pid=8292&
registry=zookeeper&
timestamp=1443707173909&
refer=
application=consumer-of-helloService&
dubbo=2.5.6&
interface=com.demo.dubbo.service.HelloService&
methods=hello&
pid=8292&
side=consumer&
timestamp=1443707173884&
第三步:使用ProxyFactory创建出Invoker的代理对象,代码如下:
ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
proxyFactory.getProxy(invoker);
3.1 Invoker
Invoker一个可执行对象。这个可执行对象的执行过程分成三种类型:
类型1:本地执行类的Invoker
类型2:远程通信执行类的Invoker
类型3:多个类型2的Invoker聚合成的集群版的Invoker
对于客户端来说,Invoker则应该是远程通信执行类的Invoker、多个远程通信类型的Invoker聚合成的集群版的Invoker这两种类型。先来说说非集群版的Invoker,即远程通信类型的Invoker。来看下DubboInvoker的具体实现
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);
}
}
大概内容就是:
将通过远程通信将 Invocation信息传递给服务器端,服务器端接收到该Invocation信息后,找到对应的本地 Invoker,然后通过 反射执行相应的方法,将方法的返回值再通过远程通信将结果传递给客户端。
这里分成3种情况:
1. 执行的方法不需要返回值:直接使用 ExchangeClient的 send方法
2. 执行的方法的结果需要异步返回:使用 ExchangeClient的 request方法,返回一个 ResponseFuture,通过 ThreadLocal方式与当前线程绑定,未等服务器端响应结果就直接返回
3. 执行的方法的结果需要同步返回:使用 ExchangeClient的 request方法,返回一个 ResponseFuture,一直 阻塞到服务器端返回响应结果
3.2 Protocol
从上面得知服务引用的第二个过程就是
invoker = refprotocol.refer(interfaceClass, url);
使用协议Protocol根据上述的url和服务接口来引用服务,创建出一个Invoker对象,针对server端来说,会如下使用Protocol:
Exporter<?> exporter = protocol.export(invoker);
Protocol要解决的问题就是:
根据url中指定的协议(没有指定的话使用默认的dubbo协议)
对外公布这个HelloService服务,当客户端根据协议调用这个服务时,将客户端传递过来的Invocation参数交给服务器端的Invoker来执行。所以Protocol加入了远程通信协议的这一块,根据客户端的请求来获取参数Invocation invocation。
而针对客户端,则需要根据服务器开放的协议(服务器端在注册中心注册的url地址中含有该信息)来创建相应的协议的Invoker对象,如:DubboInvoker、InjvmInvoker和ThriftInvoker等。
如服务器端在注册中心中注册的url地址为:
dubbo://192.168.1.104:20880/com.demo.dubbo.service.HelloService?
anyhost=true&
application=helloService-app&dubbo=2.5.3&
interface=com.demo.dubbo.service.HelloService&
methods=hello&
pid=3904&
side=provider&
timestamp=1444003718316
会看到上述服务是以dubbo协议注册的,所以这里产生的Invoker就是DubboInvoker。我们来具体的看下这个过程
先来看下Protocol的接口定义:
@Extension("dubbo")
public interface Protocol {
int getDefaultPort();
//针对server端来说,将本地执行类的Invoker通过协议暴漏给外部。这样外部就可以通过协议发送执行参数Invocation,然后交给本地Invoker来执行
@Adaptive
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
//这个是针对客户端的,客户端从注册中心获取服务器端发布的服务信息
//通过服务信息得知服务器端使用的协议,然后客户端仍然使用该协议构造一个Invoker。这个Invoker是远程通信类的Invoker。
//执行时,需要将执行信息通过指定协议发送给服务器端,服务器端接收到参数Invocation,然后交给服务器端的本地Invoker来执行
@Adaptive
<T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
void destroy();
}
详细看看服务引用的第二步:
invoker = refprotocol.refer(interfaceClass, url);
protocol的来历是:
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
Protocol的实现代码:
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)com.alibaba.dubbo.common.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)com.alibaba.dubbo.common.ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.refer(arg0, arg1);
}
refer(interfaceClass, url)的过程即根据url的配置信息来最终选择的Protocol实现,默认实现是"dubbo"的扩展实现即DubboProtocol,然后再对
DubboProtocol进行依赖注入,进行wrap包装。先来看看Protocol的实现情况:
可以看到在返回DubboProtocol之前,经过了ProtocolFilterWrapper、ProtocolListenerWrapper、RegistryProtocol的包装。所谓的包装就是如下类似的内容:
package com.alibaba.xxx;
import com.alibaba.dubbo.rpc.Protocol;
public class XxxProtocolWrapper implemenets Protocol {
Protocol impl;
public XxxProtocol(Protocol protocol) { impl = protocol; }
// 接口方法做一个操作后,再调用extension的方法
public Exporter<T> export(final Invoker<T> invoker) {
//... 一些操作
impl .export(invoker);
// ... 一些操作
}
// ...
}
使用装饰器模式,类似AOP的功能。所以上述服务引用的过程:
invoker = refprotocol.refer(interfaceClass, urls.get(0));
中的refprotocol会先经过RegistryProtocol(先暂时忽略ProtocolFilterWrapper、ProtocolListenerWrapper),它干了哪些事呢?
1. 根据注册中心的registryUrl获取注册服务Registry,将自身的consumer信息注册到注册中心上
//先根据客户端的注册中心配置找到对应注册服务
Registry registry = registryFactory.getRegistry(url);
//使用注册服务将客户端的信息注册到注册中心上
registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY,
Constants.CHECK_KEY, String.valueOf(false)));
上述subscribeUrl地址如下:
consumer://192.168.1.104/com.demo.dubbo.service.HelloService?
application=consumer-of-helloService&
dubbo=2.5.3&
interface=com.demo.dubbo.service.HelloService&
methods=hello&
pid=6444&
side=consumer&
timestamp=1444606047076
该url表述了自己是
consumer,同时自己的ip地址是192.168.1.104,引用的服务是com.demo.dubbo.service.HelloService,以及注册时间等等
2. 创建一个RegistryDirectory,从注册中心中订阅自己引用的服务,将订阅到的url在RegistryDirectory内部转换成Invoker
RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
directory.setRegistry(registry);
directory.setProtocol(protocol);
directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY,
Constants.PROVIDERS_CATEGORY
+ "," + Constants.CONFIGURATORS_CATEGORY
+ "," + Constants.ROUTERS_CATEGORY));
上述RegistryDirectory是
Directory的实现,Directory代表多个Invoker,可以把它看成List类型的Invoker,但与List不同的是,它的值可能是动态变化的,比如注册中心推送变更。
RegistryDirectory内部含有两者重要属性:
- 1. 注册中心服务Registry registry
- 2. Protocol protocol。
3. 然后使用 Cluster cluster对象将上述多个Invoker对象(此时还没有真正创建出来,异步订阅,订阅成功之后,回调时才会创建出Invoker)聚合成一个 集群版的Invoker对象。
@SPI(FailoverCluster.NAME)
public interface Cluster {
/**
* Merge the directory invokers to a virtual invoker.
*
* @param <T>
* @param directory
* @return cluster invoker
* @throws RpcException
*/
@Adaptive
<T> Invoker<T> join(Directory<T> directory) throws RpcException;
}
Cluster只有一个功能就是把上述Directory(相当于一个List类型的Invoker)聚合成一个
Invoker,同时也可以对List进行
过滤处理(这些过滤操作也是配置在注册中心的)等实现路由的功能,主要是
对用户进行透明。看看接口实现情况:
默认采用的是FailoverCluster,看下FailoverCluster:
/**
* 失败转移,当出现失败,重试其它服务器,通常用于读操作,但重试会带来更长延迟。
*
* <a href="http://en.wikipedia.org/wiki/Failover">Failover</a>
*
* @author william.liangf
*/
public class FailoverCluster implements Cluster {
public final static String NAME = "failover";
public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
return new FailoverClusterInvoker<T>(directory);
}
}
仅仅是创建了一个
FailoverClusterInvoker,具体的逻辑留在调用的时候即调用该Invoker的invoke(final Invocation invocation)方法时来进行处理。其中又会涉及到另一个接口
LoadBalance(从众多的Invoker中挑选出一个Invoker来执行此次调用任务),接口如下:
@SPI(RandomLoadBalance.NAME)
public interface LoadBalance {
/**
* select one invoker in list.
*
* @param invokers invokers.
* @param url refer url
* @param invocation invocation.
* @return selected invoker.
*/
@Adaptive("loadbalance")
<T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;
}
实现情况如下:
3.3 ProxyFactory
对于server端,ProxyFactory主要负责将服务如HelloServiceImpl统一进行包装成一个Invoker,这些Invoker通过反射来执行具体的HelloServiceImpl对象的方法。而对于client端,则是将上述创建的集群版Invoker创建出代理对象。接口定义如下:
@Extension("javassist")
public interface ProxyFactory {
//针对client端,对Invoker对象创建出代理对象
@Adaptive({Constants.PROXY_KEY})
<T> T getProxy(Invoker<T> invoker) throws RpcException;
//针对server端,将服务对象如HelloServiceImpl包装成一个Invoker对象
@Adaptive({Constants.PROXY_KEY})
<T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) throws RpcException;
}
ProxyFactory的接口实现有JdkProxyFactory、JavassistProxyFactory,默认是JavassistProxyFactory, JdkProxyFactory内容如下:
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), interfaces, new InvokerInvocationHandler(invoker));
}
可以看到是利用jdk自带的Proxy来动态代理目标对象Invoker。所以我们调用创建出来的代理对象如HelloService helloService的方法时,会执行
InvokerInvocationHandler中的逻辑:
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);
}
if ("toString".equals(methodName) && parameterTypes.length == 0) {
return invoker.toString();
}
if ("hashCode".equals(methodName) && parameterTypes.length == 0) {
return invoker.hashCode();
}
if ("equals".equals(methodName) && parameterTypes.length == 1) {
return invoker.equals(args[0]);
}
return invoker.invoke(new RpcInvocation(method, args)).recreate();
}
}
可以看到还是交给目标对象Invoker来执行。至此Proxy分析完毕。
4. Dubbo中的通信模型
Dubbo支持Netty和Mina作为底层通信模型,其结构如下图所示:
Dubbo的Transporter层完成通信功能,底层的Netty和Mina委托给统一的ChannelHandler来完成具体的功能
4.1 服务端集成Netty
如NettyServer的启动流程: 按照netty自己的API启动方式,然后依据外界传递进来的com.alibaba.dubbo.remoting.ChannelHandler接口实现,创建出NettyHandler,最终对用户的连接请求的处理全部交给NettyHandler来处理,NettyHandler又交给了外界传递进来的com.alibaba.dubbo.remoting.ChannelHandler接口实现。至此就将所有底层不同的通信实现全部转化到了外界传递进来的com.alibaba.dubbo.remoting.ChannelHandler接口的实现上了。
而上述Server接口的另一个分支实现HeaderExchangeServer则充当一个装饰器的角色,为所有的Server实现增添了如下功能:
向该Server所有的Channel依次进行心跳检测:
- 1. 如果当前时间减去最后的读取时间大于heartbeat时间或者当前时间减去最后的写时间大于heartbeat时间,则向该Channel发送一次心跳检测
- 2. 如果当前时间减去最后的读取时间大于heartbeatTimeout,则服务器端要关闭该Channel,如果是客户端的话则进行重新连接(客户端也会使用这个心跳检测任务)
4.2 客户端集成Netty
服务器端了解了之后,客户端就也非常清楚了,整体类图如下:
如NettyClient在使用netty的API开启客户端之后,仍然使用NettyHandler来处理。还是最终转化成com.alibaba.dubbo.remoting.ChannelHandler接口实现上了。
HeaderExchangeClient和上面的HeaderExchangeServer非常类似,就不再提了。
我们可以看到这样集成完成之后,就完全屏蔽了底层通信细节,将逻辑全部交给了com.alibaba.dubbo.remoting.ChannelHandler接口的实现上了。从上面我们也可以看到,该接口实现也会经过层层装饰类的包装,才会最终交给底层通信。
如HeartbeatHandler装饰类:
public void sent(Channel channel, Object message) throws RemotingException {
setWriteTimestamp(channel);
handler.sent(channel, message);
}
public void received(Channel channel, Object message) throws RemotingException {
setReadTimestamp(channel);
if (isHeartbeatRequest(message)) {
Request req = (Request) message;
if (req.isTwoWay()) {
Response res = new Response(req.getId(), req.getVersion());
res.setEvent(Response.HEARTBEAT_EVENT);
channel.send(res);
if (logger.isInfoEnabled()) {
int heartbeat = channel.getUrl().getParameter(Constants.HEARTBEAT_KEY, 0);
if(logger.isDebugEnabled()) {
logger.debug("Received heartbeat from remote channel " + channel.getRemoteAddress()
+ ", cause: The channel has no data-transmission exceeds a heartbeat period"
+ (heartbeat > 0 ? ": " + heartbeat + "ms" : ""));
}
}
}
return;
}
if (isHeartbeatResponse(message)) {
if (logger.isDebugEnabled()) {
logger.debug(
new StringBuilder(32)
.append("Receive heartbeat response in thread ")
.append(Thread.currentThread().getName())
.toString());
}
return;
}
handler.received(channel, message);
}
就会拦截那些上述提到的心跳检测请求。更新该Channel的最后读写时间。
4.3 同步调用和异步调用的实现
使用netty mina等异步事件驱动的通信框架,将Channel中信息都分发到Handler中去处理了,Handler中的send方法只负责不断的发送消息,receive方法只负责不断接收消息,这时候就产生一个问题:
客户端如何对应同一个Channel的接收的消息和发送的消息之间的匹配呢?
这也很简单,就需要在发送消息的时候,必须要产生一个请求id,将调用的信息连同id一起发给服务器端,服务器端处理完毕后,再将响应信息和上述请求id一起发给客户端,这样的话客户端在接收到响应之后就可以根据id来判断是针对哪次请求的响应结果了。来看下DubboInvoker中的具体实现:
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();
}
- 如果不需要返回值,直接使用send方法,发送出去,设置当期和线程绑定RpcContext的future为null
- 如果需要异步通信,使用request方法构建一个ResponseFuture,然后设置到和线程绑定RpcContext中
- 如果需要同步通信,使用request方法构建一个ResponseFuture,阻塞等待请求完成
String result1 = helloService.hello("World");
System.out.println("result :"+result1);
System.out.println("result : "+RpcContext.getContext().getFuture().get());
当设置成异步请求的时候,result1则为null,然后通过RpcContext来获取相应的值。
然后我们来看下异步请求的整个实现过程,即上述currentClient.request方法的具体内容:
public ResponseFuture request(Object request, int timeout) throws RemotingException {
// create request.
Request req = new Request();
req.setVersion("2.0.0");
req.setTwoWay(true);
req.setData(request);
DefaultFuture future = new DefaultFuture(channel, req, timeout);
try{
channel.send(req);
}catch (RemotingException e) {
future.cancel();
throw e;
}
return future;
}
第一步:创建出一个
request对象,创建过程中就自动产生了
requestId,如下
public class Request {
private final long mId;
private static final AtomicLong INVOKE_ID = new AtomicLong(0);
public Request() {
mId = newId();
}
private static long newId() {
// getAndIncrement()增长到MAX_VALUE时,再增长会变为MIN_VALUE,负数也可以做为ID
return INVOKE_ID.getAndIncrement();
}
}
第二步:根据request请求封装成一个
DefaultFuture对象,通过该对象的get方法就可以获取到请求结果。该方法会阻塞一直到请求结果产生。同时DefaultFuture对象会被存至
DefaultFuture类如下结构中:
private static final Map<Long, DefaultFuture> FUTURES = new ConcurrentHashMap<Long, DefaultFuture>();
key就是请求id
第三步:将上述请求对象发送给服务器端,同时将DefaultFuture对象返给上一层函数,即 DubboInvoker中,然后设置到当前线程中
第四步:用户通过 RpcContext来获取上述 DefaultFuture对象来获取请求结果,会 阻塞至服务器端返产生结果给客户端
第五步:服务器端产生结果,返回给客户端会在客户端的handler的receive方法中接收到,接收到之后判别接收的信息是 Response后,
static void handleResponse(Channel channel, Response response) throws RemotingException {
if (response != null && !response.isHeartbeat()) {
DefaultFuture.received(channel, response);
}
}
当某个线程多次发送异步请求时,都会将返回的DefaultFuture对象设置到当前线程绑定的RpcContext中,就会造成了覆盖问题,如下调用方式:
String result1 = helloService.hello("World");
String result2 = helloService.hello("java");
System.out.println("result :"+result1);
System.out.println("result :"+result2);
System.out.println("result : "+RpcContext.getContext().getFuture().get());
System.out.println("result : "+RpcContext.getContext().getFuture().get());
即异步调用了hello方法,再次异步调用,则前一次的结果就被冲掉了,则就无法获取前一次的结果了。必须要调用一次就立马将DefaultFuture对象获取走,以免被冲掉。即这样写:
String result1 = helloService.hello("World");
Future<String> result1Future=RpcContext.getContext().getFuture();
String result2 = helloService.hello("java");
Future<String> result2Future=RpcContext.getContext().getFuture();
System.out.println("result :"+result1);
System.out.println("result :"+result2);
System.out.println("result : "+result1Future.get());
System.out.println("result : "+result2Future.get());
最后来张dubbo的解释图片:
以上摘抄了Dubbo实现中的服务发布和调用方式,下一篇将关注Dubbo注册中心的实现、消息路由和解码器。