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

背景

在服务化系统中,对于上下游服务的依赖调用往往是通过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

### Java 中线程同步与互斥的多种实现方法 #### synchronized 关键字 `synchronized` 是一种基本的互斥实现方法,能够修饰方法或代码块,确保同一时刻只有一个线程能访问被保护的资源或执行特定代码段。当一个线程进入由 `synchronized` 修饰的方法或代码块时,会自动获得对象锁,在退出该区域时释放此锁。 ```java public class SynchronizedExample { private int count = 0; public synchronized void increment() { count++; } public static void main(String[] args) throws InterruptedException { SynchronizedExample example = new SynchronizedExample(); Runnable task = () -> { for (int i = 0; i < 1000; i++) { example.increment(); } }; Thread t1 = new Thread(task); Thread t2 = new Thread(task); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println("Final Count: " + example.count); } } ``` 这种方法简单易用,但是灵活性较低[^1]。 #### ReentrantLock 类 除了 `synchronized` 外,Java 还提供了 `ReentrantLock` 来实现更灵活的锁定机制。相比前者,后者允许显式地控制锁的行为,比如设置超时尝试获取锁、支持公平策略等特性。需要注意的是,使用此类需谨慎处理异常情况下的解锁逻辑以免造成死锁等问题。 ```java import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class LockExample { private final Lock lock = new ReentrantLock(true); // true 表示启用公平模式 private int value = 0; public void add(int num) { try { lock.lockInterruptibly(); // 可响应中断请求 value += num; } catch (InterruptedException e) { throw new RuntimeException(e.getMessage()); } finally { lock.unlock(); } } public static void main(String[] args) { LockExample le = new LockExample(); // 测试并发调用... } } ``` 这种方式虽然增加了编码难度,但在某些情况下可以带来更好的性能表现以及更多的功能选项[^4]。 #### 原子类(Atomic Classes) 对于简单的计数器或其他数值类型的更新操作来说,利用 `java.util.concurrent.atomic` 包中的原子类往往是最优的选择之一。这类工具内部实现了高效的无锁算法,从而避免了传统加锁所带来的开销并提高了程序的整体吞吐量。 ```java import java.util.concurrent.atomic.AtomicInteger; public class AtomicExample { private AtomicInteger atomicInt = new AtomicInteger(0); public void increase() { while (true) { int current = atomicInt.get(); boolean successful = atomicInt.compareAndSet(current, current + 1); if (successful) break; } } public static void main(String[] args) { AtomicExample ae = new AtomicExample(); // 并发测试... } } ``` 上述三种方案各有千秋,开发者应根据实际需求选择最适合的一种或几种组合起来解决问题[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值