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

背景

在服务化系统中,对于上下游服务的依赖调用往往是通过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专门去搞定线程中断的事情。

ScheduledFuture<?> f = executor.scheduleAtFixedRate(task,timeout,timeout,TimeUnit.MILLISECONDS);
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方法。

Thread.interrupted();

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

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

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

private final Lock lock = new ReentrantLock();
private final Condition done = lock.newCondition();
private volatile Object response;

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

    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;
    }
            // RPC-Invoke
            response = new Object();
            done.signalAll();

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

其他方式有哪些?

  • timingwheel,dubbo已经在用了
  • 定时通知+线程扫描

如果你有更好的方式欢迎留言赐教。

转载于:https://my.oschina.net/u/1000241/blog/3067239

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值