zoukankan      html  css  js  c++  java
  • 为缓存、外部接口调用添加超时处理

    快速失败

    抛开各种细枝末节的原因,外部组件/依赖不可用的情况时常发生。站点应用有容错能力,能够从常规情况下的失败中恢复过来继续处理后续请求,但部分条件下,失败会从源头逐步蔓延直至拖垮所有应用,称之为雪崩效应

    为了避免此类问题发生,我们需要使用一些手段规避,比较典型的像熔断器比如 Netflix/Hystrix,使用窗口机制,主动移除或者恢复失败的组件/依赖,文档很多且不是本 issue 重点,请自行查阅。

    另外的有效手段是添加超时机制,即外部依赖如果明显失效则应快速失败,而不是无限制地等待。举例来说,Redis 超过1秒都没能返回响应,要么是数据量过大(即设计或实现不合理),要么是已经失活了;SSO 调用时间可能长一点,但根据以往经验总是能得到常规值,而不是傻傻地等下去。

    编程世界里的超时/取消

    高级语言形如 .net 提供了非常多超时的 API,像 Connection.Timeout,Request.Timeout 或者 FileStream.ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) 等等,但麻烦的是 .net 的任务均为基于 CancellationToken 的协作式取消(有关资料仍请自行查阅),这意味着

    • 超时/取消 API 是随着版本更迭逐步添加的,于是覆盖率有限
    • 在业务级别设置 CancellationToken 并不容易,并暴露了低级别对象,对封装性存在挑战

    好在 aspnet core 里异步无处不在,我们可以很安全地使用 task 以很小代价完成超时设置。

    原始的超时实现往往使用 System.Threading.Timer 实现

    1. 将原本无法使用超时/取消的 API,使用异步任务启动;
    2. 初始化 Timer 并使用超时时间作为触发时间;
    3. 在 Timer 的回调逻辑里检查第1的任务的完成情况

    可能未等到 Timer 回调触发任务已完成,但在回调逻辑里检查任务未完成时,就可以主动告诉外部调用方时间已到,从而达到超时与取消目的。

    好在 CancellationTokenSource 封装了类似逻辑,合适使用可以达到相同目的。一个供参考的实现如下:

    public static async Task<T> SetTimeout<T>(this Task<T> task, TimeSpan timeout) {
        using (var cts = new CancellationTokenSource(timeout)) {
            var tsc = new TaskCompletionSource<T>();
            using (cts.Token.Register(state => tsc.SetCanceled(), tsc)) {
                if (task != await Task.WhenAny(task, tsc.Task)) {
                    throw new OperationCanceledException(cts.Token);
                }
            }
            return await task;
        }
    }
    

    代码使用 Task.WhenAny 返回一个等待特定时间完成的简单任务,并与目标任务竞争;当返回结果和入参不同时表示超时,单元测试如下:

    [Fact]
    public async Task canel_timeout_void_task_should_throw_exception()
    {
        var task = WorkHardly(1000);
        await Assert.ThrowsAsync<OperationCanceledException>(async () => await task.SetTimeout(TimeSpan.FromMilliseconds(500)));
    }
    
    [Fact]
    public async Task canel_timely_void_task_should_pass()
    {
        var task = WorkHardly(500);
        await task.SetTimeout(TimeSpan.FromMilliseconds(1000));
    }
    
    async Task<Guid> WorkHardly(Int32 ms)
    {
        await Task.Delay(ms);
        return Guid.NewGuid();
    }
    

    至此超时/取消的目标实现,但其中有若干微秒的地方:

    1. 线程调度、用户/内核模式转换,使得一个什么也不做的任务完成也要 50 Milliseconds 左右,只能模糊计时;
    2. 极少数情况下超时(即调用方 catch 到 OperationCanceledException 异常)并不代表任务没有完成;

    leoninew 原创,转载请保留出处 www.cnblogs.com/leoninew

  • 相关阅读:
    关于《注意力模型--Attention注意力机制》的学习
    神经网络参数计算
    FPN(feature pyramid networks)算法讲解
    RetinaNet-focal loss
    论文阅读: RetinaNet
    CNN+LSTM:看图说话
    非极大值抑制-NMS
    python IO文件操作 file文件操作
    软件测试定义 分类
    软件生命周期
  • 原文地址:https://www.cnblogs.com/leoninew/p/12143315.html
Copyright © 2011-2022 走看看