什么是Dubbo?
Dubbo是阿里巴巴开源的基于 Java 的高性能 RPC 分布式服务框架,现已被 Apache 维护。可以实现透明化的远程方法调用,可以实现路由选择,负载均衡,集群容错等功能
DubboX 是 Dubbo 的一个扩展,加入了一些服务如可 Restful 调用等。
Provider | 暴露服务的服务提供方 |
---|---|
Consumer | 远程服务调用的消费方 |
Registry | 服务注册与发现的注册中心 |
Monitor | 监控服务的调用次数与调用时间的监控中心 |
Container | 服务运行容器 |
为什么要使用Dubbo?
- 国内许多互联网公司都在使用,意味着该项目已经比较成熟、稳定
- 分布式架构的扩展性好、进一步实现解耦,在未来出现系统瓶颈或功能扩展时,能快速解决
其他分布式架构
Spring Cloud(使用RESTFul方式通信)、Apache Thrift(使用RPC方式通信)
Dubbo服务、消费方常见配置概念
Dubbo在 的所有配置类似,多个配置是有覆盖关系的:
- 方法级优先,接口级次之,全局配置再次之。
- 如果级别一样,则消费方优先,提供方次之。
Dubbo工作流程
服务消费方发起一次调用后,经过 路由选择服务提供方 --> 负载均衡选择服务提供节点 --> 集群容错保证服务可用性
负载均衡
通过 loadbalance=“roundrobin / leastactive / consistenthash” 属性来指定,可配置在服务级,方法级
Dubbo提供了四种策略:
- RondomLoadBalance: 随机负载均衡。随机的选择一个。是Dubbo的默认负载均衡策略。
有权重的概念,权重值默认相等;权重大的分配请求概率大,故在该模式下,应对机器性能高的设置更大的权重值。 - RoundRobinLoadBalance: 轮询负载均衡。轮询选择一个。
也有权重的概念,会按照设置的权重比例来分配请求;在该模式下,会出现慢的 Provider 不断积累请求。 - LeastActiveLoadBalance: 最少活跃调用数,相同活跃数的随机。活跃数指调用前后计数差。使慢的 Provider 收到更少请求,因为越慢的 Provider 的调用前后计数差会越大。
该模式的目的是让处理慢的机器,收到更少的请求。 - ConsistentHashLoadBalance: 一致性哈希负载均衡。相同参数的请求总是落在同一台机器上。
该模式可以配合 Provider 本地缓存使用,可以减少对数据库或缓存中间件的访问次数,同时减少网络 I/O 操作。
集群容错
通过 cluster=“failfast / failsafe / failback / forking / broadcast ” 属性来指定
Dubbo提供6中集群容错模式
- Failover: 失败后重试,使用 retries=“2” 设置重试次数,不含第一次;为 Dubbo 默认的容错方案;
- Failfast : 快速失败模式,调用只执行一次,如果失败就报错;如写操作,写入数据时,如果一次调用失败就应当提示错误信息;
- Failsafe: 失败安全模式,将忽略调用失败的信息;在该模式下调用出错时,会给上游返回一个空结果,在上游上来看,是执行成功的,但对于调用出错的下游,会记录一条错误日志。
- Failback: 首次调用失败后定时异步重发一次(5秒后异步重发,不可修改),如果还是失败则忽略;适用于对实时性要求不高,且不需要返回值的一些异步操作。
- Forking: 并行的调用多个 Provider ,只要一个返回成功就算成功;常用于实时性要求较高的读操作,可设置 forks=“2”(并行调用2台) 避免调用大量的 Provider ;
- Broadcast:广播所有 Provider ,只要一个发生错误就算调用失败;常用于广播更新 Provider 本地的缓存等;
消费方同步/异步方法调用
同步调用过程
默认便为同步调用,即消费方发出请求后,线程阻塞等待服务方返回结果。
那么Dubbo是怎么实现线程阻塞并得到结果的呢?这里便用到了 Future 类型的线程,消费方调用 future.get() 方法阻塞线程用以获取服务方的返回结果。
异步调用过程
消费方:
<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();//不同版本的应使用不同的get方法
... // 业务线程可以开始做其他事情
result = future.get(); // 阻塞需要获取异步结果时,也可以使用 get(timeout, unit) 设置超时时间
method其他配置参数
sent=“true / false” 设置是否等待消息发出,默认为false:
true:等待消息发出,消息发送失败将抛出异常。
false:不等待消息发出,将消息放入 IO 队列,即刻返回。
return=“true / false” 设置是否忽略返回值,默认为true
true:有返回值
false:完全忽略返回值,不会创建 Future 对象,也就是说 RpcContext.getContext().getFuture(); 方法将返回 null
异步方法调用代码用例:
//////////////////////////////////////// consumer端
@RequestMapping("/myTest.dhtml")
@ResponseBody
public String myTest(String str){
String s = fourDetectionService.testGet(str);//异步调用方法,立即返回null
System.err.println("return in consumer,str="+s);
System.out.println("do something");
Future<String> future = RpcContext.getContext().getFuture();
try {
System.out.println("block wait service's result");
//阻塞2000ms,等待provider返回结果
String returnStr=future.get(2000, TimeUnit.MILLISECONDS);//get方法阻塞线程,等待返回结果
System.err.println("return in consumer,str="+returnStr);
} catch (Exception e) {
e.printStackTrace();
}
return "success";
}
//////////////////////////////////////// provider端
public String testGet(String str) {
try {
System.err.println("provider sleep 1000");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.err.println(str+" in serviceImpl");
return str+" in serviceImpl";
}
//////////////////////////////////////// xml配置的reference
<dubbo:reference group="${dubbo.mygroup}" timeout="99999" id="fourDetectionService" interface="com.wisesoft.ilda.cinfo.service.FourDetectionService" >
<dubbo:method name="testGet" loadbalance="leastactive" retries="1" async="true"></dubbo:method>
</dubbo:reference>
控制台输出:
return in consumer,str=null
sun.reflect.GeneratedMethodAccessor802.invoke:do something
sun.reflect.GeneratedMethodAccessor802.invoke:block wait service's result
provider sleep 1000
abc in serviceImpl
return in consumer,str=abc in serviceImpl