前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >接口调用实现请求超时中断,你有几种方法?

接口调用实现请求超时中断,你有几种方法?

作者头像
春哥大魔王
发布2019-07-31 10:50:49
6K0
发布2019-07-31 10:50:49
举报

背景

在服务化系统中,对于上下游服务的依赖调用往往是通过RPC接口调用实现的,为了系统稳定性,防止被上游服务超时hang死,我们需要对接口调用设置超时,如果在设置的超时时间内没有响应,则需要提早中断该请求并返回。

比如下游接口对于我们的超时时间限制是150ms,因为业务特点原因,我们需要对上游服务某个接口调用设置50ms超时,如果在指定时间内没有返回,则返回降级数据。

超时中断

Future超时

说到超时中断很多人第一个想到的是Future中断。比如请求线程是一个tomcat线程池中的线程,可以通过线程池返回Future,可以轻松实现超时中断返回,这种方式也是我们使用比较多的方案,因为线程池并行调用在高并发场景下有很多的应用,所以直接借助Future方式中断是最先想到的方法。

如果有些场景不想额外引入线程池,又拿不到Future有什么其他方式吗?

线程中断

以前线程提供了Thread.stop,Thread.suspend,Thread,resume方法,但是这几个方法都已经废弃了。目前实现线程中断最先想到的就是interrupt()方法。

interrupt()方法并不是进行线程中断,而仅仅是通知线程你可以中断了,但是是否中断还是取决于线程的运行状态,由其自身决定。

比如调用一个线程的interrupt()之后,如果线程处于阻塞状态(包括:wait,sleep,join等方法),则线程会退出并返回InterruptedException异常,代码中catch这个异常后就可以继续处理了。

如果线程一直在执行没有处于阻塞,则不会中断线程。但是在RPC调用场景中,请求线程一般会处于阻塞状态等待数据,所以可以通过interrupt()方法执行中断。

知道了中断方法了,如何通过指定超时时间进行中断呢?

首先想到的是单独有一个延迟task专门去搞定线程中断的事情。

代码语言:javascript
复制
ScheduledFuture<?> f = executor.scheduleAtFixedRate(task,timeout,timeout,TimeUnit.MILLISECONDS);
代码语言:javascript
复制
Runnable task = new Runnable(){	@Override
	public void run(){		try{
			thread.interrupt();
			// 取消定时器任务
			f.cancel();
		} 		catch(Exception e){			logger.error("Failed while ticking TimerListener",e);
		}
	}
};

在进行rpc调用时,同时提交一个中断检测任务到ScheduledFuture中等待执行,如果在指定时间内rpc没有返回,则会触发延迟任务,执行请求线程的interrupt()方法,实现了请求线程的中断了,之后清除掉定时任务就OK了。

如果RPC调用在指定时间内返回,也需要清除定时任务,同时恢复请求线程中的中断标识,执行当前线程(即请求线程)的isInterrupted方法。

代码语言:javascript
复制
Thread.interrupted();

这种方式实现中断的问题是,在QPS很高情况下会存在额外性能损失,因为需要开一个任务线程池等待执行。

ReentrantLock.lock(),Condition.await,Condition.signalAll()

另一种方式是采用线程wait和notify方式。也是我比较喜欢的方式。

代码语言:javascript
复制
private final Lock lock = new ReentrantLock();
private final Condition done = lock.newCondition();
private volatile Object response;

提交请求同时提交检测任务:

代码语言:javascript
复制
    private void invokeTimeout(int timeout){
        if(timeout <= 0){
            timeout = 20;
        }

        // 检测服务提供方是否成功返回了调用结果
        if (!isDone()) {
            long start = System.currentTimeMillis();
            System.out.println("lock get message start");
            lock.lock();
            try {
                // 循环检测服务提供方是否成功返回了调用结果
                while (!isDone()) {
                    // 如果调用结果尚未返回,这里等待一段时间
                    System.out.println("lock get message wait");
                    done.await(timeout, TimeUnit.MILLISECONDS);
                    // 如果调用结果成功返回,或等待超时,此时跳出 while 循环,执行后续的逻辑
                    if (isDone() || System.currentTimeMillis() - start > timeout) {
                        break;
                    }
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                lock.unlock();
            }

            // 如果调用结果仍未返回,则抛出超时异常
            if (!isDone()) {
                throw new RuntimeException("timeout");
            }
        }

        System.out.println("lock get message finish");
    }

    private boolean isDone(){
        // 通过检测 response 字段为空与否,判断是否收到了调用结果
        return response != null;
    }
代码语言:javascript
复制
            // RPC-Invoke
            response = new Object();
            done.signalAll();

在RPC调用过程中,如果结果返回,发送signal(),通知await()同时赋值response,执行break返回。如果在指定await()时间内没有返回,同时response无值,则抛出RuntimeException业务进行捕获。

其他方式有哪些?

  • timingwheel,dubbo已经在用了
  • 定时通知+线程扫描
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-06-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 春哥talk 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • 超时中断
  • 其他方式有哪些?
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档