什么是RPC
RPC(Remote Procedure Call)是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。简言之,RPC使得程序能够像访问本地系统资源一样,去访问远端系统资源。-
RPC框架需要解决的问题
- 如何确定客户端和服务端之间的通信协议?
- 如何更高效地进行网络通信?
- 服务端提供的服务如何暴露给客户端?
- 客户端如何发现这些暴露的服务?
- 如何更高效地对请求对象和响应结果进行序列化和反序列化操作?
-
RPC的实现基础
1、需要有非常高效的网络通信,比如一般选择Netty作为网络通信框架;
2、需要有比较高效的序列化框架,比如谷歌的Protobuf序列化框架;
3、可靠的寻址方式(主要是提供服务的发现),比如可以使用Zookeeper来注册服务等等;
4、如果是带会话(状态)的RPC调用,还需要有会话和状态保持的功能;
-
RPC使用了哪些关键技术
-
动态代理
生成Client Stub(客户端存根)和Server Stub(服务端存根)的时候需要用到Java动态代理技术,可以使用JDK提供的原生的动态代理机制,也可以使用开源的:CGLib代理,Javassist字节码生成技术。
-
序列化和反序列化
在网络中,所有的数据都将会被转化为字节进行传送,所以为了能够使参数对象在网络中进行传输,需要对这些参数进行序列化和反序列化操作。
- 序列化:把对象转换为字节序列的过程称为对象的序列化,也就是编码的过程。
- 反序列化:把字节序列恢复为对象的过程称为对象的反序列化,也就是解码的过程。
-
出于并发性能的考虑,传统的阻塞式 IO 显然不太合适。可以选择Netty或者MINA来解决NIO数据传输的问题。
-
-
Dubbo核心组件
组件 说明 Provider 服务提供者 Consumer 服务消费者 Registry 服务注册与发现的注册中心 Monitor 统计服务的调用次数和调用时间的监控中心 Container 服务运行容器 - 服务容器负责启动,加载,运行服务提供者。
- 服务提供者在启动时,向注册中心注册自己提供的服务。
- 服务消费者在启动时,向注册中心订阅自己所需的服务。
- 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
- 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
- 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
Dubbo 架构具有以下几个特点,分别是连通性、健壮性、伸缩性、以及向未来架构的升级性。
-
为什么要Dubbo(为什么需要RPC)
为什么微服务化?横向扩展、并行开发、服务治理、服务编排、负载均衡
-
默认是什么通信框架,还有别的选择吗?默认使用的序列化框架,你还知道哪些
Dubbo 默认使用 Netty 框架,也是推荐的选择,另外内容还集成有Mina、Grizzly
协议 序列化协议 场景 dubbo://
(默认)hessian 单一长连接(建立连接后,持续发送使用)和NIO异步通信,适合于数据量小但高并发的服务调用,不适合大数据量的服务,如文件传输 rmi://
JDK 标准序列化 传入传出参数数据包大小混合,消费者与提供者个数差不多,可传文件;常规远程服务方法调用,与原生RMI服务互操作 hessian://
hessian 传入传出参数数据包较大,提供者比消费者个数多,提供者压力较大,可传文件;页面传输,文件传输,或与原生hessian服务互操作 http://
表单序列化 传入传出参数数据包大小混合,提供者比消费者个数多,可用浏览器查看 webservice://
SOAP 文本序列化 系统集成,跨语言调用 -
服务调用是阻塞的吗?
Dubbo 缺省协议采用单一长连接,底层实现是 Netty 的 NIO 异步通讯机制;基于这种机制,Dubbo 实现了以下几种调用方式:
-
同步调用:一种阻塞式的调用方式,即 Consumer 端代码一直阻塞等待,直到 Provider 端返回为止;
其实,Dubbo 的底层 IO 操作都是异步的。Consumer 端发起调用后,得到一个 Future 对象。对于同步调用,业务线程通过
Future#get(timeout)
,阻塞等待 Provider 端将结果返回;timeout
则是 Consumer 端定义的超时时间。当结果返回后,会设置到此 Future,并唤醒阻塞的业务线程;当超时时间到结果还未返回时,业务线程将会异常返回。 -
异步调用: 基于 Dubbo 底层的异步 NIO 实现异步调用,对于 Provider 响应时间较长的场景是必须的,它能有效利用 Consumer 端的资源,相对于 Consumer 端使用多线程来说开销较小。
<dubbo:reference id="asyncService" interface="com.alibaba.dubbo.samples.async.api.AsyncService"> <dubbo:method name="goodbye" async="true"/> </dubbo:reference>
AsyncService service = ...; String result = service.goodbye("samples");// 这里的返回值为空,请不要使用 Future<String> future = RpcContext.getContext().getFuture(); ... // 业务线程可以开始做其他事情 result = future.get(); // 阻塞需要获取异步结果时,也可以使用 get(timeout, unit) 设置超时时间
Dubbo Consumer 端发起调用后,同时通过
RpcContext.getContext().getFuture()
获取跟返回结果关联的Future
对象,然后就可以开始处理其他任务;当需要这次异步调用的结果时,可以在任意时刻通过future.get(timeout)
来获取。 参数回调: 参数回调有点类似于本地 Callback 机制,但 Callback 并不是 Dubbo 内部的类或接口,而是由 Provider 端自定义的;Dubbo 将基于长连接生成反向代理,从而实现从 Provider 端调用 Consumer 端的逻辑。
-
事件通知: 事件通知允许 Consumer 端在调用之前、调用之后或出现异常时,触发
oninvoke
、onreturn
、onthrow
三个事件。 可以通过在配置 Consumer 时,指定事件需要通知的方法,如:<bean id="demoCallback" class="com.alibaba.dubbo.samples.notify.impl.NotifyImpl" /> <dubbo:reference id="demoService" check="false" interface="com.alibaba.dubbo.samples.notify.api.DemoService" version="1.0.0" group="cn"> <dubbo:method name="sayHello" onreturn="demoCallback.onreturn" onthrow="demoCallback.onthrow"/> </dubbo:reference>
-
-
一般使用什么注册中心?还有别的选择吗?
- Multicast注册中心: Multicast注册中心不需要任何中心节点,只要广播地址,就能进行服务注册和发现。 组播受网络结构限制,只适合小规模应用或开发阶段使用。组播地址段: 224.0.0.0 - 239.255.255.255
- Zookeeper注册中心: 基于分布式协调系统Zookeeper实现,采用Zookeeper的watch机制实现数据变更
- redis注册中心: 基于redis实现,采用key/Map存储,key存储服务名和类型,Map中key存储服务URL,value服务过期时间。基于redis的发布/订阅模式通知数据变更
-
Dubbo中zookeeper做注册中心,如果注册中心集群都挂掉,那发布者和订阅者还能通信吗
可以的,为什么呢?zookeeper的信息会缓存到本地作为一个缓存文件,并且转换成
properties
对象方便使用. -
服务提供者实现失效踢出是什么原理
Zookeeper中节点是有生命周期的.具体的生命周期取决于节点的类型.节点主要分为
持久节点(Persistent)
和临时节点(Ephemeral)
.- 临时节点: 临时节点的生命周期和客户端会话绑定,也就是说,如果客户端会话失效,那么这个节点就会自动被清除掉
- 持久节点:所谓持久节点,是指在节点创建后,就一直存在,直到有删除操作来主动清除这个节点,也就是说不会因为创建该节点的客户端会话失效而消失
ZK 中我们让所有的机其都注册一个临时节点,我们判断一个机器是否可用,我们只需要判断这个节点在ZK中是否存在就可以了,不需要直接去连接需要检查的机器,降低系统的复杂度
-
如何解决调用链过长的问题
Dubbo 可以使用 Pinpoint 和 Apache Skywalking(Incubator) 实现分布式服务追踪
-
核心的配置有哪些
- dubbo:consumer
- timeout 远程服务调用超时时间(毫秒)
- retries 远程服务调用重试次数,不包括第一次调用,不需要重试请设为0,仅在cluster为failback/failover时有效
- loadbalance 负载均衡策略,可选值:random,roundrobin,leastactive,分别表示:随机,轮询,最少活跃调用
- async 是否缺省异步执行,不可靠异步,只是忽略返回值,不阻塞执行线程
- dubbo:provider(Provider 上可以配置的 Consumer 端的属性有哪些)
- protocol 协议名称
- threads 服务线程池大小(固定大小)
- serialization dubbo协议缺省为hessian2,rmi协议缺省为java,http协议缺省为json
- threadpool 线程池类型,可选:fixed/cached/limit
- retries 远程服务调用重试次数,不包括第一次调用,不需要重试请设为0
- actives 消费者端,最大并发调用限制
- loadbalance:负载均衡算法,默认随机
- timeout:方法调用超时
- dubbo:consumer
-
Dubob集群容错有几种方案?
- Failover Cluster:失败自动切换,当出现失败,重试其它服务器 [1]。通常用于读操作,但重试会带来更长延迟。可通过 retries="2" 来设置重试次数(不含第一次)。
- Failfast Cluster:快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。
- Failsafe Cluster:失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
- Failback Cluster:失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
- Forking Cluster:并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks="2" 来设置最大并行数。
- Broadcast Cluster:广播调用所有提供者,逐个调用,任意一台报错则报错 [2]。通常用于通知所有提供者更新缓存或日志等本地资源信息。
-
Dubbo服务降级,失败重试怎么做?
可以通过服务降级功能临时屏蔽某个出错的非关键服务,并定义降级后的返回策略。 如电商促销中,屏蔽“商品推荐”这些边缘业务,减少一定的并发
RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension(); Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://10.20.153.10:2181")); registry.register(URL.valueOf("override://0.0.0.0/com.foo.BarService?category=configurators&dynamic=false&application=foo&mock=force:return+null"));
-
mock=force:return+null
表示消费方对该服务的方法调用都直接返回 null 值,不发起远程调用。用来屏蔽不重要服务不可用时对调用方的影响。 - 还可以改为
mock=fail:return+null
表示消费方对该服务的方法调用在失败后,再返回 null 值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响。
-
Dubbo使用了哪些设计模式
-
Dubbo SPI与 Java SPI区别
SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。
JAVA:
public interface Robot { void sayHello(); } public class OptimusPrime implements Robot { @Override public void sayHello() { System.out.println("Hello, I am Optimus Prime."); } } public class Bumblebee implements Robot { @Override public void sayHello() { System.out.println("Hello, I am Bumblebee."); } }
-
META-INF/services 文件夹下创建一个文件,名称为 Robot 的全限定名 org.apache.spi.Robot
org.apache.spi.OptimusPrime org.apache.spi.Bumblebee
-
加载并使用
public class JavaSPITest { @Test public void sayHello() throws Exception { ServiceLoader<Robot> serviceLoader = ServiceLoader.load(Robot.class); System.out.println("Java SPI"); serviceLoader.forEach(Robot::sayHello); } }
Dubbo SPI 示例
Dubbo 并未使用 Java SPI,而是重新实现了一套功能更强的 SPI 机制。Dubbo SPI 的相关逻辑被封装在了 ExtensionLoader 类中,通过 ExtensionLoader,我们可以加载指定的实现类。Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径下,配置内容如下。
optimusPrime = org.apache.spi.OptimusPrime bumblebee = org.apache.spi.Bumblebee
与 Java SPI 实现类配置不同,Dubbo SPI 是通过键值对的方式进行配置,这样我们可以按需加载指定的实现类。另外,在测试 Dubbo SPI 时,需要在 Robot 接口上标注 @SPI 注解。下面来演示 Dubbo SPI 的用法:
public class DubboSPITest { @Test public void sayHello() throws Exception { ExtensionLoader<Robot> extensionLoader = ExtensionLoader.getExtensionLoader(Robot.class); Robot optimusPrime = extensionLoader.getExtension("optimusPrime"); optimusPrime.sayHello(); Robot bumblebee = extensionLoader.getExtension("bumblebee"); bumblebee.sayHello(); } }
-
Dubbo支持分布式事务吗?
目前暂时不支持,后续可能采用基于 JTA/XA 规范实现
-
Dubbo如何优雅停机
优雅停机是指在停止应用时,执行的一系列保证应用正常关闭的操作。这些操作往往包括等待已有请求执行完成、关闭线程、关闭连接和释放资源等,优雅停机可以避免非正常关闭程序可能造成数据异常或丢失,应用异常等问题。优雅停机本质上是JVM即将关闭前执行的一些额外的处理代码。
Provider在接收到停机指令后
- 从注册中心上注销所有服务;
- 从配置中心取消监听动态配置;
- 向所有连接的客户端发送只读事件,停止接收新请求;
- 等待一段时间以处理已到达的请求,然后关闭请求处理线程池;
- 断开所有客户端连接。
Consumer在接收到停机指令后
- 拒绝新到请求,直接返回调用异常;
- 等待当前已发送请求执行完毕,如果响应超时则强制关闭连接。
-
Dubbo与Spring Cloud区别
Dubbo确实类似于Spring Cloud的一个子集,Dubbo功能和文档完善。 对于类似于电商等同步调用场景多并且能支撑搭建Dubbo 这套比较复杂环境的成本的产品而言,Dubbo 确实是一个可以考虑的选择。
Spring Cloud由众多子项目组成,如Spring Cloud Config、Spring Cloud Netflix、Spring Cloud Consul 等,提供了搭建分布式系统及微服务常用的工具,如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性token、全局锁、选主、分布式会话和集群状态等,满足了构建微服务所需的所有解决方案。
-
当一个服务接口有多种实现时怎么做
当一个接口有多种实现时,可以用 group 属性来分组,服务提供方和消费方都指定同一个 group 即可
-
Dubbo在安全机制方面是如何解决的
Dubbo通过Token令牌防止用户绕过注册中心直连,然后在注册中心上管理授权。Dubbo还提供服务黑白名单,来控制服务所允许的调用方。