超时控制
@2024/07/12
好久不见,甚是想念,今天我们来聊聊保证微服务架构高可用性的另一个手段,超时控制。超时控制的作用可以及时释放资源,提高系统资源的利用率,而且还能保证客户端在预期的时间内可以获得响应。而超时控制在微服务领域存在两种形态,第一种就是我们熟悉的客户端-服务端的超时控制,可以称之为端到端的超时控制,比如服务A需要调用服务B,如果调用服务B的时间超过了预期的时间,那么服务A就不会再管服务B是否完成了,而是释放连接,返回给用户一个超时的响应。第二种也是微服务领域比较常用的一种,链路超时控制,这种类型的超时控制视角变大了,保证的是一个业务的超时控制,比如一个下载订单的业务,需要通过用户服务A获取用户信息,判断他是否有权限查看订单数据,然后通过订单服务B获取订单数据,格式化数据,最后通过文件服务C将订单数据打包成一个文件返回给用户,这个业务经历了三个服务 A→B→C,链路超时控制指的就是这个调用链路的调用时间不能超过预期的值,超过了就必须中断业务,返回超时响应。这就是目前主流的超时控制思路,但是超时控制的时间如何设置才是超时控制中最重要落地的一部分,因为如果时间设置的过长,就会导致系统资源耗尽(线程资源,连接资源等)服务崩溃,设置过短会导致服务一直在频繁重试得不到真正的业务执行。所以合适的超时时间是非常重要的。那么有没有什么方法论可以帮助我们在确定超时时间这件事上有迹可循呢?答案是有的,我们回到超时控制的目标上来,超时控制的目标有两个,一个是保证系统的可用性,即及时释放资源,另一个就是保证客户端在预期的时间内获取反馈。所以我们可以根据用户的体验来设置超时时间。这种事情我们可以循环产品的设计者–产品经理,如果这种方法行不通,我们可以根据被调用服务的响应时间来设置超时时间,基本上以被调用服务响应时间的99线,或者999线为超时时间,这里补充一个点,99线,就是99%请求的响应时间都这这个时间内。999线类似。但是如果你没有被调服务的99线,就只能模拟线上环境,进行压力测试。然而很多公司并没有模拟好的线上环境,所以这个办法也有可能行不通,所以你需要估算被调服务的代码中的耗时操作的时间,比如被调服务A 进行了2次数据库连接,1次Redis访问,那么估算耗时时间为:数据库响应时间 X 2 + Redis响应时间 X 1。稍微小结一下,确定超时时间有四种办法,优先级逐级降低,复杂程度逐步升高
-
根据用户体验
-
根据被调服务响应时间
-
根据模拟线上环境的压力测试
-
根据被调服务的代码估算
确定了超时时间,接下来我们来思考一个问题,那就是在服务端执行业务的过程中超时了,我们是中断业务的执行呢?还是继续执行业务呢?按照我们认知的来说确实应该中断业务,因为毕竟这个响应超时了,但是如果要实现这一目标,尤其是在端到端的超时控制中,你不得不在业务手动判断当前这个请求是否处理超时了,据此来判断业务是否继续执行。而在链路超时控制中,由于业务被分为了几个步骤,我们可以在这几步中,进行超时校验来中断业务。链路超时控制中,最重要的事情就是传递超时时间,是传递过期时间段,还是传递过期的具体时间呢?比如超时时间是1s,现在是9:30:58,那么传递的过程中是传递timeout:1000
,还是timeout: 9:30:59
呢?两者都有利弊,先来说说传递时间段,传递时间段的问题在于,网络传输时间的不确定性,比如服务A调用服务B,在调用服务B之前,已经消耗了300ms,那么交给服务B的超时时间就只能为700ms了,当服务B接受到服务A的请求时,拿到的超时时间也不过是协议头中设置的700ms,没有了传输时间,所以我们需要根据大量的测试,来估算出这段传输时间。当然你也可以结合时间戳,比如在协议头中记录了调用服务B时的时间戳,然后当服务B接收到的时候,根据自己的时间减去协议头中记录的时间就可以得出传输耗费的时间,但是这个策略多此一举,与其这样,不如直接使用超时时间戳,还是这个例子,服务A调用服务B,当服务A接收到用户的请求时,根据这个业务的超时时间,记录超时时间戳,比如当前时间为9:30:40,这个业务的超时时间为1s,那么超时时间戳为9:30:41,将这个时间戳传递给服务B,服务B在执行业务的时候判断当前时间是否超过了这个时间戳就可以了。但是使用时间戳是存在问题的,即时钟回拨问题,简单而言就是两个服务之间的时间不一致,就会发生一种情况,明明超时了,服务B觉得没有超时,明明没超时,服务B觉得超时了。这就是我所知道的超时控制的全部内容,下一期我们讲远程调用。
本文由mdnice多平台发布