好 我们接着讲超时控制
我们知道微服务我们拆分以后变成一个个独立的进程
那么我们知道其实每一个进程它作为一个集群
你们某一个业务某一个服务
它可能有50个节点
但是即便是集群
我们知道任何一个节点它可能什么随时都会故障
那么如果故障的话
会一个挨一个都会去挂掉
这样的话其实就会引起一个连锁故障
那么要控制住连锁故障
非常重要的一步就是做好超时控制
因为只有单个节点不容易被打死
才不会引起整体的全面的一个故障
所以超时控制在里面变得非常重要
因为超时控制做得好
我理解它最终就可以保证我们单个业务或者单个服务
它不太容易出现一个从局部的故障变成一个全局的故障
超时控制实际上让我们的组件能够快速的失败
因为我们知道如果你发了一个请求过来
那么如果它处理的时间非常长
那么有可能是它自己的内部进程
内的业务的bug或者一些逻辑
或者说它依赖的一些组件
比方说第三方的rpc 数据库
这些下游出现了一些不可用或者说响应非常慢
那么这个时候一旦你的请求越来越多
然后你的消耗速度越来越慢
我们内层就容易oom掉
或者说我们的线程会爆掉
因为线程数量不够
因为来不及处理这么多请求
所以这种情况下就有可能会节点会一个挨着一个全部故障掉
所以超时控制本质上你可以理解为它是快速失败就fail fast
让我们的堆积或者是阻塞的一些请求
尽快的能够消耗掉
所以这个变得非常重要
一般我们是不希望说你的超时
不希望等到断开的实例
直到超时要等很长时间
就比方说你下游可能要5秒
然后你要等5秒才返回
我们希望主动介入
能够让5秒变得更短
比方说可能只需要100毫秒或者是200毫秒
像我们内部的话一般来说接口的要求的
平均的耗时时间一般是100毫秒左右
比方说我内网调其他兄弟部门的一个rpc服务
一般来说是100毫秒
当然我见过曾经很多传统行业或者非互联网的一些公司
他们的一些内部接口很多是三秒啊一秒这种
我认为这个是比较难以接受的
因为你这样的一个操作
如果是面向c端用户请求的话
很有可能用户的整个等待时间是非常长的
我之前有做过一家公司的咨询
他们做了一个会员的登录的接口
因为登录的时候它会触发一些权益的这种收益之类的
然后他把这种逻辑做成同步的
然后导致说他每次登录的时候要接近10秒钟才会返回
你可以想象一下你的用户在iPhone上点了个登录
然后他基本上要加载个10秒钟转菊花才会返回的话
我觉得这个时间真的是体验是非常糟糕的
所以我们一般要求c端业务的话
内网的接口一般是100毫秒
那么最终你这个接口对外网的话
对公网的话是不能超过一秒钟的
就一秒钟不返回
我们认为就应该主动介入
让它fail fast快速失败掉
因为你这种挂起的请求转菊花 对吧
或者说你无响应的这种界面是非常令我们失望的
我们经常讲叫一秒钟法则
指的是我们移动互联网
或者是我们PC上一般来说是秒级要返回的
就是一秒钟必须要返回的
所以我给个参考值
就是我们内网的rpc是不能超过100毫秒
特别情况下是可以调到300甚至500毫秒啊
大部分情况下是不能超过300毫秒的
最佳值是100毫秒
那么对于公网的请求
最终对用户的请求应该是不能超过一秒钟返回的
还有大家有没有注意到
你的服务整个请求是相互叠加的
就是说你可能调a调b调c
那么可能ABC是一个串联依赖
它数据是有依赖的
那么你的耗时是什么
是a加b加c 对吧
它是整个加起来的
刚开始我们做微服务
遇到多的很多case或者故障
都是因为超时叠加导致的
比方说你a服务可能很快50毫秒
但是你b服务要300毫秒
那么你的整个超时一叠加起来
它就特别容易导致什么
我们的请求堆积
那么这个时候就容易出现一个问题
就是说我们的大量的请求无法被消耗掉
最终导致我们的进程oom掉
所以这个其实是比较惨的一个事情
为什么在内网的微服务之间仍然会出现大量的超时呢
第一网络传递具有不确定性
大家不要觉得内网怎么可能会出现网络问题
以前我们同学经常会说调某个同学内网超时了
是不是网络问题
其实我们知道内网一般来说是不太容易出现网络问题的
但是我们工作中真的会遇到过
我印象中有几个case
第一次是我们运维的同学进机房
然后进行一些日常的机器上架
然后不小心手握了一下我们光纤线
机器到机架交换的时候有个光纤
不小心手握了一下
然后导致丢包
导致丢包就一波报错
你可以想象一下这种非常意外的情况其实也是很常见
另外一次我们遇到过是我们的机房交换
它用的驱动的模块是山寨的比较便宜
几十块钱
没有买原装华为的
大概一个要几百块钱
所以当时为了省钱买的是山寨的
那也是一直丢包
经常内网会超时
所以无论哪些case啊
本质上来说网络传递是具有不确定性的
因为你中间要经过很多的一些物理的设备
要经过一些物理的链路
那么它一定是具备不确定性的
它有可能会遇到一些意外的case
导致服务或者我们最终导致我们的rpc是超时的
那么第二个点其实也是比较坑的就是说
客户端和服务端不一致的超时策略导致资源浪费
这什么意思呢
举个例子
我们的a服务
它可能处理的时间大概是500毫秒 比较久
但是我们使用a服务的人
它只能接受100毫秒
那么这个时候它设置了100毫秒
它去调a服
那么是不是a服务触底500毫秒时间很长
那么当触底到100毫秒的时候
是不是使用a服的这个人它就主动超时了
这个时候向它的上游报504
可能是向用户报504
那么报504以后
这里面有个小细节就是
a服务它实际上还在继续运行请求并不会中断
注意请求并不会中断
因为是你客户端主动自己断掉了
但是我的服务的请求仍然在继续
因为它还要再浪费掉400毫秒去处理这个请求
这个也是会导致一定的资源浪费
那么第三个是叫默认值策略
我们以前很多基础库的实现者
比方说做rpc的 做redis的 做MySQL的
通常他们做这种库的设计的时候
默认值都是非常保守的
都是零零
可能是意味着永不超时
很容易因为意外的一些配置错误
导致最终你的服务出现一些连锁故障
我们曾经就遇到过一次
MySQL client的那个库它有个bug
应该是某个超时不生效
比方说read timeout或者write timeout不生效
导致因为我们当时有一个跨园区的数据库的读操作超时 堵住
然后堆积了大量请求
因为那个时候是专线断了
其实也是很容易出这种锅的
因为我们当时用了它的默认值策略
它默认这是永不超时
当时我们一个bug是设置了一个超时
它也不生效
我们默认值策略是指你要在你的基础库设计的时候
最好也是兜底的超时比建议用什么永不超时这种方式非常不好
很容易出现一些意外
那么所以这种高延迟的服务
导致我们client浪费资源等待
并且有风险
导致我们的进程堆积请求
我们给的解决方案是什么是 叫超时传递
那么超时传递指的是进程内传的每一个组件
那么跨进程rpc传到下游服务
都能够可以延伸
这样的好处是什么
我既能控制住我教进程内的整体的超时策略
同时我这个超时策略要通过一层层的服务的累加叠加之后
它也能够传递
那么最终可以达到一个什么
就是全局的超市控制和不会出现我们的请求堆积这种情况
所以它是一种多面的一种防御
我们总结来说就是超时控制它是微服可用性的第一道关卡
非常非常重要
就良好的超时策略可以尽可能
让我们的服务不堆积请求
尽快的清空高延迟的一些请求
释放它的goroutine
因为大家知道来一个请求
我们通常像net/http库
标准库http它来一个请求开一个goroutine
goroutine在做内部做一些这种http协议的解析之后
它要去做一些http很多function
它要去做你的很多function里面的一些业务逻辑
如果你的处理时间非常长的话
它就会goroutine无法被快速释放
那么goroutine一多
内存就爆炸
同时你的runtime我们要调度越来越多goroutine
它的响应速度也会变慢
最终的结果是什么
整体你的内存就往上升
最终就挂掉了
如果你是个Java的一个服务的话
你的同步开展的线程来不及响应那么多请求
它只能排队
排队完之后
最终内存也会爆掉
内存涨得非常快的时候
那个时候gc也会介入
gc介入以后CPU会高
所以它最终就进入一个恶性循环
就是gc的一个死循环
那种内存高涨不下
然后gc频繁介入
然后整个你的服务响应更慢
然后越来越多请求变得越来越慢
然后最终这个进程就挂掉
所以超时其实是非常重要的
那么对于简单来说
一个rpc的或者是一个redis一个MySQL的这种请求
我们知道一个TCP或者是一个TCP这样的连接
它会包含几个部分
我们所谓的一个TCP连接一般会所谓TCP建接连
然后你会发包
你有一个写超时
连接是连接超时
你发包是写超时
那么等发完包之后你要收到请求
你的rpc要收到响应
那么这个时候叫读超时
所以说有dial timeout
大家很多看过库里面
有dial timeout 有reed timeout有write timeout
那么这三个一定都要设置
任何一个漏掉都有可能会出现一些意外
所以这个是非常重要一个点
我们接下来就是要讲一下
如何去做好进程间和跨进程的超时传递
我们先看第一个就是说在实际的业务开发中
我们还有一个情况也经常会遇到什么
你的一个服务的一个提供者
比方说刚刚我们a服务
它的一个超时策略
它会经常去迭代代码
就是一开始它跟你说
很开心跟你说我的接口大概是100毫秒可以返回
但是随着它的业务迭代
它超时时间是不可控的
你业务迭代你可能加了一些逻辑
然后耗时时间会变得越来越长
随着业务迭代
你的耗时发生了变化
会导致一个什么情况
一开始100毫秒
后来变成300毫秒
那么会导致你上游的一个消费者
它以前配的是100毫秒
很开心没问题
过段时间之后
随着你的业务迭代
超时的契约就被破坏了
那么就会出现一些意外的情况
因为随着它业务迭代以后
它把这个代码发布上线
就会出现上游消费者大量的超时
其实也是我们经常遇到的
我这边会给一个case是什么呢
出现这种情况
假设你们在公司内部也出现这样的情况
我建议是说我们服务提供者
我们作为一个服务提供者
要定义好一个latency SLO
定义一个超时的SLO
那么这个SLO我们叫什么可用性的一个指标
这个SLO放在哪里呢
你可以放到你们API的文档里面
或者是放在一个公共的地方
大家可以查阅的
像我们比较推荐的话
如果你用的gRPC的话
你可以在gRPC的protobuf的定义文件中
比方说我在某一个接口上可以写上接口的latency SLO
95线是100毫秒
99线是150毫秒
那么对方可能能够接受99线大概是150毫秒的话
它可能会设置180毫秒
这样的话你的文档
或者说你的pb文件会把这个描述出来的话
看起来是非常友好的
并且对方会基于你pb的定义文件契约来去设定超时时间
同时我们服务提供者提供的pb文件
你写的是99线150毫秒
那么你在做业务迭代
那么你上线前一定要压测
要保障指标
所以它是一种君子约定
我们通过在protobuf文件定义中写好
接口的95线或者99的一个SLO 延迟的一个SLO
那么服务消费者它可以去基于策略来设定超时
同时服务提供者要保证契约能够生效的
这样的话能够尽可能避免出现一些意外的默认超时策略
就是可能你大家一开始随便说的100 200那种
这种其实是很松散的一个没有约束的一种做法
这个是一个点
我这边强调
第二个是什么
我们基础库还是要兜底一些默认超时
我们之前讲了
我们通常是100毫秒
那么进行一些配置的防御
基础库呢
在你没有配置配置文件的时候
那么我们基础库一般默认是100毫秒
尽量不建议说用无限等待的这种
或者说什么60秒之类的这种超大的超时策略
就是这种是靠我们的代码层面去做一些保护
比方说它的配置文件没配置
我们的基础库代码层面
在设计这个client的时候要做一些兜底的保护
这样的话避免一些意外的错误配置
我们曾经也出现过
有人手抖把配置文件配错了
然后策略不生效
如果我们有多了一层技术库的兜底的话
其实这种意外的情况也不太会出现
这是第一个
第二个就是说我们尽可能用配置中心的公共配置模板
因为这种配置策略越多
我们配的越多其实越容易出错
应该我们在讲工程化的时候其实也讲过
我们配置文件其实是应该是越少越好
我们趋于简单的去管理这些配置
那么公共服务它的一些配置
尤其是耗时的配置
我们应该是走公共模板
尽可能减少这些配置
这些也是能得到这种比较好的一些收益
不会意外的配错之类的
我们接下来是要了解一下超时传递
我们看一个case
超时创建它到底是什么呢
就是说当我们上游服务已经超时返回504
它提前报错了
但下游服务仍然在执行
会导致浪费资源
做一些无用功
那么超时传递指的是把当前服务我当前服务剩余的Quota
超时剩余的Quota
比方说我的总超时是300
我现在用掉了100
那么Quota是等于200
还剩200
把Quota传递到下游服务中
或者说我进程中去继承超时策略
那么控制的是一个请求级别
因为你一个请求会向下扇出调很多rpc服务
它控制的是请求级别的全局超时策略
我觉得这个是非常重要的
就像我们这个地方
我们看一下它整个超时传递是怎么做的
比方说service A
我在入口的地方设置的是1s超时
那么这个时候第一步它调了一个redis
它花了100毫秒时间
那么这个时候我们service A它的Quota还剩多少
是不是还剩下900毫秒
我请求的一个redis可能是判断一下一个token是否有效
那么第二步
我去调了一下我的service B
扣了一个gRPC调到service B它花了500毫秒
因为刚刚剩900毫秒
那么花了500毫秒
900减500还剩下400毫秒
这个时候我的service A
在第三步它会去请求请求一下数据库
那么请求数据库这个时候有个小细节
我们要把超时的Quota继承好
这个时候还剩400毫秒
但是我的数据库的配置
就是我config这个地方是配置的是500毫秒
但是我当前剩下的只剩只有400毫秒了
我要很好地继承策略
那么一定是两者取其小
当前是400毫秒
但是我的配置是500毫秒
那是不是取一下min
看起来500毫秒更小还是400毫秒更小
那么取的结果肯定是400毫秒
也就是说最终应用到我数据库的超时测量应该是多少
应该是400毫秒
那么这个时候我的实际数据库执行的时间是350毫秒
有没有意味着我最终只剩下50毫秒的Quota
如果我再调其他的服务
那么这个50毫秒可以跨进程传递到下游
它们一定下游的服务不能超过50毫秒
那么你只有这样
由最开始service A节点设计一秒的超市策略
才能被你进程内和进程外全局去继承
那么进程内怎么达到这个效果
一般来说在一个请求
比方说在我们net/http请求前
我们都会用什么context.WithTimeout
我们会用标准库Go的context.WithTimeout
获取到一个Go的context的上下文
context的这个ctx对象
你其实会掉redis也好
调gRPC的微服务也好调数据库也好
我们的接口首参数都是context
那么有了context
你就有办法去控制住每一个网络请求的他的耗时
超时时间
举个例子
在我们这个地方我们redis怎么做的
我们redis的接口一个get操作
它会接收一个context参数
然后接受一个key参数
会返回一个Item和error
那么在我们网络请求开始前
我肯定是要拼凑一个memcache协议
这个地方我们肯定是拼凑一个getX去发送给memcached
所以我们之前讲过有连接超时
有读写超时
我这个地方是设置的写超时
因为我要发包
我要发送给memcached
这个地方有个叫SetWriteDeadLine什么意思呢
我肯定我的redis是有自己的一个超时时间的
比方说我虽然在http请求配置的一秒钟
但是我内部有很多个组件
有redis 有gRPC 有数据库
我肯定不是说大家都是用最大的额度去花
因为我可能给redis给50 或给gRPC给100
给数据库给300
我进程内肯定是有一些策略分配的
就不是说大家都是用一秒最大这个值
我肯定是说希望自己不能超过多少
比方说我们这个地方redis配置的是50毫秒
SetWriteDeadLine它为什么不直接去读context的剩下的Quota
因为我们要手动的
有些情况下需要手动的给redis一些固定的超时时间
我刚刚说的是50毫秒
为什么我又不直接设定50毫秒
就是c.writeTimeout
这个是个配置文件读出来的
redis连接里面获取到的对假设是50毫秒
它应该是跟你的redis的设定值和当前剩下的值做一个
就是取谁最小
比方说我剩下40毫秒
你配的50毫秒
那么我最终设定的超时时间40秒
因为这样的话我入口设置一秒钟的Quota是不会超的 明白吧
如果设定是50毫秒
那么我们入口设置成一秒就变成什么1.001
对吧
它就多出了10毫秒出来
所以你要让策略最终生效的话
一定是去你当前剩下的Quota和你配置的超时策略取一个最小的值
那么你进程内在每一个有网络操作
你都应该做策略
大家感觉会很复杂
其实并不复杂
最终其实效果是什么
就是相当于你自己做的一些基础库
在gRPC 在redis memcache MySQL
这种代码的这种处理的地方需要做这个逻辑
能够让它继承context的超时策略和你的配置的超时的策略能让它生效
你的进程内的超时的策略就在你的掌控之中
就是你入口设置一秒钟
你下游所有组件加起来一定不会超过一秒
这个其实是非常关键的
所以我们把这种策略叫进程内超时控制
它的核心思想就是利用context和我们基础库
比方说redis memcache MySQL这些库的实现
两者结合
像如果你是用直接用database sql
它都有query context
你其实就是在query context
做处理的时候你要去应用你的策略
这个是一个点
那么第二个就是我们要做什么
跨进程的一个传递
那么要做跨进程传递超时策略
怎么去往下传递呢
你像我们第一个case
service A设定的是一秒钟
那么如果它调service B
service B实际上要执行两秒
那么它超时那么它就会产生资源浪费
你要把超时策略传递的话
实际上我们调gRPC
我们知道gRPC有什么有Metadata Exchange
它具备元数据的一个交换能力
那么你就可以service A调service B的时候
你是不是可以把当前剩下多少Quota是不是可以把它传递下去
你当前比方说service A调service B你剩多少Quota
你是不是可以传递给它
传递给它以后gRPC server一接受请求
那么它是不是可以把这个请求初始化的时候把它的context.WithTimeout给设定好
实际上核心思路就是利用了gRPC的Metadata的元数据传递
像gRPC框架里面
因为gRPC是基于HTTP2.0做的
所以它最终其实是基于HTTP2.0的Heads自定义头
去传递一个叫做grpc-timeout的字段传递到下游
传递到下游之后
它会自己构建一个新的context
那么这样的话
其实你的跨服务也就能做到一个效果的
最终效果是什么
在我们这个地方
service A设定的是一秒钟
那么它调service B它花了300毫秒
那么它还剩700毫秒
那么service B在调service C他花了500毫秒
对于service B来说
因为它继承的是700毫秒
所以它只剩200毫秒
这样的话你的一层层传递
那么你的全局的链路里面
它的总超时就不会超过多少
就不会超过一秒钟
这个情况下
如果它你在调的时候
你传递到下游的时候
发现余量不足了
就是你的context总有一次会用完的
那么发现余量不足的话
它两种情况
一种是发到下游只剩个几毫秒
那么你的接口可能会立马报context.Timeout
就立马返回了
它快速失败的
还有一种情况下
你在发送下游的时候
发现context剩余的时间已经没有了
那么这个时候client会直接报错
context.Timeout往上抛
所以它下游服务完全不会请求
这个时候你的请求级别的所有服务
它都不会浪费资源
它不会出现第一种情况
就是service A一秒钟 service B执行了两秒
还在那里无脑的在那里做一些无用的
因为你的用户早就已经504
用户可能都已经重新刷一下它的APP
重新发了一个新请求进来
就浪费资源了
另外就是说还容易导致请求的被堆积
所以这两个其实非常关键
超时控制里面
还有一个我们叫双峰分布
通常来说95%的请求耗时在100毫秒内能够返回
我们的95线一般都是100毫秒的能够返回的
那么剩下5%的请求可能永远不会完成
也就是说我们的常规请求才是最可怕的
我们大部分请求都是
所以说我们看监控图
不能只看它的均值
就像我们挣的钱一样
被平均一下好像个个都是千万富翁
但我们不能只看均值
我们还是要去看它的95线和99线的耗时
甚至是999线
95线99线如果它能保证
在100毫秒内能够返回的话
我觉得相对来说是比较安全的
因为有5%的请求永远不会完成
它的耗时非常长
可能一秒两秒
那么这种情况下
你就要搞清楚是不是你有一些组件没有继承你的超时策略
没有继承你的超市控制的策略
就是在进程内没有继承超时长期的策略
或者跨微服务的时候
是不是有一个微服务没有继承你的超时策略
导致你的总超时变长
这也是有可能的
所以5%的请求才是你真正需要去关注的
叫bad case就是它的比较糟的一些情况
因为这5%的请求才是最有可能导致你微服出现服务
整个挂掉最可能的一个因子
所以这5%的请求是需要我们去重点关注
比方说我们可以利用一些链路追踪
去分析去了解它为什么会出现这些case
那么你就要去优化你的代码
所以说我们的超时的分布通常都是双峰分布
大部分很快或者一部分很慢
所以它不是像我们的正态分布一样
它比较像是一个双峰分布
一坨密集在很快
一坨密集在很慢
它基本上是这样的一个分布的策略
所以说我们要做好超时控制就几个点
因为超时决定着微服务的线程
或者goroutine是否会被耗尽
所以它几个点非常关键
第一一定要设定非常合理的超时策略
那么合力的超时策略取决于我们的API的定义的文档里面
要把你的SLO
你的latency SLO写得清清楚楚
服务提供者和服务消费者都要去满足契约
这是第一
第二我们基础库一定要有个兜底的策略
避免一些意外的超时策略去生效
比方说永不超时之类的
这是第二
第三我们进程内的超时传递要实现好
那么进程内的超时传递取决于你的context传递到每一个function
那么在产生网络请求的时候一定要注意三个超时
连接超时 读超时 写超时
那么在读超时
因为dial timeout一般是在我们基础库初始化的时候要去设定的
比方说是100毫秒
当然它也可以继承你的context来做这个逻辑
第二个你在发送rpc请求
发送redis请求 发送MySQL请求的时候
发送网络请求的时候一定要设定好读超时和写超时
那么要做好进程内的超时传递
那时候就要你的读写超时的策略
就是优先读配置文件和context去比谁最小
取最小的
因为你的全局超时不能超过你的root的节点设置那些超时
所以你要取一个最小值
这是进程内的超时传递
最后一个点是什么
是跨进程的超时传递策略
那么就是gRPC好像天然就带这个功能
gRPC的Metadata的传递是天然带这个功能的
那么如果你是HTTP框架
或者是别的一些rpc框架
你就要手动去实现策略
让超时传递通过一个Meta data
比方说你可以加一个xxxx-timeuot一个400毫秒过来
那么带过来以后呢你的rpc服务就收到Metadata以后
新建一个context
以前你新建context可能是一秒钟
你这个时候发现这个一秒钟和你传递过来的400毫秒
更小的是400毫秒
你就优先用400毫秒新建一个新的context
这里面还有小的一个点要说一下
我们400毫秒一般还会减去一个10毫秒
或者减去个5毫秒或者减去个3毫秒
为什么
因为我们内网ping通需要时间
网络传输需要时间
所以我们一般会乘以2~3个TTL
就是我们发一个ping来回它大概是多少毫秒
因为我们内网一般是减去1~2个毫秒
你会减掉一些网络传递的一些时间
那么你做好了上述的一些事情
你的服务进程是大概率不会挂掉的
你的进程大概率不挂
你才有办法生效 什么熔断 超时重试 降级
这些策略才能生效
因为你只有前提你的进程不挂掉你才能生效
后续的一些降级什么各种策略才能生效
所以它是个前置条件
我们看一下我们当时超时的一些Case Study
印象比较深刻的
第一就是说我们当时入口的SLB
我们入口是一个Nginx
proxy timeout和upsteam timeout没有设置
我们当时搜索还是一个什么服务出现的故障
然后大量的用户请求
因为大家知道一般来说服务不可用
用户会大量重试
那么大量的请求就涌到我们的Nginx
那Nginx就不断的转发
但我们那个服务其实已经不响应了
但是它又没死透
然后最终导致我们Nginx堆积了超过300万个连接 长连接
然后堆积了很多连接以后Nginx没挂
然后我们的核心交换扛不住了 就挂掉了
核心交换的灾备当时又没生效
所以最终我们当类似事故
是导致我们整个机房所有流量全部都进不来
全部都不可用
所以这个其实就是一个很小的连锁故障
你想象一下
如果那个时候我们的Nignx 入口的Nginx
SLB配置的一个proxy timeout等于一秒钟
那是不是意味着即便你下游的服务故障了
我的Nginx发现超过一秒钟
我就直接往上游抛504
那么其实也不会出现一个连锁故障
它就也不会间接影响到我们的核心交换的连接数太多导致故障
所以这个是个比较典型的一个case
那么第二个其实就是我们服务依赖的一个数据库连接池
漏配的超时导致很多MySQL请求被堆积阻塞了
最终导致我们好多服务oom掉
因为你的请求堆积越来越多
策略不生效
所以说这也是我刚才说的我们基础库一定要有一些兜底的超时策略
第三个是也是比较常见的
就是我们当时一个广告服务
我们广告服务因为它要请求外网DSP
它会去请求很多外网的一些接口
就做广告的一些投放 做预算
它做了很多逻辑的改动
那么它下游服务耗时变多了
从300毫秒变成了500毫秒
他又没跟我说之后
我们上游的服务配置超时很短
所以它一上线以后
我们大量的调广告接口超时
就导致我们大量的失败
因为你最终还是广告虽然可能时超时了
但是我可能做了一些降级
可能不返回广告数据的或者返回一些别的一些内容
那是实际上对我们用户体验还是有影响
或者对我们收入有影响
那么如何避免这个情况呢
在我们API定义里面设定好latency SLO
那么提供者和消费者都要满足契约
所以这个是超时里面非常重要的一个点
我们超时就讲完了
那么接下来就是要讲一下过载保护
过载保护和超市其实也是一样的
核心就是让我们的服务不死掉
因为只有不死掉
你才有办法去做限流降级
从事各种其他的一些策略
好 我们超时就先讲到这