阐述Dubbo接口调用的超时机制

1 Dubbo超时机制概述

在Dubbo中可以设置服务的超时时间,这个超时时间指服务消费方调用Dubbo接口的超时时间

当调用Dubbo接口的耗时小于等于超时时间,则调用正常执行完成;当调用Dubbo接口的耗时大于

超时时间,则抛出接口超时异常。

注意这个超时时间对服务提供方接口的执行无任何影响。服务提供方接口执行耗时即使超过了这

个超时时间,接口依旧正常执行。

2 设置超时时间的目的

设置Dubbo接口调用的超时时间的主要目的是确保服务消费方系统的稳定性和可用性

如果没有设置超时时间,那么服务消费方发起远程调用后可能会一直等待,直到服务提供方返回结

果,这可能导致调用线程阻塞,进而影响整个系统的性能和稳定性。

通过设置超时时间,可以在服务调用超过预定时间后自动中断调用,释放被阻塞的线程资源,避免

系统因为长时间的等待而陷入僵局。同时,这也可以让调用方有机会进行错误处理,比如重试调

用、记录日志、触发报警等,以应对服务调用失败的情况。

3 设置超时时间和配置的优先级

3.1 设置超时时间

使用 timeout 参数进行设置。可以在服务消费方设置,也可以在服务提供方设置。设置方式主要有以下几种。

(1)全局超时设置

设置全局的超时时间。这通常是在dubbo.properties文件中完成的。举例如下。

# 服务提供端(ms)
dubbo.provider.timeout=1000

或

# 服务消费端(ms)
dubbo.consumer.timeout=1000

(2)局部超时设置

针对具体的服务或方法进行超时设置。举例如下。

<dubbo:service interface="com.example.DemoService" ref="demoService" timeout="10000"/>

或

<dubbo:reference id="demoService" interface="com.example.DemoService" timeout="10000"/>

(3)注解方式设置

@DubboService(timeout = 10000)  
public class TestServiceImpl implements TestService {  
    // ...  
}  
  
或

@DubboReference(timeout = 10000)  
TestService testService;

3.2 配置的优先级

Dubbo的超时时间生效的优先级为:消费者方法级配置 > 提供者方法级配置 > 消费者接口级配置 >

提供者接口级配置 > 消费者全局配置 > 提供者全局配置。消费者未设置超时时间,则直接使用提

供者设置的超时时间。

举例:服务提供者设置调用订单接口的超时时间为10s,服务消费者a设置调用该接口的超时时间

为12s,服务消费者b未设置调用该接口的超时时间。则服务消费者a调用该接口的超时时间为

12s,服务消费者调用该接口的超时时间为10s。

4 超时机制源码解析

消费端将通过DubboInvoker的doInvoke()方法发起远程调用。

    protected Result doInvoke(final Invocation invocation) throws Throwable {
        RpcInvocation inv = (RpcInvocation) invocation;
        final String methodName = RpcUtils.getMethodName(invocation);
        inv.setAttachment(PATH_KEY, getUrl().getPath());
        inv.setAttachment(VERSION_KEY, version);

        ExchangeClient currentClient;
        List<? extends ExchangeClient> exchangeClients = clientsProvider.getClients();
        if (exchangeClients.size() == 1) {
            currentClient = exchangeClients.get(0);
        } else {
            currentClient = exchangeClients.get(index.getAndIncrement() % exchangeClients.size());
        }
        try {
            boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);
            
            // 获取配置的接口超时时间
            int timeout = RpcUtils.calculateTimeout(getUrl(), invocation, methodName, DEFAULT_TIMEOUT);
            if (timeout <= 0) {
                return AsyncRpcResult.newDefaultAsyncResult(new RpcException(RpcException.TIMEOUT_TERMINATE,
                    "No time left for making the following call: " + invocation.getServiceName() + "."
                        + RpcUtils.getMethodName(invocation) + ", terminate directly."), invocation);
            }

            invocation.setAttachment(TIMEOUT_KEY, String.valueOf(timeout));

            Integer payload = getUrl().getParameter(PAYLOAD, Integer.class);

            Request request = new Request();
            if (payload != null) {
                request.setPayload(payload);
            }
            request.setData(inv);
            request.setVersion(Version.getProtocolVersion());

            if (isOneway) {
                boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);
                request.setTwoWay(false);
                currentClient.send(request, isSent);
                return AsyncRpcResult.newDefaultAsyncResult(invocation);
            } else {
                request.setTwoWay(true);
                ExecutorService executor = getCallbackExecutor(getUrl(), inv);
                // 发起请求
                CompletableFuture<AppResponse> appResponseFuture =
                    currentClient.request(request, timeout, executor).thenApply(AppResponse.class::cast);
                // save for 2.6.x compatibility, for example, TraceFilter in Zipkin uses com.alibaba.xxx.FutureAdapter
                if (setFutureWhenSync || ((RpcInvocation) invocation).getInvokeMode() != InvokeMode.SYNC) {
                    FutureContext.getContext().setCompatibleFuture(appResponseFuture);
                }
                AsyncRpcResult result = new AsyncRpcResult(appResponseFuture, inv);
                result.setExecutor(executor);
                return result;
            }
        } catch (TimeoutException e) {
            throw new RpcException(RpcException.TIMEOUT_EXCEPTION, "Invoke remote method timeout. method: " + RpcUtils.getMethodName(invocation) + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
        } catch (RemotingException e) {
            String remoteExpMsg = "Failed to invoke remote method: " + RpcUtils.getMethodName(invocation) + ", provider: " + getUrl() + ", cause: " + e.getMessage();
            if (e.getCause() instanceof IOException && e.getCause().getCause() instanceof SerializationException) {
                throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, remoteExpMsg, e);
            } else {
                throw new RpcException(RpcException.NETWORK_EXCEPTION, remoteExpMsg, e);
            }
        }
    }

内部调用HeaderExchangeChannel#request发起请求。其中会创建DefaultFuture实例,提交接口

请求超时检测任务。

    public CompletableFuture<Object> request(Object request, int timeout, ExecutorService executor) throws RemotingException {
        if (closed) {
            throw new RemotingException(this.getLocalAddress(), null, "Failed to send request " + request + ", cause: The channel " + this + " is closed!");
        }
        Request req;
        if (request instanceof Request) {
            req = (Request) request;
        } else {
            // create request.
            req = new Request();
            req.setVersion(Version.getProtocolVersion());
            req.setTwoWay(true);
            req.setData(request);
        }
        // 创建DefaultFuture实例,提交请求超时检测任务
        DefaultFuture future = DefaultFuture.newFuture(channel, req, timeout, executor);
        try {
            channel.send(req);
        } catch (RemotingException e) {
            future.cancel();
            throw e;
        }
        return future;
    }


    public static DefaultFuture newFuture(Channel channel, Request request, int timeout, ExecutorService executor) {
        final DefaultFuture future = new DefaultFuture(channel, request, timeout);
        future.setExecutor(executor);
        // timeout check 提交超时检测任务
        timeoutCheck(future);
        return future;
    }

    /**
     * check time out of the future
     */
    private static void timeoutCheck(DefaultFuture future) {
        TimeoutCheckTask task = new TimeoutCheckTask(future.getId());
        future.timeoutCheckTask = TIME_OUT_TIMER.get().newTimeout(task, future.getTimeout(), TimeUnit.MILLISECONDS);
    }

5 接口超时的数据一致性保证

当服务消费端调用接口超时,但是服务提供端接口实际是正常执行完成时,可能导致消费端和提供

端数据不一致。可以考虑使用以下措施来保证数据的最终不一致:

(1)消费端进行调用重试,服务端接口确保接口的幂等性。如果之前已执行成功,返回执行成功的结果。

(2)消费端增加数据检查机制,确保数据的最终一致性。

(3)使用MQ代替高耗时接口的调用 

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值