指定方法异步调用

前面我们讲解了通过设置ReferenceConfig的setAsync()方法来让整个接口里的所有方法变为异步调用,那么如何指定某些方法为异步调用呢?下面讲解下如何正确地设置默写方法为异步调用。

假如你只需要设置接口里的方法sayHello为异步调用,那么可以使用下面方式:

final List<MethodConfig> asyncMethodList = new ArrayList<MethodConfig>();
MethodConfig methodConfig = new MethodConfig();
methodConfig.setAsync(true);
methodConfig.setName("sayHello");
asyncMethodList.add(methodConfig);

然后调用ReferenceConfig的setMethods(asyncMethodList)即可。另外如果异步调用的方法没有返回值,则可以再调用methodConfig.setReturn(false); ,以便减少Dubbo内部Future对象的创建和管理成本。

关闭启动检查

正常情况下,在服务消费端启动时会检查依赖的服务是否存在,如果不存在则会抛出 throw new IllegalStateException("Failed to check the status of the service" + interfaceName + ".No provider available for the service ")异常阻止Spring初始化完成,以便上线前能及早发现问题。

可以通过调用ReferenceConfig.setCheck(false)关闭检查,设置check为true有助于及时发现问题,那么什么时候需要设置false呢?

比如测试时,有些无关的服务启动不了,导致整个应用都启动不了,这时候你可以把那些无关服务的check设置为false。再比如出现了循环依赖,必须有一方先启动,比如你给服务使用方提供了一个SDK,SDK里面使用Dubbo API远程消费服务器提供方的服务,如果你在服务提供方的服务器里面也引入这个SDK,在服务提供方启动时候就会抛出 No Provider available for the service异常,具体原因是服务提供方启动时候会初始化SDK,而SDK里面初始化时候需要检查服务提供方是否存在,而服务提供方的服务还没提供出来。

另外需要注意的是check设置为false,总是会返回调用,当服务提供方恢复服务时,能自动连上。

如何设置均衡策略

由于Dubbo提供的一致性Hash负载均衡策略,可以允许你自定义虚拟节点个数和指定某些方法需要使用一致性Hash策略,下面具体讲下如何设置:

// 虚拟节点设置为512
Map<String,String> parameters = new HashMap<String,String>();
parameters.put("hash.nodes","512");
ReferenceConfig<T> reference = new ReferenceConfig<T>();
// 设置负载均衡为一致性Hash
reference.setLoadbalance(consistenthash);
// 设置参数
reference.setParameters(parameters);

如下代码设置接口的sayHello方法为一致性Hash负载均衡策略,设置saySomething方法为随机负载均衡策略:

ReferenceConfig reference = new ReferenceConfig();
final List<MethodConfig> methodList = new ArrayList<MethodConfig>();
// 设置sayHello方法为一致性Hash负载均衡策略
MethodConfig methodConfig = new MethodConfig();
methodConfig.setName("sayHello");
methodConfig.setLoadbalance("consistenthash");
// 虚拟节点设置为512
Map<String,String> parameters = new HashMap<String,String>();
parameters.put("hash.nodes","512");
methodConfig.setParameters(parameters);
methodList.add(methodConfig);
// 设置saySomething方法为随机负载均衡策略
methodConfig = new MethodConfig();
methodConfig.setName("saySomething");
methodConfig.setLoadbalance("random");
methodList.add(methodConfig);
reference.setMethods(methodList);

另外,默认情况下一致性hash使用第一个参数值计算hash值,如果你需要自定义可以通过以下设置:

Map<String,String> parameters = new HashMap<String,String>();
parameters.put("hash.nodes","512");
parameters.put("hash.arguments","0,1"); // 使用第一个和第二个参数值计算hash值
methodConfig.setParameters(parameters);

注意"0,1"是一个字符串,里面使用英文","分隔。

服务消费端ReferenceConfig需要自行缓存

ReferenceConfig实例是个很重的实例,每个ReferenceConfig实例里都维护了与服务注册中心的一个长链,并且维护了与所有服务提供者的长链。假设有一个服务注册中心和N个服务提供者,那么每个ReferenceConfig实例里面维护了N+1个长链,如果频繁地生成ReferenceConfig实例,可能会造成性能问题,甚至产生内存或者连接泄露的风险。特别是使用Dubbo API编程时候容易忽略这个问题。

为了解决这个问题,之前都是自行缓存,但自从发布Dubbo 2.4.0版本后,Dubbo提供了简单的工具类ReferenceConfigCache用于缓存ReferenceConfig实例。使用如下:

// 创建服务消费实例
ReferenceConfig<XxxService> reference = new ReferenceConfig<XxxService>();
reference.setInterface(XxxService.class);
reference.setVersion("1.0.0");
......
// 获取Dubbo提供的缓存
ReferenceConfigCache cache = ReferenceConfigCache.getCache();
// cache.get方法中会缓存reference对象,并且调用reference.get方法启动ReferenceConfig,并返回经过代理后的服务接口的对象
XxxService xxxService = cache.get(reference);
// 使用xxxService对象
xxxService.sayHello();

需要注意的是Cache内持有ReferenceConfig对象的引用,不要在外部再调用ReferenceConfig的destroy方法了,这会导致Cache内的ReferenceConfig失效!

如果要销毁Cache中的ReferenceConfig,将销毁ReferenceConfig并释放对应的资源,具体使用下面方法来销毁:

ReferenceConfigCache cache = ReferenceConfigCache.getCache();
cache.destroy(reference);

另外在Dubbo中唯一确定一个服务是通过 接口+分组+版本,所以默认情况下cache内是通过服务Group/接口/版本三个属性来标示一个ReferenceConfig实例的。即以服务Group/接口/版本为缓存的key,ReferenceConfig实例为对应的value。如果你需要使用自定义的key,可以在创建cache时候调用 ReferenceConfigCache cache = ReferenceConfigCache.getCache(keyGenerator); 方法传递自定义的keyGenerator。

并发控制

服务消费方并发控制

在服务消费方进行并发控制,需要设置actives参数,如下:

<dubbo:reference id="userService" interface="com.test.UserServiceBo" group="dubbo" version="1.0.0" timeout="3000" actives="10" />

设置com.test.UserServiceBo接口中的所有方法,每个方法最多同时并发请求10个请求。

也可以使用下面方法设置接口中单个方法的并发请求个数,如下:

<dubbo:reference id="userService" interface="com.test.UserServiceBo" group="dubbo" version="1.0.0" timeout="3000">
<dubbo:method name="sayHello" actives="10" />
</dubbo:reference>

如上设置sayHello方法的并发请求数量最大为10,如果客户端请求该方法并发超过了10则客户端会被阻塞,等客户端并发请求数量少于10的时候,该请求才会被发送到服务提供方服务器。在Dubbo中客户端并发控制使用ActiveLimitFilter过滤器来控制,代码如下:

public class ActiveLimitFilter implements Filter{
public Result invoke(Invoker<?> invoker , Invocation invocation) throws RpcException{
URL url = invoker.getUrl();
String methodName = invocation.getMethodName();
// 获取设置的active值,默认为0
int max = invoker.getUrl().getMethodParameter(methodName , Constants.ACTIVES_KEY , 0);
RpcStatus count = RpcStatus.getStatus(invoker.getUrl() , invocation.getMethodName());
if(max > 0){
long timeout = invoker.getUrl().getMethodParameter(invocation.getMethodName() , Constants.TIMEOUT_KEY , 0);
long start = System.currentTimeMillis();
long remain = timeout;
int active = count.getActive();
// 如果该方法并发请求数量大于设置值,则挂起当前线程
if(active >= max){
sychronized(count){
while((active = count.getActive()) >= max){
try{
count.wait(remain);
}catch(InterruptedException e){ }
// 如果等待时间超时,则抛出异常
long elapsed = System.currentTimeMillis() - start;
remain = timeout - elapsed;
if(remain <= 0){
throw new RpcException("Waiting concurrent invoke timeout in client-side for service: " + invoker.getInterface().getName() + " , method: " + invocation.getMethodName() + ",elapsed: " + elapsed + ",timeout: " + timeout + ". concurrent invokes: " + active + ". max concurrent invoke limit:" + max);
}
}
}
} }
// 没有限流的时候,正常调用
try{
long begin = System.currentTimeMillis();
RpcStatus.beginCount(url , methodName);
try{
Result result = invoker.invoke(invocation);
RpcStatus.endCount(url , methodName , System.currentTimeMillis() - begin , true);
return result;
}catch(RuntimeException t){
RpcStatus.endCount(url , methodName , System.currentTimeMillis() - begin , false);
throw t;
}
}finally{
if(max > 0){
synchronized(count){
count.notify();
}
}
}
}
}

由上可知,在客户端并发控制中,如果当并发量达到指定值后,当前客户端请求线程会被挂起,如果在等待超时期间并发请求量少了,那么阻塞的线程会被激活,然后发送请求到服务提供方,如果等待超时了,则直接抛出异常,这时服务根本都没有发送到服务提供方服务器。

服务提供方并发控制

在服务提供方进行并发控制需要设置executes参数,如下:

<dubbo:service interface="com.test.UserServiceBo" ref="userService" group="dubbo" version="1.0.0" timeout="3000" executes="10" />

设置com.test.UserServiceBo 接口中所有方法,每个方法最多同时并发处理10个请求,这里并发是指同时在处理10个请求。

也可以使用下面方法设置接口中单个方法的并发处理个数,如下:

<dubbo:service interface="com.test.UserServiceBo" ref="userService" group="dubbo" version="1.0.0" timeout="3000">
<dubbo:method name="sayHello" executes="10" />
</dubbo:service>

如上设置sayHello方法的并发处理数量为10 。

需要注意的是,服务提供方设置并发数量后,如果同时请求数量大于了设置的executes的值,则会抛出异常,而不是像服务端设置actives时候,会等待。服务提供方并发控制是使用ExecuteLimitFilter过滤器实现的,ExecuteLimitFilter代码如下:

public class ExecuteLimitFilter implements Filter{
public Result invoke(Invoker<?> invoker , Invocation invocation) throws RpcException{
URL url = invoker.getUrl();
// 默认不设置executes时候,其值为0
int max = url.getMethodParameter(methodName , Constants.EXECUTES_KEY , 0);
if(max > 0){ // max>0说明设置了executes值
RpcStatus count = RpcStatus.getStatus(url , invocation.getMethodName());
// 可知如果并发处理数量大于设置的值,会抛出异常
executesLimit = count.getSemaphore(max);
if(executesLimit != null && !(acquireResult = executesLimit.tryAcquire())){
throw new RpcException("Failed to invoke method " + invocation.getMethodName() + " in provider " + url + ",cause: The service using threads greater than <dubbo:service execute=\"" + max + "\"> limited.");
}
}
...
try{ // 没有限流的时候,激活filter链
Result result = invoker.invoke(invocation);
return result;
}catch(){
...
}finally{
...
} }
}

所以当使用executes参数时候要注意,当并发量过大时侯,多余的请求会失败。

改进的广播策略

前面我们讲解集群容错时谈到广播策略,该策略主要用于对所有服务提供者广播消息,那么有个问题需要思考,广播是说你在客户端调用接口一次,内部就是轮询调用所有服务提供者的机器的服务,那么你调用一次该接口,返回值是什么呢?比如内部轮询了10台机器,每个机器应该都有一个返回值,那么你调用的这一次返回值是10个返回值的组成?其实不是,返回的轮询调用的最后一个机器结果,我们可以看下BroadcastClusterInvoker的主干代码:

public class BroadcastClusterInvoker<T> extends AbstractClusterInvoker<T>{
private static final Logger logger = LoggerFactory.getLogger(BroadcastClusterInvoker.class); public BroadcastClusterInvoker(Directory<T> directory){
super(directory);
} @SuppressWarnings({"unchecked","rawtypes"})
public Result doInvoke(final Invocation invocation , List<Invoker<T>> invokers , LoadBalance loadbalance){
...
// 使用循环,轮询每个机器进行调用,其中result为最后一个机器的结果
for(Invoker<T> invoker : invokers){
try{
result = invoker.invoke(invocation);
}catch(RpcException e){
exception = e;
logger.warn(e.getMessage() , e);
}catch(Throwable e){
exception = new RpcException(e.getMessage(),e);
logger.warn(e.getMessage() , e);
}
}
if(exception != null){
throw exception;
}
return result;
}
}

如上代码,可知使用循环轮询调用每个机器,其中result为调用最后一个机器的结果。

如果我想获取所有服务提供者的结果,该怎么办呢?其实我们可以自定义一个SPI扩展,并且规定我们的服务接口的返回结果为一个map,代码如下:

public Result doInvokePara(final Invocation invocation , List<Invoker<T>> invokers , LoadBalance loadbalance) throws RpcException{
// 用来保存所有服务提供者返回的结果
Map allResult = new ConcurrentHashMap<String , Result>();
// 保存异步调用返回的Future对象
List<Future<Result>> futureList = new ArrayList<Future<Result>>();
// 所有服务提供者的机器个数
int machineNum = invokers.size();
for(Invoker<T> invoker : invokers){
try{
// 异步调用服务提供者
Future<Result> future = paramCallPool.submit(new Callable<Result>(){
@Override
public Result call() throws Exception{
try{
// 具体调用服务提供者
Result result = invoker.invoke(invocation);
// 服务提供者 ip:port
String url = invoker.getUrl().getAddress();
// 保存结果到map,key为服务提供者的地址,value为返回结果
allResult.put(url , result.getResult());
return result;
}catch(RpcException e){
logger.warn(e.getMessage(),e);
}catch(Throwable e){
logger.warn(e.getMessage(),e);
}
return null;
}
});
futureList.add(future);
}catch(Exception e){
logger.warn(e.getMessage() , e);
}
}
// 等所有调用完成
for(Future<Result> future : futureList){
try{
future.get();
}catch(InterruptedException | ExecutionException e){
e. printStackTrace();
}
} // 假设服务接口返回中类型为这个
ACCSResult<Map> resultTemp = new ActionResult<Map>(true,null,null,null);
// 自定义返回结果
Map finalResult = new HashMap<String,Result>();
finalResult.put("machineNum",machineNum);
finalResult.put("result",result);
resultTemp.setData(finalResult);
// 重新设置返回值
Result result = new RpcResult(resultTemp);
return result;
}

  

Dubbo学习笔记11:使用Dubbo中需要注意的一些事情的更多相关文章

  1. Dubbo学习笔记6:Dubbo增强SPI与SPI中扩展点自动包装的实现原理

    在Dubbo整体架构分析中介绍了Dubbo中除了Service和Config层为API外,其他各层均为SPI,为SPI意味着下面各层都是组件化可以被替换的,也就是扩展性比较强,这也是Dubbo比较好的 ...

  2. Dubbo学习笔记3:Dubbo管理控制台与监控中心的搭建

    Dubbo源码下载与编译 本文来讲下如何在Dubbo源码编译后,得到用于搭建管理控制台的war包和监控平台的jar包. 首先需要到Github上下载Dubbo的源码,解压后如下: 在dubbo-2.5 ...

  3. Dubbo学习笔记2:Dubbo服务提供端与消费端应用的搭建

    Demo结构介绍 Demo使用Maven聚合功能,里面有三个模块,目录如下: 其中Consumer模块为服务消费者,里面TestConsumer和consumer.xml组成了基于Spring配置方式 ...

  4. Dubbo学习笔记5:Dubbo整体框架分析

    Dubbo的分层架构 本文将简单介绍Dubbo的分层架构设计,如下图是Dubbo官方的整体架构图: Dubbo官方提供的该架构图很复杂,一开始我们没必要深入细节,下面我们简单介绍下其中的主要模块. 其 ...

  5. Dubbo学习笔记10:Dubbo服务消费方启动流程源码分析

    同理我们看下服务消费端启动流程时序图: 在<Dubbo整体架构分析>一文中,我们提到服务消费方需要使用ReferenceConfig API来消费服务,具体是调用代码(1)get()方法来 ...

  6. Dubbo学习笔记8:Dubbo的线程模型与线程池策略

    Dubbo默认的底层网络通讯使用的是Netty,服务提供方NettyServer使用两级线程池,其中 EventLoopGroup(boss) 主要用来接受客户端的链接请求,并把接受的请求分发给 Ev ...

  7. Dubbo学习笔记9:Dubbo服务提供方启动流程源码分析

    首先我们通过一个时序图,直观看下Dubbo服务提供方启动的流程: 在<Dubbo整体框架分析>一文中我们提到,服务提供方需要使用ServiceConfig API发布服务,具体是调用代码( ...

  8. Dubbo学习笔记7:Dubbo的集群容错与负载均衡策略

    Dubbo的集群容错策略 正常情况下,当我们进行系统设计时候,不仅要考虑正常逻辑下代码该如何走,还要考虑异常情况下代码逻辑应该怎么走.当服务消费方调用服务提供方的服务出现错误时候,Dubbo提供了多种 ...

  9. tornado学习笔记11 Web应用中模板(Template)使用应用实践

    上一篇中(Web应用中模板的工作流程分析),已经分析了模板的渲染流程,以及相关参数获取及设置原理.这篇主要讲述模板在实际应用案例. 11.1 需求 根据用户输入的两次密码,判断两次密码是否一致,并将判 ...

随机推荐

  1. Dubbo+zookeeper搭建环境学习笔记

    Dubbo背景和简介 Dubbo开始于电商系统,因此在这里先从电商系统的演变讲起. 1.单一应用框架(ORM) 当网站流量很小时,只需一个应用,将所有功能如下单支付等都部署在一起,以减少部署节点和成本 ...

  2. LeetCode Container With Most Water (Two Pointers)

    题意 Given n non-negative integers a1, a2, ..., an, where each represents a point at coordinate (i, ai ...

  3. 写个发邮件的功能php的(全代码)

    ---恢复内容开始--- 正好做了个项目,需要在线留言,一般在线留言发邮件是很常见的方式,一开始从网上搜了很久都没有很全的,也有全一点的,但是也不能用,运行不成功,下面给大家分享一下运行成功了的全部代 ...

  4. 基于AngularJs的单页面程序

    基于AngularJs的单页面程序 在Abpzero的后台管理系统是一个AngularJs的单页面程序.当你登陆后,系统会跳转到"ApplicationController",然后 ...

  5. Css_button样式对不齐

    发现了是按钮的vertical-align式样,统一显示的设置为middle,搞定.

  6. python基础篇----基本数据类型

    bit  #bit_length 当前数字的二进制,只用用n位来表示a = 123b = a.bit_length()print(b)#==>7

  7. 06-docker组件如何协作

    容器启动过程如下: Docker 客户端执行 docker run 命令. Docker daemon 发现本地没有 httpd 镜像. daemon 从 Docker Hub 下载镜像. 下载完成, ...

  8. 了不起的Node.js--之三

    开发工具: 我使用的开发工具是Mac版的WebStorm,这个工具支持Nodejs,只要按照如下步骤设置即可以支持 1.WebStorm的开发界面,这个开发工具还是非常好用的. 2.WebStorm的 ...

  9. opencv图像识别技术在自动化测试中的应用

    在自动化测试中,基于xpath.js选择器.css选择器进行元素定位及判定的技术已经比较成熟.在实际应用中,无论是web端还是移动端,仍有很多时候需要根据页面内容.页面中的图像进行定位及判定,这里介绍 ...

  10. Codeforces914G Sum the Fibonacci(FWT)

    FWT大杂烩.跟着模拟做很多次FWT即可. #include<iostream> #include<cstdio> #include<cmath> #include ...