Dubbo特性
- Dubbo缺省协议采用单一长连接和NIO异步通讯,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况
- 作为RPC:支持各种传输协议,如dubbo,hession,json,fastjson,底层采用mina,netty长连接进行传输!典型的provider和cusomer模式!
- 作为SOA:具有服务治理功能,提供服务的注册和发现!用zookeeper实现注册中心!启动时候服务端会把所有接口注册到注册中心,并且订阅configurators,服务消费端订阅provide,configurators,routers,订阅变更时,zk会推送providers,configuators,routers,启动时注册长连接,进行通讯!proveider和provider启动后,后台启动定时器,发送统计数据到monitor(监控中心)!提供各种容错机制和负载均衡策略!
dubbo的框架设计
各层说明
- config 配置层:对外配置接口,以 ServiceConfig, ReferenceConfig 为中心,可以直接初始化配置类,也可以通过 spring 解析配置生成配置类
- proxy 服务代理层:服务接口透明代理,生成服务的客户端 Stub 和服务器端 Skeleton, 以 ServiceProxy 为中心,扩展接口为 ProxyFactory
- registry 注册中心层:封装服务地址的注册与发现,以服务 URL 为中心,扩展接口为 RegistryFactory, Registry, RegistryService
cluster 路由层:封装多个提供者的路由及负载均衡,并桥接注册中心,以 Invoker 为中心,扩展接口为 Cluster, Directory, Router, LoadBalance-
Cluster:容错机制
- FailoverCluster:dubbo默认。调用失败可重试,次数可配置。
- FailfastCluster:调用只执行一次,失败则立即报错。适用于非幂等性操作,由服务消费方控制是否重新发起调用。
- FailsafeCluster:失败安全模式,如果调用失败,则直接忽略失败的调用,而是要记录下失败的调用到日志文件,以便后续审计。
- FailbackCluster:失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
- ForkingCluster:并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。
- BroadcastCluster:广播调用所有提供者,逐个调用,任意一台报错则报错(2.1.0开始支持)。通常用于通知所有提供者更新缓存或日志等本地资源信息。
-
Router:负责从多个Invoker中按路由规则选出子集,比如读写分离,应用隔离等。
-
LoadBalance:负载均衡,选择逻辑在doSelect(List<Invoker> invokers, URL url, Invocation invocation)方法中
- RandomLoadBalance:默认。 可以设置权重,有利于充分利用服务器的资源,高配的可以设置权重大一些,低配的可以稍微小一些
- RoundRobinLoadBalance:轮询策略。
- LeastActiveLoadBalance:根据请求调用的次数计数,处理请求更慢的节点会受到更少的请求
- ConsistentHashLoadBalance:一致性Hash策略,具体配置方法可以参考Dubbo文档。相同调用参数的请求会发送到同一个服务提供方节点上,如果某个节点发生故障无法提供服务,则会基于一致性Hash算法映射到虚拟节点上(其他服务提供方)。
-
- monitor 监控层:RPC 调用次数和调用时间监控,以 Statistics 为中心,扩展接口为 MonitorFactory, Monitor, MonitorService
protocol 远程调用层:封装 RPC 调用,以 Invocation, Result 为中心,扩展接口为 Protocol, Invoker, Exporter- Protocol
- Invoker
- Exporter
- exchange 信息交换层:封装请求响应模式,同步转异步,以 Request, Response 为中心,扩展接口为 Exchanger, ExchangeChannel, ExchangeClient, ExchangeServer
- Exchanger
- ExchangeChannel
- transport 网络传输层:抽象 mina 和 netty 为统一接口,以 Message 为中心,扩展接口为 Channel, Transporter, Client, Server, Codec
- Channel
- Transporter
- serialize 数据序列化层:可复用的一些工具,扩展接口为 Serialization, ObjectInput, ObjectOutput, ThreadPool
初始化过程细节
- 基于dubbo.jar内的Meta-inf/spring.handlers配置,spring在遇到dubbo名称空间时,会回调DubboNamespaceHandler类。
- 所有的dubbo标签,都统一用DubboBeanDefinitionParser进行解析,基于一对一属性映射,将XML标签解析为Bean对象。
- 在ServiceConfig.export 或者ReferenceConfig.get 初始化时,将Bean对象转会为url格式,将所以Bean属性转成url的参数。
- 然后将URL传给Protocol扩展点,基于扩展点的Adaptive机制,根据URL的协议头,进行不同协议的服务暴露和引用。
服务暴露
-
只暴露服务端口:
在没有注册中心,直接暴露提供者的情况下 [1],ServiceConfig 解析出的 URL 的格式为:dubbo://service-host/com.foo.FooService?version=1.0.0。
基于扩展点自适应机制,通过 URL 的 dubbo:// 协议头识别,直接调用 DubboProtocol的 export() 方法,打开服务端口。
-
向注册中心暴露服务:
在有注册中心,需要注册提供者地址的情况下 [2],ServiceConfig 解析出的 URL 的格式为:registry://registry-host/org.apache.dubbo.registry.RegistryService?export=URL.encode("dubbo://service-host/com.foo.FooService?version=1.0.0")
基于扩展点自适应机制,通过 URL 的 registry:// 协议头识别,就会调用 RegistryProtocol 的 export() 方法,将 export 参数中的提供者 URL,先注册到注册中心。
再重新传给 Protocol 扩展点进行暴露: dubbo://service-host/com.foo.FooService?version=1.0.0,然后基于扩展点自适应机制,通过提供者 URL 的 dubbo:// 协议头识别,就会调用 DubboProtocol 的 export() 方法,打开服务端口
引用服务
-
直连引用服务:
在没有注册中心,直连提供者的情况下 [3],ReferenceConfig 解析出的 URL 的格式为:dubbo://service-host/com.foo.FooService?version=1.0.0
基于扩展点自适应机制,通过 URL 的 dubbo:// 协议头识别,直接调用 DubboProtocol 的 refer() 方法,返回提供者引用。
-
从注册中心发现引用服务:
在有注册中心,通过注册中心发现提供者地址的情况下 [4],ReferenceConfig 解析出的 URL 的格式为:registry://registry-host/org.apache.dubbo.registry.RegistryService?refer=URL.encode("consumer://consumer-host/com.foo.FooService?version=1.0.0")
基于扩展点自适应机制,通过 URL 的 registry:// 协议头识别,就会调用 RegistryProtocol 的 refer() 方法,基于 refer 参数中的条件,查询提供者 URL,如:
dubbo://service-host/com.foo.FooService?version=1.0.0
基于扩展点自适应机制,通过提供者 URL 的 dubbo:// 协议头识别,就会调用 DubboProtocol 的 refer() 方法,得到提供者引用。
然后 RegistryProtocol 将多个提供者引用,通过 Cluster 扩展点,伪装成单个提供者引用返回。
拦截服务
- 基于扩展点自适应机制,所有的 Protocol 扩展点都会自动套上 Wrapper 类。
- 基于ProtocolFilterWrapper 类,将所有 Filter 组装成链,在链的最后一节调用真实的引用。
- 基于ProtocolListenerWrapper 类,将所有 InvokerListener 和 ExporterListener组装集合,在暴露和引用前后,进行回调。 包括监控在内,所有附加功能,全部通过 Filter 拦截实现。
服务提供者暴露一个服务的详细过程
- 首先 ServiceConfig 类拿到对外提供服务的实际类 ref(如:HelloWorldImpl),然后通过 ProxyFactory 类的 getInvoker 方法使用 ref 生成一个 AbstractProxyInvoker 实例,到这一步就完成具体服务到 Invoker 的转化。接下来就是 Invoker 转换到 Exporter 的过程。
- Dubbo 处理服务暴露的关键就在 Invoker 转换到 Exporter 的过程,上图中的红色部分。下面我们以 Dubbo 和 RMI 这两种典型协议的实现来进行说明:
- Dubbo 的实现
Dubbo 协议的 Invoker 转为 Exporter 发生在 DubboProtocol 类的 export 方法,它主要是打开 socket 侦听服务,并接收客户端发来的各种请求,通讯细节由 Dubbo 自己实现。创建一个DubboExporter,绑定invoker的信息放入AbstractProtocol的exporterMap中。
- Dubbo 的实现
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
URL url = invoker.getUrl();
// export service.
String key = serviceKey(url);
DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
exporterMap.put(key, exporter);
//export an stub service for dispatching event
Boolean isStubSupportEvent = url.getParameter(STUB_EVENT_KEY, DEFAULT_STUB_EVENT);
Boolean isCallbackservice = url.getParameter(IS_CALLBACK_SERVICE, false);
if (isStubSupportEvent && !isCallbackservice) {
String stubServiceMethods = url.getParameter(STUB_EVENT_METHODS_KEY);
if (stubServiceMethods == null || stubServiceMethods.length() == 0) {
if (logger.isWarnEnabled()) {
logger.warn(new IllegalStateException("consumer [" + url.getParameter(INTERFACE_KEY) +
"], has set stubproxy support event ,but no stub methods founded."));
}
}
}
openServer(url);
optimizeSerialization(url);
return exporter;
}
- RMI 的实现
- RMI 协议的 Invoker 转为 Exporter 发生在 AbstractProxyProtocol类的
export方法中调用RmiProtocol的doExport方法生成一个Runnable对象,封装成Exporter。uri是key放入AbstractProtocol的exporterMap中。 - 它通过 Spring 或 Dubbo 或 JDK 来实现 RMI 服务,通讯细节这一块由 JDK 底层来实现,这就省了不少工作量。
- Http的实现 同RMI,不同的是由dubbo的HttpBinder生成的server
Http 协议的 Invoker 转为 Runnable对象发生在 HttpProtocol类的 doExport方法,同时创建一个RemotingServer,绑定url等信息放入AbstractProtocol的serverMap中。
@SPI("jetty")
public interface HttpBinder {
@Adaptive({Constants.SERVER_KEY})
HttpServer bind(URL url, HttpHandler handler);
}
服务消费者消费一个服务的完整过程
首先 ReferenceConfig 类的 init 方法调用 Protocol 的 refer 方法生成 Invoker 实例(如上图中的红色部分),这是服务消费的关键。接下来把 Invoker 转换为客户端需要的接口(如:HelloWorld)。
关于每种协议如 RMI/Dubbo/Web service 等它们在调用 refer 方法生成 Invoker 实例的细节和上一章节所描述的类似。
Dubbo服务集群容错
这里的Invoker是Provider的一个可调用Service的抽象,Invoker封装了Provider地址及Service接口信息。
- Directory代表多个Invoker,可以把它看成List,但与List不同的是,它的值可能是动态变化的,比如注册中心推送变更。
- Cluster将Directory中的多个Invoker伪装成一个Invoker,对上层透明,伪装过程包含了容错逻辑,调用失败后,重试另一个。
- Router负责从多个Invoker中按路由规则选出子集,比如读写分离,应用隔离等。
- LoadBalance负责从多个Invoker中选出具体的一个用于本次调用,选的过程包含了负载均衡算法,调用失败后,需要重选。