Dubbo是阿里巴巴开源的基于 Java 的高性能 RPC 分布式服务框架,现已成为 Apache 基金会孵化项目。
其核心部分包含:
集群容错:提供基于接口方法的透明远程过程调用,包括多协议支持,以及软负载均衡,失败容错,地址路由,动态配置等集群支持。
远程通讯:提供对多种基于长连接的NIO框架抽象封装,包括多种线程模型,序列化,以及“请求-响应”模式的信息交换方式。
自动发现:基于注册中心目录服务,使服务消费方能动态的查找服务提供方,使地址透明,使服务提供方可以平滑增加或减少机器。
最大的区别:
Dubbo底层是使用Netty这样的NIO框架,是基于TCP协议传输的,配合以Hession序列化完成RPC通信;
而SpringCloud是基于Http协议+rest接口调用远程过程的通信,相对来说,Http请求会有更大的报文,占的带宽也会更多。但是REST相比RPC更为灵活,服务提供方和调用方的依赖只依靠一纸契约,不存在代码级别的强依赖,这在强调快速演化的微服务环境下,显得更为合适,至于注重通信速度还是方便灵活性,具体情况具体考虑。
模块区别:
Dubbo主要分为服务注册中心,服务提供者,服务消费者,还有管控中心;
相比起Dubbo简单的四个模块,SpringCloud则是一个完整的分布式一站式框架,他有着一样的服务注册中心,服务提供者,服务消费者,管控台,断路器,分布式配置服务,消息总线,以及服务追踪等;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eX13tBdm-1692792388982)(file://C:\Users\gr\AppData\Roaming\marktext\images\2023-08-23-19-51-41-image.png?msec=1692791501031)]
Provider:暴露服务的服务提供方
Consumer:调用远程服务消费方
Registry:服务注册与发现注册中心
Monitor:监控中心和访问调用统计
Container:服务运行容器
Dubbo协议:Dubbo默认使用Dubbo协议。
适合大并发小数据量的服务调用,以及服务消费者远大于提供者的情况
Hessian二进制序列化。
缺点是不适合传送大数据包的服务。
rmi协议:采用JDK标准的rmi协议实现,传输参数和返回参数对象需要实现Serializable接口。使用java标准序列化机制,使用阻塞式短连接,传输数据包不限,消费者和提供者个数相当。
多个短连接,TCP协议传输,同步传输,适用常规的远程服务调用和rmi互操作
缺点:在依赖低版本的Common-Collections包,java反序列化存在安全漏洞,需升级commonscollections3 到3.2.2版本或commons-collections4到4.1版本。
webservice协议:基于WebService的远程调用协议(Apache CXF的frontend-simple和transportshttp)实现,提供和原生WebService的互操作多个短连接,基于HTTP传输,同步传输,适用系统集成和跨语言调用。
http协议:基于Http表单提交的远程调用协议,使用Spring的HttpInvoke实现。对传输数据包不限,传入参数大小混合,提供者个数多于消费者
hessian:集成Hessian服务,基于底层Http通讯,采用Servlet暴露服务,Dubbo内嵌Jetty作为服务器实现,可与Hession服务互操作通讯效率高于WebService和Java自带的序列化
适用于传输大数据包(可传文件),提供者比消费者个数多,提供者压力较大
缺点是参数及返回值需实现Serializable接口,自定义实现List、Map、Number、Date、Calendar等接口
thrift协议:对thrift原生协议的扩展添加了额外的头信息。使用较少,不支持传null值
memcache:基于memcached实现的RPC协议
redis:基于redis实现的RPC协议
服务容器Container负责启动,加载,运行服务提供者。
服务提供者Provider在启动时,向注册中心注册自己提供的服务。
服务消费者Consumer在启动时,向注册中心订阅自己所需的服务。
注册中心Registry返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
服务消费者Consumer,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
服务消费者Consumer和提供者Provider,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心Monitor。
三种服务容器:
Spring Container
Jetty Container
Log4j Container
Dubbo的服务容器只是一个简单的 Main 方法,并加载一个简单的 Spring 容器,用于暴露服务。
将负载均衡功能实现在rpc客户端侧,以便能够随时适应外部的环境变化,更好地发挥硬件作用。而且客户端的负载均衡天然地就避免了单点问题。定制化的自有定制化的优势和劣势。
它可以从配置文件中指定,也可以在管理后台进行配置修改。
事实上,它支持 服务端服务/方法级别、客户端服务/方法级别 的负载均衡配置。
Dubbo提供了4种负载均衡实现:
RandomLoadBalance:随机负载均衡。随机的选择一个。是Dubbo的默认负载均衡策略。
RoundRobinLoadBalance:轮询负载均衡。轮询选择一个。
LeastActiveLoadBalance:最少活跃调用数,相同活跃数的随机。活跃数指调用前后计数差。使慢的 Provider 收到更少请求,因为越慢的 Provider 的调用前后计数差会越大。
ConsistentHashLoadBalance:一致性哈希负载均衡。相同参数的请求总是落在同一台机器上。
默认是同步等待结果阻塞的,支持异步调用。
Dubbo是基于 NIO 的非阻塞实现并行调用,客户端不需要启动多线程即可完成并行调用多个远程服务,相对多线程开销较小,异步调用会返回一个 Future 对象。
Consumer 端在发起调用之前会先走 filter 链;provider 端在接收到请求时也是先走 filter 链,然后才进行真正的业务逻辑处理。默认情况下,在 consumer 和 provider 的 filter 链中都会有 Monitorfilter。
MonitorFilter 向 DubboMonitor 发送数据
DubboMonitor 将数据进行聚合后(默认聚合 1min 中的统计数据)暂存到ConcurrentMap
,然后使用一个含有 3 个线程(线程名字:DubboMonitorSendTimer
)的线程池每隔 1min 钟,调用 SimpleMonitorService
遍历发送statisticsMap
中的统计数据,每发送完毕一个,就重置当前的 Statistics 的AtomicReference
SimpleMonitorService
将这些聚合数据塞入 BlockingQueue queue
中(队列大写为 100000)
SimpleMonitorService
使用一个后台线程(线程名为:DubboMonitorAsyncWriteLogThread)
将 queue 中的数据写入文件(该线程以死循环的形式来写)
SimpleMonitorService
还会使用一个含有 1 个线程(线程名字:DubboMonitorTimer
)的线程池每隔 5min 钟,将文件中的统计数据画成图表
Multicast 注册中心:Multicast 注册中心不需要任何中心节点,只要广播地址,就能进行服务注册和发现,基于网络中组播传输实现。
Zookeeper 注册中心:基于分布式协调系统 Zookeeper 实现,采用 Zookeeper 的 watch 机制实现数据变更。
Redis 注册中心:基于 Redis 实现,采用 key/map 存储,key 存储服务名和类型,map 中 key 存储服务 url,value 服务过期时间。基于 Redis 的发布/订阅模式通知数据变更。
Simple 注册中心。
推荐使用 Zookeeper 作为注册中心
Failover Cluster:失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。
Failfast Cluster:快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。
Failsafe Cluster:失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
Failback Cluster:失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
Forking Cluster:并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks=”2″ 来设置最大并行数。
Broadcast Cluster:广播调用所有提供者,逐个调用,任意一台报错则报错 。通常用于通知所有提供者更新缓存或日志等本地资源信息。
Dubbo超时设置有两种方式:
服务提供者端设置超时时间,在Dubbo的用户文档中,推荐如果能在服务端多配置就尽量多配置,因为服务提供者比消费者更清楚自己提供的服务特性。
服务消费者端设置超时时间,如果在消费者端设置了超时时间,以消费者端为主,即优先级更高。因为服务调用方设置超时时间控制性更灵活。如果消费方超时,服务端线程不会定制,会产生警告。
(1) 工厂模式
Provider 在 export 服务时,会调用ServiceConfig
的 export
方法。ServiceConfig
中有个字段:
private static final Protocol protocol =
ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtensi
on();
Dubbo里有很多这种代码。这也是一种工厂模式,只是实现类的获取采用了 JDKSPI 的机制。这么实现的优点是可扩展性强,想要扩展实现,只需要在 classpath下增加个文件就可以了,代码零侵入。另外,像上面的 Adaptive 实现,可以做到调用时动态决定调用哪个实现,但是由于这种实现采用了动态代理,会造成代码调试比较麻烦,需要分析出实际调用的实现类。
(2) 装饰器模式
Dubbo在启动和调用阶段都大量使用了装饰器模式。以 Provider 提供的调用链为例,具体的调用链代码是在 ProtocolFilterWrapper
的 buildInvokerChain
完成的,具体是将注解中含有 group=provider
的 Filter 实现,按照 order 排序,最后的调用顺序是:
EchoFilter -> ClassLoaderFilter -> GenericFilter -> ContextFilter ->
ExecuteLimitFilter -> TraceFilter -> TimeoutFilter -> MonitorFilter ->
ExceptionFilter
更确切地说,这里是装饰器和责任链模式的混合使用。例如,EchoFilter 的作用是判断是否是回声测试请求,是的话直接返回内容,这是一种责任链的体现。而像ClassLoaderFilter 则只是在主功能上添加了功能,更改当前线程的 ClassLoader,这是典型的装饰器模式。
(3) 观察者模式
Dubbo的 Provider 启动时,需要与注册中心交互,先注册自己的服务,再订阅自己的服务,订阅时,采用了观察者模式,开启一个 listener。注册中心会每 5 秒定时检查是否有服务更新,如果有更新,向该服务的提供者发送一个 notify 消息,provider 接受到 notify 消息后,运行 NotifyListener 的 notify方法,执行监听器方法。
(4) 动态代理模式
Dubbo扩展 JDK SPI 的类 ExtensionLoader
的 Adaptive
实现是典型的动态代理实现。Dubbo需要灵活地控制实现类,即在调用阶段动态地根据参数决定调用哪个实现类,所以采用先生成代理类的方法,能够做到灵活的调用。生成代理类的代码是 ExtensionLoader
的createAdaptiveExtensionClassCode
方法。代理类主要逻辑是,获取 URL 参数中指定参数的值作为获取实现类的 key。