SOFABolt 源碼分析21 - 超時(shí)與快速失敗機(jī)制的設(shè)計(jì)

SOFABolt 的超時(shí)分為兩種:連接超時(shí)和調(diào)用超時(shí)。

  • 連接超時(shí)
  • 僅客戶端可設(shè)置诅岩,因?yàn)橹挥锌蛻舳藭?huì)建連
  • 連接超時(shí)時(shí)間的設(shè)置只作用于建連的時(shí)候讳苦,有兩種方式可以指定 - addr 拼接屬性參數(shù) 或者 url 設(shè)置超時(shí)時(shí)間屬性
  • 連接超時(shí)機(jī)制底層實(shí)際上使用的 Netty 的超時(shí)參數(shù)設(shè)置方式
  • 調(diào)用超時(shí):調(diào)用端無(wú)論是 RpcClient 還是 RpcServer带膜,除了 oneway 模式下,剩余的三種調(diào)用模式 sync / future / callback 的方法都提供了一個(gè)必填參數(shù) int timeoutMillis鸳谜,該參數(shù)用來(lái)指定調(diào)用超時(shí)時(shí)間
    調(diào)用超時(shí)時(shí)間對(duì)于同步調(diào)用和異步調(diào)用不同
  • sync:使用 CountDownLatch # boolean await(long timeout, TimeUnit unit) 實(shí)現(xiàn)當(dāng)前線程的實(shí)現(xiàn)阻塞等待
  • future 和 callback:使用 Netty 的 HashedWheelTimer 實(shí)現(xiàn) - 在發(fā)出請(qǐng)求的時(shí)候創(chuàng)建 timeoutMillis 時(shí)間的超時(shí)任務(wù) task膝藕,當(dāng)在 timeoutMillis 時(shí)間內(nèi)沒(méi)有返回響應(yīng),則執(zhí)行 task 中的內(nèi)容(構(gòu)造超時(shí)響應(yīng)咐扭,返回給調(diào)用程序)芭挽;否則,取消 task

fail-fast 機(jī)制

  • 僅用在被調(diào)用端草描,即請(qǐng)求的處理端
  • fail-fast 機(jī)制指的是在序列化全部?jī)?nèi)容之前览绿,會(huì)做一個(gè)判斷策严,如果處理當(dāng)前請(qǐng)求的 UserProcessor 開(kāi)啟了 fail-fast 功能穗慕,并且此時(shí)已經(jīng)超時(shí),并且不是 oneway 模式(oneway 沒(méi)有超時(shí)概念)妻导,則直接丟棄該請(qǐng)求逛绵,不再進(jìn)行后續(xù)的序列化和業(yè)務(wù)邏輯處理操作
  • fail-fast 機(jī)制是由 UserProcessor#timeoutDiscard() 指定的,如果返回 true倔韭,則打開(kāi) fail-fast术浪,默認(rèn)情況下 fail-fast 機(jī)制是打開(kāi)的;如果想關(guān)閉該功能寿酌,手動(dòng)覆蓋 UserProcessor#timeoutDiscard() 方法即可胰苏,直接返回 false,之后是否超時(shí)就從 DefaultBizContext#isRequestTimeout() 進(jìn)行判斷

一醇疼、連接超時(shí)機(jī)制

默認(rèn)情況下硕并,連接超時(shí)時(shí)間由RpcConfigs.CONNECT_TIMEOUT_KEY來(lái)指定,默認(rèn)為 1000ms秧荆,可以通過(guò)在 addr 上拼接參數(shù)或者在 url 上設(shè)置值來(lái)指定連接超時(shí)時(shí)間倔毙。

使用姿勢(shì)

// addr 拼接方式
String addr = "127.0.0.1:8888?_CONNECTTIMEOUT=3000";

// url 設(shè)置值的方式
Url url = new Url("127.0.0.1",8888);
url.setConnNum(1);
url.setConnectTimeout(3000);

源碼分析

以 url 方式為例,addr 只是將 _CONNECTTIMEOUT=3000 properties 屬性設(shè)置到了url.connectTimeout 中乙濒。后續(xù)還是使用 url 方式進(jìn)行調(diào)用陕赃。在調(diào)用的過(guò)程中會(huì)獲取或者創(chuàng)建連接,連接超時(shí)作用于創(chuàng)建連接颁股。

public abstract class AbstractConnectionFactory implements ConnectionFactory {
    @Override
    public Connection createConnection(Url url) throws Exception {
        // 創(chuàng)建連接
        Channel channel = doCreateConnection(url.getIp(), url.getPort(), url.getConnectTimeout());
        ...
        return conn;
    }

    protected Channel doCreateConnection(String targetIP, int targetPort, int connectTimeout) {
        // 預(yù)處理 connectTimeout么库,最小為 1000
        connectTimeout = Math.max(connectTimeout, 1000);
        // 設(shè)置 Netty 的連接超時(shí)時(shí)間屬性
        bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeout);
        // 進(jìn)行連接
        ChannelFuture future = bootstrap.connect(new InetSocketAddress(targetIP, targetPort));

        future.awaitUninterruptibly();
        ...
        return future.channel();
    }
}

二、調(diào)用超時(shí)機(jī)制(調(diào)用端)

調(diào)用超時(shí)時(shí)間對(duì)于同步調(diào)用和異步調(diào)用不同

  • sync:使用 CountDownLatch # boolean await(long timeout, TimeUnit unit) 實(shí)現(xiàn)當(dāng)前線程的實(shí)現(xiàn)阻塞等待
  • future 和 callback:使用 Netty 的 HashedWheelTimer 實(shí)現(xiàn)

調(diào)用端無(wú)論是 RpcClient 還是 RpcServer甘有,除了 oneway 模式下廊散,剩余的三種調(diào)用模式 sync / future / callback 的方法都提供了一個(gè)必填參數(shù) int timeoutMillis,該參數(shù)用來(lái)指定調(diào)用超時(shí)時(shí)間

Object invokeSync(String addr, Object request, int timeoutMillis)
RpcResponseFuture invokeWithFuture(String addr, Object request, int timeoutMillis)
void invokeWithCallback(String addr, Object request, InvokeCallback invokeCallback, int timeoutMillis)

同步方式

image.png
============================ class RpcRemoting extends BaseRemoting ============================
    public Object invokeSync(Connection conn, Object request, InvokeContext invokeContext, int timeoutMillis) {
        // 創(chuàng)建請(qǐng)求:設(shè)置 int timeout = timeoutMillis(默認(rèn)為-1梧疲,表示永不超時(shí))
        // 該 timeout 參數(shù)會(huì)用在被調(diào)用端判斷是否超時(shí)允睹,進(jìn)而做 fail-fast 操作
        RemotingCommand requestCommand = toRemotingCommand(request, conn, invokeContext, timeoutMillis);
        ...
        // 發(fā)起請(qǐng)求
        ResponseCommand responseCommand = (ResponseCommand) super.invokeSync(conn, requestCommand, timeoutMillis);
        ...
        // 解析響應(yīng)
        Object responseObject = RpcResponseResolver.resolveResponseObject(responseCommand, RemotingUtil.parseRemoteAddress(conn.getChannel()));
        return responseObject;
    }

看下解析響應(yīng)流程
    public static Object resolveResponseObject(ResponseCommand responseCommand, String addr) throws RemotingException {
        ... 預(yù)處理響應(yīng)运准,服務(wù)服務(wù)端返回異常信息,直接拋出異常
        preProcess(responseCommand, addr);
        if (responseCommand.getResponseStatus() == ResponseStatus.SUCCESS) {
            return toResponseObject(responseCommand);
        } else {
            ...
        }
    }

    private static void preProcess(ResponseCommand responseCommand, String addr)  throws RemotingException {
        RemotingException e = null;
        // responseCommand == null 超時(shí)
        if (responseCommand == null) {
            e = new InvokeTimeoutException(msg);
        } else {
            switch (responseCommand.getResponseStatus()) {
                case TIMEOUT:
                    e = new InvokeTimeoutException(msg);
                    break;
                ...
            }
        }
        // 直接拋出超時(shí)異常
        if (null != e) {
            throw e;
        }
    }

============================ BaseRemoting ============================
    protected RemotingCommand invokeSync(Connection conn, RemotingCommand request, int timeoutMillis) {
        // 創(chuàng)建 InvokeFuture
        final InvokeFuture future = createInvokeFuture(request, request.getInvokeContext());
        // 添加 InvokeFuture 到 Connection
        conn.addInvokeFuture(future);
        // 使用 Netty 發(fā)送請(qǐng)求
        conn.getChannel().writeAndFlush(request)
        // 等待 timeoutMillis缭受,如果超時(shí)胁澳,不等待結(jié)果直接返回 response,此時(shí)的 response == null米者,實(shí)際代碼如下
        //    public ResponseCommand waitResponse(long timeoutMillis) throws InterruptedException {
        //        // 如果超時(shí)韭畸,知己返回false,this.responseCommand 直接返回 null
        //        this.countDownLatch.await(timeoutMillis, TimeUnit.MILLISECONDS);
        //        return this.responseCommand;
        //    }
        RemotingCommand response = future.waitResponse(timeoutMillis);
        // response == null 客戶端自己創(chuàng)建超時(shí)響應(yīng)
        if (response == null) {
            conn.removeInvokeFuture(request.getId());
            response = this.commandFactory.createTimeoutResponse(conn.getRemoteAddress());
        }
        return response;
    }

注意:RpcRequestCommand 中的 timeout 參數(shù)會(huì)用在被調(diào)用端判斷是否超時(shí)蔓搞,進(jìn)而做 fail-fast 操作

HashWheelTimer 簡(jiǎn)介

future 和 callback 異步模式的超時(shí)機(jī)制都使用了 Netty 的 HashWheelTimer 機(jī)制胰丁。

image.png

Netty 的 HashWheelTimer 是一個(gè)環(huán)形結(jié)構(gòu),假設(shè)有 8 個(gè)格子(格子數(shù)可以通過(guò) ticksPerWheel 構(gòu)造器參數(shù)指定喂分,默認(rèn)為 512)锦庸,一個(gè)格子代表一段時(shí)間(越短 Timer 精度越高,每個(gè)格子代表的時(shí)間可以通過(guò) tickDuration 指定蒲祈,默認(rèn)為 100ms甘萧,SOFABolt 指定為 10ms)。

以上圖為例梆掸,假設(shè)一個(gè)格子是 1s扬卷,則整個(gè) wheel 能表示的時(shí)間段為 8s,假如當(dāng)前指針指向 2酸钦,此時(shí)需要調(diào)度一個(gè) 3s 后執(zhí)行的任務(wù)怪得,顯然應(yīng)該加入到 (2+3=5) 的方格中的鏈表中,并且 round = 0卑硫,指針再走 3 次就可以執(zhí)行了徒恋;如果任務(wù)要在 10s 后執(zhí)行,應(yīng)該等指針走完一個(gè) round 零 2 格再執(zhí)行拔恰,因此應(yīng)放入4((2+10)%8=4)的方格中的鏈表中因谎,其 round 設(shè)為 1。檢查到期任務(wù)時(shí)只執(zhí)行 round 為 0 的颜懊,格子上其他任務(wù)的 round 減 1财岔。

Netty 中有一條線程控制 HashWheelTimer 中的指針每隔 tickDuration 移動(dòng)到下一個(gè)格子,執(zhí)行其中 round 為 0 并且不在 cancel 隊(duì)列的任務(wù)河爹,該格子上的其他任務(wù)的 round 減 1匠璧。

當(dāng)取消一個(gè)超時(shí)任務(wù)時(shí),直接將該任務(wù)添加到 cancel 隊(duì)列咸这,就不會(huì)被執(zhí)行了夷恍。

最后看看 HashWheelTimer 在 SOFABolt 中的最佳實(shí)踐。

public class TimerHolder {
    // 每格 10 ms
    private final static long defaultTickDuration = 10;
    // HashedWheelTimer 單例
    private static class DefaultInstance {
        static final Timer INSTANCE = new HashedWheelTimer(new NamedThreadFactory( "DefaultTimer" + defaultTickDuration, true), defaultTickDuration, TimeUnit.MILLISECONDS);
    }

    private TimerHolder() {
    }

    public static Timer getTimer() {
        return DefaultInstance.INSTANCE;
    }
}

// 使用見(jiàn)下邊的分析
Timeout timeout = TimerHolder.getTimer().newTimeout(new TimerTask() {
    @Override
    public void run(Timeout timeout) throws Exception {
        InvokeFuture future = conn.removeInvokeFuture(request.getId());
        future.putResponse(commandFactory.createTimeoutResponse(conn.getRemoteAddress()));
        future.tryAsyncExecuteInvokeCallbackAbnormally();
    }
}, timeoutMillis, TimeUnit.MILLISECONDS);

異步方式(future 模式)

image.png

future 的阻塞點(diǎn)在 RpcResponseFuture.get()操作上媳维,實(shí)際上同同步一樣酿雪,底層也是阻塞在 countDownLatch.await() 上

============================ class RpcRemoting extends BaseRemoting ==================
    public RpcResponseFuture invokeWithFuture(Connection conn, Object request, InvokeContext invokeContext, int timeoutMillis) {
        // 創(chuàng)建請(qǐng)求:設(shè)置 int timeout = timeoutMillis(默認(rèn)為-1遏暴,表示永不超時(shí))
        // 該 timeout 參數(shù)會(huì)用在被調(diào)用端判斷是否超時(shí),進(jìn)而做 fail-fast 操作
        RemotingCommand requestCommand = toRemotingCommand(request, conn, invokeContext, timeoutMillis);
        ...
        // 發(fā)起請(qǐng)求
        InvokeFuture future = super.invokeWithFuture(conn, requestCommand, timeoutMillis);
        // 直接返回響應(yīng)指黎,在 get 的時(shí)候朋凉,依然使用 RpcResponseResolver.resolveResponseObject 解析響應(yīng),如果超時(shí)醋安,直接拋出超時(shí)異常
        return new RpcResponseFuture(RemotingUtil.parseRemoteAddress(conn.getChannel()), future);
    }

============================ BaseRemoting ============================
    protected InvokeFuture invokeWithFuture(Connection conn, RemotingCommand request, int timeoutMillis) {
        // 創(chuàng)建 InvokeFuture
        final InvokeFuture future = createInvokeFuture(request, request.getInvokeContext());
        // 添加 InvokeFuture 到 Connection
        conn.addInvokeFuture(future);
        try {
            // 創(chuàng)建超時(shí)任務(wù)杂彭,添加到 Netty 的 hashWheel 中,在 timeoutMillis 之后如果該超時(shí)任務(wù)沒(méi)有被取消吓揪,則執(zhí)行其 run 方法
            // 進(jìn)而創(chuàng)建超時(shí)響應(yīng)
            Timeout timeout = TimerHolder.getTimer().newTimeout(new TimerTask() {
                public void run(Timeout timeout) throws Exception {
                    InvokeFuture future = conn.removeInvokeFuture(request.getId());
                    // 創(chuàng)建超時(shí)響應(yīng) + 設(shè)置超時(shí)響應(yīng) + 喚醒阻塞線程
                    future.putResponse(commandFactory.createTimeoutResponse(conn.getRemoteAddress()));
                }
            }, timeoutMillis, TimeUnit.MILLISECONDS);
            // 將 Timeout 設(shè)置到 InvokeFuture 中亲怠,后續(xù)可以從 InvokeFuture 中取出 Timeout,執(zhí)行取消超時(shí)任務(wù)的操作
            // 超時(shí)任務(wù)的取消有兩種情況:正常返回響應(yīng) + 如下發(fā)生異常
            future.addTimeout(timeout);
            conn.getChannel().writeAndFlush(request);
        } catch (Exception e) {
            InvokeFuture f = conn.removeInvokeFuture(request.getId());
            // 如果發(fā)生異常柠辞,取消超時(shí)任務(wù)
            f.cancelTimeout();
            f.putResponse(commandFactory.createSendFailedResponse(conn.getRemoteAddress(), e));
        }
        return future;
    }

============================ RpcResponseProcessor ============================
    public void doProcess(RemotingContext ctx, RemotingCommand cmd) {
        // 獲取 Connection
        Connection conn = ctx.getChannelContext().channel().attr(Connection.CONNECTION).get();
        // 獲取 InvokeFuture
        InvokeFuture future = conn.removeInvokeFuture(cmd.getId());
        // 設(shè)置響應(yīng)团秽,喚醒阻塞線程
        future.putResponse(cmd);
        // 取消 timeout task
        future.cancelTimeout();
        // 回調(diào) callback
        future.executeInvokeCallback();
    }

異步方式(callback 模式)

image.png
============================ class RpcRemoting extends BaseRemoting ==================
    public void invokeWithCallback(Connection conn, Object request, InvokeContext invokeContext, InvokeCallback invokeCallback, int timeoutMillis) {
        // 創(chuàng)建請(qǐng)求:設(shè)置 int timeout = timeoutMillis(默認(rèn)為-1,表示永不超時(shí))
        // 該 timeout 參數(shù)會(huì)用在被調(diào)用端判斷是否超時(shí)钾腺,進(jìn)而做 fail-fast 操作
        RemotingCommand requestCommand = toRemotingCommand(request, conn, invokeContext, timeoutMillis);
        ...
        // 發(fā)出請(qǐng)求
        super.invokeWithCallback(conn, requestCommand, invokeCallback, timeoutMillis);
    }

============================ BaseRemoting ============================
       protected void invokeWithCallback(Connection conn, RemotingCommand request, InvokeCallback invokeCallback, int timeoutMillis) {
        // 創(chuàng)建 InvokeFuture
        InvokeFuture future = createInvokeFuture(conn, request, request.getInvokeContext(), invokeCallback);
        // 添加 InvokeFuture 到 Connection
        conn.addInvokeFuture(future);

        try {
            // 創(chuàng)建超時(shí)任務(wù)徙垫,添加到 Netty 的 hashWheel 中讥裤,在 timeoutMillis 之后如果該超時(shí)任務(wù)沒(méi)有被取消放棒,則執(zhí)行其 run 方法
            // 進(jìn)而創(chuàng)建超時(shí)響應(yīng)
            Timeout timeout = TimerHolder.getTimer().newTimeout(new TimerTask() {
                @Override
                public void run(Timeout timeout) throws Exception {
                    InvokeFuture future = conn.removeInvokeFuture(request.getId());
                    // 創(chuàng)建超時(shí)響應(yīng) + 設(shè)置超時(shí)響應(yīng)
                    future.putResponse(commandFactory.createTimeoutResponse(conn.getRemoteAddress()));
                    // 想較于 future 而言,多出這一步己英,執(zhí)行異臣涿回調(diào) callback.onException(e)
                    future.tryAsyncExecuteInvokeCallbackAbnormally();
                }

            }, timeoutMillis, TimeUnit.MILLISECONDS);
            // 將 Timeout 設(shè)置到 InvokeFuture 中,后續(xù)可以從 InvokeFuture 中取出 Timeout损肛,執(zhí)行取消超時(shí)任務(wù)的操作
            // 超時(shí)任務(wù)的取消有兩種情況:正常返回響應(yīng) + 如下發(fā)生異常
            future.addTimeout(timeout);
            conn.getChannel().writeAndFlush(request);
        } catch (Exception e) {
            InvokeFuture f = conn.removeInvokeFuture(request.getId());
            // 如果發(fā)生異常厢破,取消超時(shí)任務(wù)
            f.cancelTimeout();
            f.putResponse(commandFactory.createSendFailedResponse(conn.getRemoteAddress(), e));
            // 想較于 future 而言,多出這一步治拿,執(zhí)行異衬幔回調(diào) callback.onException(e)
            f.tryAsyncExecuteInvokeCallbackAbnormally();
        }
    }

============================ RpcResponseProcessor ============================
    // 這里與 future 的處理方式一樣
    public void doProcess(RemotingContext ctx, RemotingCommand cmd) {
        // 獲取 Connection
        Connection conn = ctx.getChannelContext().channel().attr(Connection.CONNECTION).get();
        // 獲取 InvokeFuture
        InvokeFuture future = conn.removeInvokeFuture(cmd.getId());
        // 設(shè)置響應(yīng),喚醒阻塞線程
        future.putResponse(cmd);
        // 取消 timeout task
        future.cancelTimeout();
        // 回調(diào) callback
        future.executeInvokeCallback();
    }

心跳請(qǐng)求:RpcHeartbeatTrigger#heartbeatTriggered(final ChannelHandlerContext ctx) 在發(fā)送了心跳請(qǐng)求之后劫谅,也是用了 TimeOut 做超時(shí)任務(wù)见坑,與 Callback 使用一致,不再分析捏检。

三荞驴、快速失敗機(jī)制(被調(diào)用端)

以上都是在分析調(diào)用端的超時(shí)邏輯彬犯,對(duì)于三種調(diào)用方式充尉,被調(diào)用端的處理都是一套邏輯。

public class RpcRequestCommand extends RequestCommand {
    // 調(diào)用超時(shí)時(shí)間觉啊,調(diào)用端進(jìn)行設(shè)置能犯,默認(rèn)值為 -1鲫骗,代表永不超時(shí)
    private int timeout = -1;
    // 不會(huì)序列化到對(duì)端犬耻,該值會(huì)在被調(diào)用端的解碼器收到該消息時(shí),設(shè)置為收到的當(dāng)前時(shí)間
    private transient long arriveTime = -1;
}

public class RpcCommandDecoder implements CommandDecoder {
    @Override
    public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        ...
        RequestCommand command;
        if (cmdCode == CommandCode.HEARTBEAT_VALUE) {
            // 如果是心跳請(qǐng)求消息执泰,直接創(chuàng)建一個(gè) HeartbeatCommand
            command = new HeartbeatCommand();
        } else {
            // 如果是正常請(qǐng)求香追,創(chuàng)建一個(gè) RpcRequestCommand
            command = createRequestCommand(cmdCode);
        }
        ...
        out.add(command);
    }

    private RpcRequestCommand createRequestCommand(short cmdCode) {
        RpcRequestCommand command = new RpcRequestCommand();
        ...
        // 設(shè)置請(qǐng)求到達(dá)時(shí)間
        command.setArriveTime(System.currentTimeMillis());
        return command;
    }
}

public class RpcRequestProcessor {
    public void process(RemotingContext ctx, RpcRequestCommand cmd, ExecutorService defaultExecutor) {
        // 反序列化 className
        if (!deserializeRequestCommand(ctx, cmd, RpcDeserializeLevel.DESERIALIZE_CLAZZ)) {
            return;
        }
        // 獲取 userProcessor
        UserProcessor userProcessor = ctx.getUserProcessor(cmd.getRequestClass());

        // 將 userProcessor.timeoutDiscard() 設(shè)置到 RemotingContext 中
        // userProcessor.timeoutDiscard() 用于 fail-fast,該值默認(rèn)為 true坦胶,如果要關(guān)閉 fail-fast 功能透典,需要手動(dòng)覆蓋 UserProcessor#timeoutDiscard()
        ctx.setTimeoutDiscard(userProcessor.timeoutDiscard());

        ... 不管是 IO 線程執(zhí)行還是業(yè)務(wù)線程池執(zhí)行
        // ProcessTask.run() 調(diào)用 doProcess()
        executor.execute(new ProcessTask(ctx, cmd));
    }

    public void doProcess(final RemotingContext ctx, RpcRequestCommand cmd) throws Exception {
        long currentTimestamp = System.currentTimeMillis();
        // 設(shè)置超時(shí)時(shí)間 timeout 與到達(dá)時(shí)間 arriveTime 到 RemotingContext 中,為后續(xù)計(jì)算是否超時(shí)(ctx.isRequestTimeout())做準(zhǔn)備
        preProcessRemotingContext(ctx, cmd, currentTimestamp);
        // 如果開(kāi)啟了 fail-fast 并且 (System.currentTimeMillis() - this.arriveTimestamp) > this.timeout 確實(shí)超時(shí)顿苇,直接返回
        // 不再進(jìn)行后續(xù)的反序列化和業(yè)務(wù)邏輯處理
        if (ctx.isTimeoutDiscard() && ctx.isRequestTimeout()) {
            return;// then, discard this request
        }
        // decode request all
        if (!deserializeRequestCommand(ctx, cmd, RpcDeserializeLevel.DESERIALIZE_ALL)) {
            return;
        }
        // 派發(fā)請(qǐng)求峭咒,做真正的業(yè)務(wù)邏輯處理
        dispatchToUserProcessor(ctx, cmd);
    }

    private void preProcessRemotingContext(RemotingContext ctx, RpcRequestCommand cmd, long currentTimestamp) {
        // 將 RpcRequestCommand.getArriveTime()(該值在解碼器中進(jìn)行設(shè)置)設(shè)置到 RemotingContext 中
        ctx.setArriveTimestamp(cmd.getArriveTime());
        // 將 RpcRequestCommand.getTimeout() 設(shè)置到 RemotingContext 中
        ctx.setTimeout(cmd.getTimeout());
        ...
    }

    // RemotingContext#isRequestTimeout()
    public boolean isRequestTimeout() {
        // timeout = -1 表示永不超時(shí)
        // oneway 沒(méi)有超時(shí)概念
        // 是否超時(shí)是按照當(dāng)前時(shí)間減去響應(yīng)達(dá)到時(shí)間,而非被調(diào)用端的當(dāng)前時(shí)間 - 調(diào)用端的請(qǐng)求發(fā)送時(shí)間(這樣計(jì)算就是使用了兩個(gè)機(jī)器的時(shí)鐘纪岁,跨機(jī)器的時(shí)鐘會(huì)有時(shí)鐘差)
        if (this.timeout > 0 
                && (this.rpcCommandType != RpcCommandType.REQUEST_ONEWAY)
                && (System.currentTimeMillis() - this.arriveTimestamp) > this.timeout) {
            return true;
        }
        return false;
    }
}

注意

  • 是否超時(shí)是按照當(dāng)前時(shí)間減去響應(yīng)達(dá)到時(shí)間凑队,而非被調(diào)用端的當(dāng)前時(shí)間 - 調(diào)用端的請(qǐng)求發(fā)送時(shí)間(這樣計(jì)算就是使用了兩個(gè)機(jī)器的時(shí)鐘,跨機(jī)器的時(shí)鐘會(huì)有時(shí)鐘差)
  • fail-fast 機(jī)制是由 UserProcessor#timeoutDiscard() 指定的幔翰,如果返回 true漩氨,則打開(kāi) fail-fast,默認(rèn)情況下 fail-fast 機(jī)制是打開(kāi)的遗增;如果想關(guān)閉該功能叫惊,手動(dòng)覆蓋 UserProcessor#timeoutDiscard() 方法即可,直接返回 false做修,之后是否超時(shí)就從 DefaultBizContext#isRequestTimeout() 進(jìn)行判斷
DefaultBizContext#isRequestTimeout() 實(shí)際上還是調(diào)用了 RemotingContext#isRequestTimeout()

    private RemotingContext remotingCtx;
    @Override
    public boolean isRequestTimeout() {
        return this.remotingCtx.isRequestTimeout();
    }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末霍狰,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子饰及,更是在濱河造成了極大的恐慌蔗坯,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件燎含,死亡現(xiàn)場(chǎng)離奇詭異宾濒,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)屏箍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門绘梦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人铣除,你說(shuō)我怎么就攤上這事谚咬。” “怎么了尚粘?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵择卦,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng)秉继,這世上最難降的妖魔是什么祈噪? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮尚辑,結(jié)果婚禮上辑鲤,老公的妹妹穿的比我還像新娘。我一直安慰自己杠茬,他們只是感情好月褥,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著瓢喉,像睡著了一般宁赤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上栓票,一...
    開(kāi)封第一講書(shū)人閱讀 51,165評(píng)論 1 299
  • 那天决左,我揣著相機(jī)與錄音,去河邊找鬼走贪。 笑死佛猛,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的坠狡。 我是一名探鬼主播继找,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼擦秽!你這毒婦竟也來(lái)了码荔?” 一聲冷哼從身側(cè)響起漩勤,我...
    開(kāi)封第一講書(shū)人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤感挥,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后越败,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體触幼,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年究飞,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了置谦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡亿傅,死狀恐怖媒峡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情葵擎,我是刑警寧澤谅阿,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響签餐,放射性物質(zhì)發(fā)生泄漏寓涨。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一氯檐、第九天 我趴在偏房一處隱蔽的房頂上張望戒良。 院中可真熱鬧,春花似錦冠摄、人聲如沸糯崎。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)拇颅。三九已至,卻和暖如春乔询,著一層夾襖步出監(jiān)牢的瞬間樟插,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工竿刁, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留黄锤,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓食拜,卻偏偏與公主長(zhǎng)得像鸵熟,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子负甸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理流强,服務(wù)發(fā)現(xiàn),斷路器呻待,智...
    卡卡羅2017閱讀 134,651評(píng)論 18 139
  • 簡(jiǎn)介摘要SOFARPC服務(wù)調(diào)用創(chuàng)建服務(wù)引用配置ConsumerConfig打月,自定義設(shè)置接口名稱、調(diào)用協(xié)議蚕捉、直連調(diào)用...
    鋒Nic閱讀 9,698評(píng)論 1 7
  • 主要內(nèi)容 1.什么是異步 程序或系統(tǒng)中關(guān)于異步的概念使用的比較多奏篙,那么什么是異步呢?下面舉個(gè)生活中最常見(jiàn)的情景來(lái)進(jìn)...
    topgunviper閱讀 2,209評(píng)論 0 12
  • Netty是一個(gè)高性能迫淹、異步事件驅(qū)動(dòng)的NIO框架秘通,它提供了對(duì)TCP、UDP和文件傳輸?shù)闹С至舶荆鳛橐粋€(gè)異步NIO框架...
    認(rèn)真期待閱讀 2,780評(píng)論 1 27
  • 徒步所用的裝備 以我目前的經(jīng)驗(yàn)肺稀,一定要符合兩點(diǎn),第一应民,準(zhǔn)備應(yīng)對(duì)各種天氣路況.第二话原,必須輕量化.】如果high sn...
    Hobbit霍比特人閱讀 2,440評(píng)論 1 7