如何处理Dubbo调用超时

2 篇文章 0 订阅

一、简述

同步调用是一种阻塞式的调用方式,即 Consumer 端代码一直阻塞等待,直到 Provider 端返回为止。dubbo默认的协议是netty, Netty 是 NIO 异步通讯机制,那么服务调用是怎么转化为同步的呢?Dubbo是阿里开源的RPC框架,因为基于接口开发支持负载均衡、集群容错、版本控制等特性,因此现在有很多互联网公司都在使用Dubbo。
1️⃣Dubbo有三个级别的超时设置分别为:
①针对方法设置超时时间
②在服务方设置超时时间
③在调用方设置超时时间

2️⃣一般超时是调用端发生在请求发出后,无法在指定的时间内获得对应的响应。原因大概有以下几种情况:
①服务端确实处理比较慢,无法在指定的时间返回结果,调用端就自动返回一个超时的异常响应来结束此次调用。
②服务端如果响应的比较快,但当客户端 Load 很高,负载压力很大的时候,会因为客户端请求发不出去、响应卡在 TCP Buffer 等问题,造成超时。因为客户端接收到服务端发来的数据或者请求服务端的数据,都会在系统层面排队,如果系统负载比较高,在内核态的时间占比就会加长,从而造成客户端获取到值时已经超时。
③通常是业务处理太慢,可在服务提供方机器上执行:jstack [PID] > jstack.log 分析线程都卡在哪个方法调用上,这里就是慢的原因。如果不能调优性能,请调高 timeout 阈值。

3️⃣排查和解决步骤
①两边可能有 GC,检查服务端和客户端 GC 日志,耗时很长的 GC,会导致超时。超时的发生很可能意味着调用端或者服务端的资源(CPU、内存或者网络)出现了瓶颈,需要检查服务端的问题还是调用端的问题来排除GC抖动等嫌疑。
②检查服务端的网络质量,比如重传率来排除网络嫌疑。
③借助链路跟踪的分析服务(比如阿里的 ARMS,开源的 OpenTracing 系的实现 Zipkin、SkyWalking 等)来分析下各个点的耗时情况。

4️⃣Dubbo调用超时(client-side timeout)后会有两种情况:
①客户端会收到一个TimeoutException异常
②服务端会收到一个警告The timeout response finally returned at xxx
看起来还蛮正常的,但是实际上会有这样问题:调用超时后服务端还是会继续执行,该如何处理呢?

 

@Service(version = "1.0")
@Slf4j
public class DubboDemoServiceImpl implements DubboDemoService {
  public String sayHi(String name) {
    try {
      Thread.sleep(3000);
     } catch (InterruptedException e) {
      throw new RuntimeException(e);
     }
     String result = "hi: " + name;
     log.info("Result: {}" , result);
     return result;
  }
}

服务非常简单,三秒后返回字符串。controller层:

 

@RestController
@RequestMapping
public class DubboDemoController {
  @Reference(url = "dubbo://127.0.0.1:22888?timeout=2000", version = "1.0")
  private DubboDemoService demoService;
  @GetMapping
  public ResponseEntity<String> sayHi(@RequestParam("name") String name){
    return ResponseEntity.ok(demoService.sayHi(name));
  }
}

连接DubboDemoService服务使用的直连方式dubbo://127.0.0.1:22888?timeout=2000,演示中的超时时间都由url中的timeout指定。

二、Consumer超时处理

前面服务端的sayHi()实现休眠3秒,而连接服务时指定的超时时间是2000ms,那肯定会收到一个TimeoutException异常:

 

There was an unexpected error (type=Internal Server Error, status=500).
Invoke remote method timeout. method: sayHi

客户端超时处理比较简单,既然发生了异常也能捕获到异常那是该回滚还是不做处理,完全可以由开发者解决。

 

try{
  return ResponseEntity.ok(demoService.sayHi(name));
}catch (RpcException te){
  //do something...
  log.error("consumer", te);
  return msg;
}

重点还是解决服务方的超时异常。

三、Provider超时处理

Provider的处理没有客户端那样简单,因为Provider不会收到异常,而且线程也不会中断,这样就会导致Consumer超时数据回滚,而Provider继续执行最终执行完数据插入成功,数据不一致。

 

 

上面Provider方法休眠3000ms且Consumer的超时是参数是2000ms。调用发生2000ms后就会发生超时,而Provider的sayHi()不会中断在1000ms后打印hi xx。

 

很明显要保持数据一致就需要在超时后,将Provider的执行终止或回滚才行,如何做到数据一致性呢?

1️⃣重试机制
Dubbo自身有重试机制,调用超时后会发起重试,Provider端需考虑幂等性。

2️⃣最终一致性
使用补偿事务或异步MQ保持最终一致性,需要写一些与业务无关的代码来保持数据最终一致性。比如在Provider端加个check方法,检查是否成功,具体实现还需要结合自身的业务需求来处理。

 

@GetMapping
public ResponseEntity<String> sayHi(String name){
   try{
       return ResponseEntity.ok(demoService.sayHi(name));
    }catch (RpcException te){
       //do something...
       try{
          demoService.check(name);
       }catch (RpcException ignore){
       }
       log.error("consumer", te);
       return msg;
    }
}

虽然可以通过添加检查来验证业务状态,但是这个调用执行时间是没办法准确预知的,所以这样简单的检测是效果不大,最好还是通过MQ来做这样的检测。

3️⃣基于时间回滚
原理比较简单,在Consumer端调用时设置两个参数ctime、ttime分别表示调用时间、超时时间,将参数打包发给Provider。Provider收到两个参数后进行操作,如果执行时间越过ttime则回滚数据,否则正常执行。改造下代码:

 

public ResponseEntity<String> sayHi(@RequestParam("name") String name){
   try{
      RpcContext context = RpcContext.getContext();
      context.setAttachment("ctime", System.currentTimeMillis() + "");
      context.setAttachment("ttime", 2000 + "");
      return ResponseEntity.ok(demoService.sayHi(name));
   }catch (RpcException te){
      //do something...
      log.error("consumer", te);
      return msg;
   }
 }

将ctime、ttime两个参数传到Provider端处理:

 

public String sayHi(String name) {
   long curTime = System.currentTimeMillis();
   String ctime = RpcContext.getContext().getAttachment("ctime");
   String ttime = RpcContext.getContext().getAttachment("ttime");
   long ctimeAsLong = Long.parseLong(ctime);
   long ttimeAsLong = Long.parseLong(ttime);
   try {
     Thread.sleep(3000);
   } catch (InterruptedException e) {
     throw new RuntimeException(e);
   }
   long spent = System.currentTimeMillis() - curTime;
   if(spent >= (ttimeAsLong - ctimeAsLong - curTime)){
     throw new RpcException("Server-side timeout.");
   }
     String result = "hi: " + name;
     log.info("Result: {}" , result);
     return result;
 }

 

 

画个图看一下执行的时间线:

 

从上图在执行完成后,响应返回期间这段时间是计算不出来的,所以这种办法也不能完全解决Provider超时问题。

四、dubbo中配置的优先级

dubbo作为一个服务治理框架,功能相对比较完善,性能也挺不错。要知道dubbo中配置是有优先级的,以免出现调优参数设置了却没发现效果实际是配置被覆盖导致这样的问题。dubbo分为consumer和provider端,在配置各个参数时,其优先级如下:

1、consumer的method配置
2、provider的method配置
3、consumer的reference配置
4、provider的service配置
5、consumer的consumer节点配置
6、provider的provider节点配置

可以看到,方法级的配置优先级高于接口级,consumer的优先级高于provider。同时,在本地参数配置还存在一层优先级:

1、系统参数(-D),如-Ddubbo.protocol.port=20003
2、xml配置
3、property文件

了解了这两个优先级,调优起来才会更加清晰,省去了一些诸如配置设置了不生效这样的麻烦。注意,其实dubbo中还可以通过将配置写入注册中心的方式覆盖用户配置(优先级高于系统参数)。



作者:日常更新
链接:https://www.jianshu.com/p/75f7fbe4944f
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Dubbo连接超时问题通常是由于网络延迟、服务提供者负载过高或者配置错误等多种原因引起的。下面是排查Dubbo连接超时问题的一些常见方法和建议: 1. 检查网络延迟:首先,可以尝试通过ping命令来检查与服务提供者之间的网络延迟情况。如果延迟较高,可能需要优化网络环境或者切换到更稳定的网络连接。 2. 服务提供者负载过高:可以查看服务提供者的系统资源使用情况,例如 CPU、内存、磁盘等是否过载。如果负载过高,可以优化代码、增加服务器资源或者增加服务实例数等来提高性能。 3. 超时配置错误:可以检查Dubbo超时配置是否正确。例如,可以确认是否设置了正确的连接超时时间、读写超时时间等,以及是否合理地设置了重试次数等参数。 4. 服务提供者响应时间过长:可以对服务提供者进行性能分析,找出响应时间较长的接口或者方法,并优化其实现。如果有必要,可以采用异步调用方式来提高吞吐量和响应速度。 5. 检查服务调用链路:可以通过监控工具或者日志来查看服务调用链路,找出是否存在调用关系错乱、环路或者循环依赖等问题。这些问题可能导致连接超时或者请求被阻塞。 6. 调整Dubbo配置参数:可以尝试调整Dubbo的相关配置参数,如线程池大小、队列大小、IO线程数等,以适应当前的应用场景。 总之,解决Dubbo连接超时问题的关键是要深入分析问题背后的原因,并针对性地采取相应的优化措施。在排查问题过程中,可以结合相关的监控工具、日志和性能测试工具来帮助定位和解决问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值