如何处理Dubbo调用超时

一、简述

同步调用是一种阻塞式的调用方式,即 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中还可以通过将配置写入注册中心的方式覆盖用户配置(优先级高于系统参数)。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,701评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,649评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,037评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,994评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,018评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,796评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,481评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,370评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,868评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,014评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,153评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,832评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,494评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,039评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,156评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,437评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,131评论 2 356