Grpc中Deadline分析

  1. Deadline核心解决的问题,就是在client请求在遇到异常情况时的最大等待时间,减少不必要的阻塞。
  2. GRPC中没有采用传统的timeout方式去处理,而是采用了Deadline机制,主要的区别大致如下:



    先举一个超时的例子,设置超时时长为200毫秒,这时需要调用三个服务A/B/C,当处理到B的时候,已经超时了,这时client会抛出TimeoutException,但是这时请注意,只有client一端响应,实际server端还在工作!



    实际这个时候server是否返回已经不重要了,因为client已经主动的断开调用了,虽然返回不重要,但是这样会造成很大的资源浪费。
    结论:实际看到这,Deadline的要解决的核心问题已经暴露出来了,就是如何两端同步超时时间,如何将超时传播给其他(等待)服务。

    client为自己设置监听器,此处为了体现client与server一致,所以EXCEED由server端报出

设计思路

  1. 当client产生请求时,将request中的deadline绑定到context,并启动一个定时任务,执行时间为当前时间+deadline时间。
  2. service按照依赖关系依次运行,如果超出deadline规定的时间,执行cancel任务,包括中断当前task,清除所有listeners等。
  3. 向server返回exceed异常,通知超时,client结束等待。
client设置deadline

stub.withDeadlineAfter(long duration, TimeUnit unit)
public final S withDeadlineAfter(long duration, TimeUnit unit) {
  return build(channel, callOptions.withDeadlineAfter(duration, unit));
}
定义在

public abstract class AbstractStub<S extends AbstractStub<S>>    
是一个抽象方法,实现在GRPC代码生成器生成的代码中

以下为example的实现

@Override
protected GreeterStub build(io.grpc.Channel channel,
 io.grpc.CallOptions callOptions) {
  return new GreeterStub(channel, callOptions);
}
只是在当前的channel和callOptions基础上新建一个stub对象.(如果反复设置会创建大量stub,会不会影响性能,这么设计的初衷是什么,为何不能复用.)

 

Context 

Context为一个链表性的结构,每个Context会记录自己的父Context,Root的父Context为null.
每个线程的Context保存到ThreadLocal中

final class ThreadLocalContextStorage extends Context.Storage {
/**
 * Currently bound context.
 */
 private static final ThreadLocal<Context> localContext = new ThreadLocal<Context>();
 

server端绑定上下文

首先server端启动时会创建一个serverImpl

public final class ServerImpl extends io.grpc.Server implements WithLogId
该类在初始化的过程中,会创建Context.
@Override
public void streamCreated(
    final ServerStream stream, final String methodName, final Metadata headers) {

  final StatsTraceContext statsTraceCtx = Preconditions.checkNotNull(
      stream.statsTraceContext(), "statsTraceCtx not present from stream");

 final Context.CancellableContext context = createContext(stream, headers, statsTraceCtx);
.......}
以下是createContext方法的内容

private Context.CancellableContext createContext(
    final ServerStream stream, Metadata headers, StatsTraceContext statsTraceCtx) {
  Long timeoutNanos = headers.get(TIMEOUT_KEY);

 Context baseContext = statsTraceCtx.serverFilterContext(rootContext);

 if (timeoutNanos == null) {   如果client没有指明timeout,则生成一个timeout为null的上下文,此举应该是为了向下兼容,之前不支持Deadline的概念,所以扩展了Context
    return baseContext.withCancellation();
 }

  Context.CancellableContext context =
      baseContext.withDeadlineAfter(timeoutNanos, NANOSECONDS, timeoutService); 还是启动一个监听程序,如果当前上线文的状态是超时状态,则自动取消(满足了取消传播的需求)
 context.addListener(new Context.CancellationListener() {
    @Override
 public void cancelled(Context context) {
      Status status = statusFromCancelled(context);
 if (DEADLINE_EXCEEDED.getCode().equals(status.getCode())) {
        // This should rarely get run, since the client will likely cancel the stream before
 // the timeout is reached.
 stream.cancel(status);
 }
    }
  }, directExecutor());

 return context;
}
server端也可以根据实际情况自己设置Context,可以参见API中 public Context attach() 和 public void detach(Context toAttach).

关于Deadline的设置,官方的一段注释写的非常清楚

/**
* If the parent deadline is before the given deadline there is no need to install the value
* or listen for its expiration as the parent context will already be listening for it.
*/
也就是说只有在新设定的deadline值在parent context之前才会创建新的销毁线程.

 

 

 

client端监听代码,这段在clientStreamImpl中,代码跳转太多,只贴上来最后取消的代码

/**
 * Stores listener and executor pair. client端会启动一个监听线程,用来cancel掉这个任务
 */
private class ExecutableListener implements Runnable {
  private final Executor executor;
 private final CancellationListener listener;

 private ExecutableListener(Executor executor, CancellationListener listener) {
    this.executor = executor;
 this.listener = listener;
 }

  private void deliver() {
    try {
      executor.execute(this);
 } catch (Throwable t) {
      log.log(Level.INFO, "Exception notifying context listener", t);
 }
  }

  @Override
 public void run() {
    listener.cancelled(Context.this);
 }
}
 

调用listener.cancelled(Context.this);   会对当前状态进行判断,并给出相应的status

Throwable cancellationCause = context.cancellationCause();
if (cancellationCause == null) {
  return Status.CANCELLED;
}
if (cancellationCause instanceof TimeoutException) {
  return Status.DEADLINE_EXCEEDED
 .withDescription(cancellationCause.getMessage())
      .withCause(cancellationCause);
}
 
//Status.CANCELLED,DEADLINE_EXCEEDED   给出取消的status参数,返回给调用者
@Override
protected void sendCancel(Status reason) { 
  synchronized (lock) {
    if (cancelSent) {
      return;
 }
    cancelSent = true;
 if (pendingData != null) {
      // stream is pending.
 transport.removePendingStream(this); 把暂停的流给释放掉
 // release holding data, so they can be GCed or returned to pool earlier.
 requestHeaders = null;
 for (PendingData data : pendingData) {
        data.buffer.clear();
 }
      pendingData = null;
 transportReportStatus(reason, true, new Metadata());
 } else {
      // If pendingData is null, start must have already been called, which means synStream has
 // been called as well.
 transport.finishStream(id(), reason, ErrorCode.CANCEL); 结束传输
 }
  }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,616评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,020评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,078评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,040评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,154评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,265评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,298评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,072评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,491评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,795评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,970评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,654评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,272评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,985评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,815评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,852评论 2 351

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,644评论 18 139
  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,936评论 6 13
  • 这个世界就是都是一样的 没有什么是不一样的 差不多先生
    泰_3fa6阅读 141评论 1 1
  • 感赏儿子一天酷热下奔波的学习。 感赏儿子今天去体训了。 感赏儿子对暑假作业有所紧迫感,有计划地安排完成时间了。 投...
    玲03阅读 188评论 0 1
  • 父亲去世10年后,在我的“软硬兼施”下,母亲终于同意来郑州跟着我——她最小的女儿一起生活。这一年,母亲70岁,我4...
    若兰ZHOU阅读 233评论 2 0