Dubbo
Dubbo
RPC框架简介
RPC(Remote Procedure Call Protocol)远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。简言之,RPC使得程序能够像访问本地系统资源一样,去访问远端系统资源。比较关键的一些方面包括:通讯协议、序列化、资源(接口)描述、服务框架、性能、语言支持等。简单的说,RPC就是从一台机器(客户端)上通过参数传递的方式调用另一台机器(服务器)上的一个函数或方法(可以统称为服务)并得到返回的结果。HTTP接口是在接口不多、系统与系统交互较少的情况下,解决信息孤岛初期常使用的一种通信手段;优点就是简单、直接、开发方便。利用现成的HTTP协议进行传输。但是如果是一个大型的网站,内部子系统较多、接口非常多的情况下,RPC框架的好处就显示出来了,首先就是长链接,不必每次通信都要像HTTP一样去3次握手什么的,减少了网络开销;其次就是RPC框架一般都有注册中心,有丰富的监控管理;发布、下线接口、动态扩展等,对调用方来说是无感知、统一化的操作。第三个来说就是安全性。最后就是最近流行的服务化架构、服务化治理,RPC框架是一个强力的支撑。Socket只是一个简单的网络通信方式,只是创建通信双方的通信通道,而要实现RPC的功能,还需要对其进行封装,以实现更多的功能。RPC一般配合Netty框架、Spring自定义注解来编写轻量级框架,其实Netty内部是封装了Socket的,较新的Jdk的IO一般是NIO,即非阻塞IO,在高并发网站中,RPC的优势会很明显。
Dubbo框架简介
Dubbo是一款高性能、轻量级的开源RPC框架,提供服务自动注册、自动发现等高效服务治理方案, 可以和Spring框架无缝集成。随着服务化的进一步发展,服务越来越多,服务之间的调用和依赖关系也越来越复杂,诞生了面向服务的架构体系(SOA),也因此衍生出了一系列相应的技术,如对服务提供、服务调用、连接处理、通信协议、序列化方式、服务发现、服务路由、日志输出等行为进行封装的服务框架。就这样为分布式系统的服务治理框架就出现了,Dubbo也就这样产生了。
Dubbo与微服务
Dubbo 是 SOA 时代的产物,它的关注点主要在于服务的调用,流量分发、流量监控和熔断。而 Spring Cloud 诞生于微服务架构时代,考虑的是微服务治理的方方面面,另外由于依托了 Spring、Spring Boot 的优势之上,两个框架在开始目标就不一致,Dubbo 定位服务治理、Spring Cloud 是打造一个生态。Dubbo 底层是使用 Netty 这样的 NIO 框架,是基于 TCP 协议传输的,配合以 Hession 序列化完成 RPC 通信。Spring Cloud 是基于 Http 协议 Rest 接口调用远程过程的通信,相对来说 Http 请求会有更大的报文,占的带宽也会更多。但是 REST 相比 RPC 更为灵活,服务提供方和调用方的依赖只依靠一纸契约,不存在代码级别的强依赖,这在强调快速演化的微服务环境下,显得更为合适,至于注重通信速度还是方便灵活性,具体情况具体考虑。
Dubbo与Feign
Dubbo与Feign都依赖注册中心、负载均衡。
协议区别(TCP和HTTP的区别):默认的Dubbo协议,利用Netty,TCP传输,单一、异步、长连接,适合数据量小、高并发和服务提供者远远少于消费者的场景。Feign基于Http传输协议,短连接,不适合高并发的访问。
负载均衡区别:Dubbo支持4种算法(随机、轮询、活跃度、Hash一致性),而且算法里面引入权重的概念。配置的形式不仅支持代码配置,还支持Dubbo控制台灵活动态配置。负载均衡的算法可以精准到某个服务接口的某个方法。Feign只支持N种策略(轮询、随机、ResponseTime加权)。负载均衡算法是Client级别的。
远程调用区别(了解重试与降级):Dubbo支持多种容错策略,failover、failfast、brodecast、forking等,也引入了retry次数、timeout等配置参数。Feign利用熔断机制来实现容错的,处理的方式不一样。
Dubbo使用场景
透明化的远程方法调用:就像调用本地方法一样调用远程方法,只需简单配置,没有任何API侵入。
软负载均衡及容错机制:可在内网替代F5等硬件负载均衡器,降低成本,减少单点。
服务自动注册与发现:不再需要写死服务提供方地址,注册中心基于接口名查询服务提供者的IP地址,并且能够平滑添加或删除服务提供者。
Dubbo核心功能
Remoting:网络通信框架,提供对多种NIO框架抽象封装,包括“同步转异步”和“请求-响应”模式的信息交换方式。
Cluster:服务框架,提供基于接口方法的透明远程过程调用,包括多协议支持,以及软负载均衡,失败容错,地址路由,动态配置等集群支持。
Registry:服务注册,基于注册中心目录服务,使服务消费方能动态的查找服务提供方,使地址透明,使服务提供方可以平滑增加或减少机器。
Dubbo注册与发现流程
Provider:暴露服务的服务提供方、Consumer:调用远程服务消费方、Registry:服务注册与发现注册中心、Monitor:监控中心和访问调用统计、Container:服务运行容器
1)服务容器Container负责启动,加载,运行服务提供者。
2)服务提供者Provider在启动时,向注册中心注册自己提供的服务。
3)服务消费者Consumer在启动时,向注册中心订阅自己所需的服务。
4)注册中心Registry返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
5)服务消费者Consumer,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
6)服务消费者Consumer和提供者Provider,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心Monitor。
Dubbo Monitor实现原理
Consumer端在发起调用之前会先走filter链;provider端在接收到请求时也是先走filter链,然后才进行真正的业务逻辑处理。默认情况下,在consumer和provider的filter链中都会有Monitorfilter。MonitorFilter向DubboMonitor发送数据。DubboMonitor将数据进行聚合后(默认聚合 1min 中的统计数据)暂存到ConcurrentMap<Statistics, AtomicReference> statisticsMap,然后使用一个含有 3 个线程(线程名字:DubboMonitorSendTimer)的线程池每隔 1min 钟,调用 SimpleMonitorService 遍历发送 statisticsMap 中的统计数据,每发送完毕一个,就重置当前的 Statistics 的 AtomicReference。SimpleMonitorService 将这些聚合数据塞入 BlockingQueue queue 中(队列大小为 100000)。SimpleMonitorService 使用一个后台线程(线程名为:DubboMonitorAsyncWriteLogThread)将 queue 中的数据写入文件(该线程以死循环的形式来写)。SimpleMonitorService 还会使用一个含有 1 个线程(线程名字:DubboMonitorTimer)的线程池每隔 5min 钟,将文件中的统计数据画成图表。
Dubbo负载均衡策略
Dubbo集群默认策略为Random随机调用。
Random LoadBalance: 随机选取提供者策略,有利于动态调整提供者权重。截面碰撞率高,调用次数越多,分布越均匀。
RoundRobin LoadBalance: 轮循选取提供者策略,平均分布,但是存在请求累积的问题。
LeastActive LoadBalance: 最少活跃调用策略,解决慢提供者接收更少的请求。
ConstantHash LoadBalance: 一致性 Hash 策略,使相同参数请求总是发到同一提供者,一台机器宕机,可以基于虚拟节点,分摊至其他提供者,避免引起提供者的剧烈变动。
Dubbo容错方案
Dubbo集群默认的容错方案是Failover Cluster。
Failover Cluster:失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。
Failfast Cluster:快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。
Failsafe Cluster:失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
Failback Cluster:失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
Forking Cluster:并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks=”2″ 来设置最大并行数。
Broadcast Cluster:广播调用所有提供者,逐个调用,任意一台报错则报错 。通常用于通知所有提供者更新缓存或日志等本地资源信息。
Dubbo常见配置
Spring 容器在启动的时候,会读取到 Spring 默认的一些 schema 以及 Dubbo 自定义的 schema,每个 schema 都会对应一个自己的 NamespaceHandler,NamespaceHandler 里面通过 BeanDefinitionParser 来解析配置信息并转化为需要加载的 bean 对象。如果是SpringBoot项目就只需要注解或者配置在Application配置文件里面。
XML配置标签 | 用途 | 解释 |
---|---|---|
dubbo:service/ | 服务配置 | 用于暴露一个服务,定义服务的元信息,一个服务可以用多个协议暴露,一个服务也可以注册到多个注册中心 |
dubbo:reference/ | 引用配置 | 用于创建一个远程服务代理,一个引用可以指向多个注册中心 |
dubbo:protocol/ | 协议配置 | 用于配置提供服务的协议信息,协议由提供方指定,消费方被动接受 |
dubbo:application/ | 应用配置 | 用于配置当前应用信息,不管该应用是提供者还是消费者 |
dubbo:module/ | 模块配置 | 用于配置当前模块信息,可选 |
dubbo:registry/ | 注册中心配置 | 用于配置连接注册中心相关信息 |
dubbo:monitor/ | 监控中心配置 | 用于配置连接监控中心相关信息,可选 |
dubbo:provider/ | 提供方配置 | 当 ProtocolConfig 和 ServiceConfig 某属性没有配置时,采用此缺省值,可选 |
dubbo:consumer/ | 消费方配置 | 当 ReferenceConfig 某属性没有配置时,采用此缺省值,可选 |
dubbo:method/ | 方法配置 | 用于 ServiceConfig 和 ReferenceConfig 指定方法级的配置信息 |
dubbo:argument | 参数配置 | 用于指定方法参数配置 |
Dubbo超时重试
Dubbo在调用服务不成功时,默认是会重试两次。Dubbo可以在2处设置超时时间,并且有3种实现方式:
服务提供者端设置超时时间,在Dubbo的用户文档中,推荐如果能在服务端多配置就尽量多配置,因为服务提供者比消费者更清楚自己提供的服务特性。服务消费者端设置超时时间,如果在消费者端设置了超时时间,以消费者端为主,即优先级更高。因为服务调用方设置超时时间控制性更灵活。如果消费方超时,服务端线程不会定制,会产生警告。
3种方式:设置在指定的接口具体方法上、设置在接口类上(所有方法生效)、全局设置
优先级:消费者指定方法 > 生产者指定方法 > 消费者指定服务 > 生产者指定服务 > 消费者全局配置 > 生产者全局配置
Dubbo调用超时后:客户端会收到一个TimeoutException异常,服务端会收到一个警告The timeout response finally returned at xxx异常。但是实际上会存在调用超时后,服务端程序还是会继续执行的问题。Provider的处理就不像客户端那样简单,因为Provider不会收到异常,而且线程也不会中断,这样就会导致Consumer超时数据回滚,而Provider继续执行最终执行完数据插入成功,导致数据不一致。解决方案可以参考:
1)Dubbo自身有重试机制,调用超时后会发起重试,Provider端需考虑幂等性。
2)使用补偿事务或异步MQ保持最终一致性,需要写一些与业务无关的代码来保持数据最终一致性。
3)基于时间回滚,将方法执行时间和超时时间在Provider端进行比较,如果执行时间大于超时时间,Provider则进行回滚。
Dubbo通信协议
Dubbo默认使用Netty作为通讯框架,Dubbo支持的协议有:
Dubbo:单一长连接和NIO异步通讯,适合大并发小数据量的服务调用,以及消费者远大于提供者。传输协议TCP,异步Hessian序列化。Dubbo推荐使用dubbo协议。
RMI:采用JDK标准的RMI协议实现,传输参数和返回参数对象需要实现Serializable接口,使用Java标准序列化机制,使用阻塞式短连接,传输数据包大小混合,消费者和提供者个数差不多,可传文件,传输协议TCP。 多个短连接TCP协议传输,同步传输,适用常规的远程服务调用和RMI交互操作。在依赖低版本的Common-Collections包,Java序列化存在安全漏洞。
WebService:基于WebService的远程调用协议,集成CXF实现,提供和原生WebService的交互操作。多个短连接,基于HTTP传输,同步传输,适用系统集成和跨语言调用。
HTTP:基于Http表单提交的远程调用协议,使用Spring的HttpInvoke实现。多个短连接,传输协议HTTP,传入参数大小混合,提供者个数多于消费者,需要给应用程序和浏览器JS调用。
Hessian:集成Hessian服务,基于HTTP通讯,采用Servlet暴露服务,Dubbo内嵌Jetty作为服务器时默认实现,提供与Hession服务互操作。多个短连接,同步HTTP传输,Hessian序列化,传入参数较大,提供者大于消费者,提供者压力较大,可传文件。
Memcache:基于Memcache实现的RPC协议。
Redis:基于Redis实现的RPC协议。
Dubbo SPI机制
Java SPI/JDK SPI机制:SPI全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制。SPI是一种动态替换发现的机制, 比如有个接口,想运行时动态的给它添加实现,你只需要添加一个实现。我们经常遇到的就是java.sql.Driver接口,其他不同厂商可以针对同一接口做出不同的实现,mysql和postgresql都有不同的实现提供给用户,而Java的SPI机制可以为某个接口寻找服务实现。DriverManager是jdbc里管理和注册不同数据库driver的工具类。针对一个数据库,可能会存在着不同的数据库驱动实现。我们在使用特定的驱动实现时,不希望修改现有的代码,而希望通过一个简单的配置就可以达到效果。SPI机制在实际开发中使用得场景也有很多。特别是统一标准的不同厂商实现,当有关组织或者公司定义标准之后,具体厂商或者框架开发者实现,之后提供给开发者使用。(驱动的实现类由不同的厂商实现,用户通过配置自动进行实现类切换)。但是JDK标准的SPI会一次性加载所有的扩展实现,如果有的扩展很耗时,但也没用上,很浪费资源。所以只希望加载某个的实现,就不现实了。
Dubbo SPI机制:对Dubbo进行扩展,不需要改动Dubbo的源码。延迟加载,可以一次只加载自己想要加载的扩展实现。增加了对扩展点IOC和AOP的支持,一个扩展点可以直接setter注入其它扩展点。Dubbo的扩展机制能很好的支持第三方IOC容器,默认支持Spring Bean。