【原创】从4个9到5个9可用性调优-总章

摘要:
第二部分:目标有很大的影响,领导者更关注系统的可用性。在以上两点得到明确确认后,大家对可用性的概念有了共识。之前出现不可用异常的原因是核心业务数据和非核心业务数据放在一个Redis中,正是因为单个热键的异常导致非核心业务的数据不可用。
适用场景&适读对象

场景关键词:互联网 分布式 业务集群 qps高 扛量 高可用

涉及内容:缓存调优 GC调优 隔离 上下游防护 运维优化

适读对象:资深业务开发同学,团队leader

Part1.背景

之前年底线上用户端工程出了一次可用性故障,导致用户进app首页白屏,系统不可用。

影响:

产生了实际的资金损失;后续运维计量不可用时间为35分钟;
最后定级为S0级别故障;影响较大。
Part2.目标
这个事情影响较大,领导比较重视系统的可用性。新一年的质量OKR是4给9的可用性,是52.3分钟;
当然我们定目标之前因为上面这个背景问题损失了35分钟,所以指定目标么,总得有挑战,自己定的目标是:5个9的可用性,即一年不可用时间为5.2分钟。
Part3.挑战
说实话一开始要觉得做这个事情,也是不知道要怎么做的,大伙儿也都没有正儿八经的可用性调优经验。
整个过程也是摸着石头过河,下面就是我们调优经验的总结。
具体措施过程如下:
【原创】从4个9到5个9可用性调优-总章第1张
Part4.措施

定义可用性

【原创】从4个9到5个9可用性调优-总章第2张

第一步:定义【可用性】

【非故障的节点在合理的时间内返回合理的响应(不是错误和超时的响应,不保证是最新的数据)】。
可用性的两个关键:一个是合理的时间,一个是合理的响应
合理的时间指的是请求不能无限被阻塞,应该在合理的时间给出返回。
合理的响应指的是系统应该明确返回结果并且结果是正确的,这里的正确指的是比如应该返回50,而不是返回40。
总结来说:分布式系统在出现故障时,允许损失部分可用功能,保证核心功能可用。并且对于核心的功能,其要在合理的时间有合理的响应
基于这个定义,在定义可用性时,我们要做的事情如下:
    1. 梳理功能,找各业务方代表确认,哪些是核心功能,哪些是非核心功能可以放弃。
    2. 核心功能,在极端情况下,如果要被降级以保证可用性,此时降级的策略是怎么样的?或者说降级时业务逻辑怎样?
    3. 以上2点讨论清楚,然后和部门领导check下就ok了。

第二步:量化【可用性】

这也要结合具体的业务功能,比如排列一下各个接口的重要程度,然后拍脑袋定定一下对应权限。
然后和运维同学确认下异常时的计量方式,一般是异常时相关接口的 某1分钟(可以调整监控粒度)域名可用性*业务功能权重 可以算出来 这分钟损失了多少时间。
上面2个点确认清楚 大家就对可用性的概念有了共识了。


改造大纲

这些措施项并不是一开始就有这个全貌,是基于结果梳理的:

【原创】从4个9到5个9可用性调优-总章第3张


Part4.1.缓存调优

【原创】从4个9到5个9可用性调优-总章第4张

4.1.1.分布式cache-redis侧

已有是redisCluster的HA架构增加了一个降级redis来单独存放核心业务实体以提高整体可用性。
之前导致的不可用异常原因是因为核心业务数据及非核心业务数据都放在一个redis,而正是因为非核心的业务数据因单点大热key的异常导致了redis的不可用。
基于这一点,根据业务场景将核心业务实体的redis数据,除了放在原有redis中;单独搭了一套降级redis,仅将这部分核心业务数据放进这个降级redis中。
那么非核心业务数据导致主redis异常时,业务层代码再降级到降级redis。
这一步的改造,可以背景问题中的不可用问题。也是我们最初所想到的。

4.1.2. 本地进程缓存侧

重点放在本地进程缓存侧的改造:

【原创】从4个9到5个9可用性调优-总章第5张

4.1.2.1.本地缓存调优:3个策略

3个策略:更新策略(TTL)、一致性策略、降级策略

1.TTL&更新策略:
改造之前是:对于最核心的业务实体是3分钟过期用户侧线程同步阻塞更新策略;改造为异步线程增量更新,TTL改为2小时过期。
这么改的背后的动力还是GC频繁,GC的细节在下一章会细讲。
增量更新的模式是新增了一个变更事件表,(整体架构是CQRS架构),
在Commend工程里进行业务实体变更时,加一个AOP切面,捕获变更实体ID,将id写入变更实体表,
然后整个集群【在异步线程】每2分钟去轮询这个变更事件表的增量变更id,然后更新内存中的这些过期实体。
改了之后效果明显:上下游改善(用户请求45ms>30ms依赖服务集群压力减少40台缩容为20台),ygc改善(次数15秒一次>10秒一次时间40ms>30ms)。
2.一致性策略:
改造之前TTL是3分钟过期:
问题1:若某key有变更,最慢的机器3分钟才能刷新;问题2:集群间这个key过期时间不同步,不能保证用户请求的单调一致性;
改造后:采用异步异步线程增量更新,集群最慢2分钟更新,但整个集群同步节奏一致,corn表达式在偶数分钟第一秒获取增量变更id,,1秒加盐分散对redis的压力防雪崩。
也就是说增量变更部分,整个集群在2秒内可以达到一致性状态,可以保证用户请求单调一致性或会话一致性(用户点击操作,两次点击直接相隔大于3秒)
3.更新时2级降级+3阶段查询:
更新时2个降级准备:
第一级:刷新redis时,对于核心key, 除了刷入主redis,也刷入降级redis, 注意:这里只针对这部分核心的key
第二级:有个异步线程周期性扫ehcache中的所有核心业务实体,json序列化到本地文件系统。
这一步操作要结合自身业务场景,如果核心业务实体是千万级别,可以考虑将顶部热游做json序列化, 如果业务实体不多,则可以考虑将全部实体都做json序列化。
这一级降级异常重要,若周边依赖系统都挂了(因下游系统不可以,网络异常等致命问题),此时可基于本地文件系统做基础实体返回,保证业务系统基本可用。
查询时3阶段查询:
第一阶段
先查询本地缓存,若差得实体,则直接返回;若本地缓存为空或者实体过期,此时进入二阶段。
这里务必注意: 查询本地缓存时,一般的本地缓存中间件都会使用惰性过期机制,也就是说get这个key时,若该key过期,则会释放这个key,
此时千万不能使用原生的get API,要获取中间件底层的存储数据结构,如ehcache地产的componentStore, 进行get操作,本质思路是不触发过期key的释放。
第二阶段(核心3级降级构建):
因第一阶段未差得key,此时要发起远程redis的查询,若远程redis不可用,则取降级redis的key;
若此时因为网络原因不能访问redis, 或者两级redis都不可用,则取本地缓存中过期实体(再set进本地缓存,ttl拉长3分钟);
若本地缓存中也没有实体则取本地文件系统做最后尝试,即取本地文件系统json文件,构建为内存实体,set进本地缓存;
通过上面三级降级,只要某实体之前被成功构建过一次,被成功加载进内存一次且被json序列化到本地文件系统;
那么在未来的查询中,只要用户请求到这台机器链路是通的,即便周边系统全挂,那么这个实体一定能够查询返回。
第三阶段:再去本地缓存中get一次,此时基本能差得实体,若没有则返回空。

4.1.2.2.本地缓存调优:4个防护

4个防护:防雪崩(冷热分离)、防击穿、防穿透、防污染

1.启动时缓存预热:
即key的冷热分离,启动时对热点数据进行启动预热。
对于热点数据的构建,可以有很多模式:
可以是单独一个服务来维护热点数据;
简单点的,可以加一个异步线程,周期性扫描本地ehcache中的实体,根据命中率统计排序,取顶部一定比例的数据作为热点数据列表存储到本地文件系统,在启动时则取改列表进行预热构建。
2.防雪崩:
缓存雪崩定义:【 当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统(比如DB)带来很大压力,造成数据库后端故障,从而引起应用服务器雪崩。】
即本质是缓存大面积失效,导致请求直连数据库,导致后端组件故障从而引起全局不可用的场景。这里我想扩展一下这个定义:
核心2点:1是短时间内大面积key失效,这个失效可以是缓存组件不可以,也可以是某段时间内确实有大面积的key变更;
2是瞬时压力请求后端,这个后端的定义是指rt性能比当前缓存rt低一个数量级的后端:
如果当前是jvm进程缓存,后端可以是指redis也可以是指mysql也可以是指依赖的一个微服务;如果当前是redis后端可以是指mysql也可以是单独的一个微服务。
在本例中是指依赖的一个微服务。
具体场景是在间隔2分钟内,变更事件表中有大量多的key,此时整个集群都需要刷新,对下游服务的请求压力巨大,可能引起下游服务挂掉。
基于这个背景,解决缓存雪崩的思路就是每偶数分钟获取变更事件表的变更key集合时,请求下游实体服务时,基于加盐的方式(random1000ms)分散对下游集群的瞬时压力,防止其挂掉。
3.热key防击穿:
击穿一般是指的热key,在业务高点时过期导致请求直接打到高能耗媒介(如mysql);
解决思路2个方向:
1个方向是避开业务高点,比如发布时避开业务高点,在凌晨发布,或者发布时有缓存预热;
第2个方向是key不要在用户侧线程同步更新,即缓存中的key的ttl比更新周期长,用户请求时总能拿到key,更新在异步线程更新。
以是2个思路都可以解决击穿的问题,可以结合具体业务场景来定解决方案。
4.空key防穿透:
这个空值分2个场景:
一般场景是指在key的id范围内,某个key被删除了,此时要存空对象(注意区分null值);
但是更致命的往往是指有外部攻击,外部识别了我们的请求,然后遍历一个很大的id范围来捞数据,此时可能很多很多的key都是不存在的,
因为#1防穿透逻辑会导致内存爆掉非常危险,因此很有必要限定一个范围,
比如if(id<=0 || id>max(DB_id)+10000) return null;
这个max(DB_id)需要定期异步更新,比如10分钟更新一次,而10000则是基于业务10分钟内最大会增加的key。
以上2步基本可以做到穿透保护。
5.缓存污染:
缓存污染的定义是指基于某个key拿到实体后,对实体进行了修改,导致后向请求业务异常。
这个问题的解决方案2个方向:
方向1是基于缓存get时进行深拷贝,一般缓存中间件都有这个配置项,这个方法从结果上绝对可以规避污染的问题,
但是每次都深拷贝一个对象对YoungGC非常不友好,特别是在业务高点,所以一般也不建议用这个方式来解决。
方向2还是在开发阶段来规避,比如 1.内部培训强调不能对缓存数据进行变更 2.基于模板方法模式规范化使用ehcacheapi:3.代码审核
4.1.2.3.缓存调优-效果:
以上的缓存调优帮助我们规避了一次线上S0的故障:
在2020年国庆节第一天,我们的redis中的key大面积失效(包括降级redis),导致集群去redis中拿实体时都为空
(原因是国庆前一周最后一次发布变更刷入redis的逻辑出了问题,而redis中的key又是7天失效),
因为有后面2级降级(内存过期实体以及本地json文件),使得实体都可以返回展示,
帮我们逃过了一劫。太幸运!~~~~

4.1.2.4.一句话总结:

3分钟过期同步阻塞更新模式改为2小时过期异步增量更新模式提升了上下游性能;
2级降级+3阶段查询极大提高了本地cache的可用性;4个防护进一步提高了本地cache的可用性;
最终改造帮我们规避了一次线上S0故障。


Part4.2.GC调优

3个方向,降空间 降时间 降频
【原创】从4个9到5个9可用性调优-总章第6张

4.2.1.降空间:

【原创】从4个9到5个9可用性调优-总章第7张
1.去除命中率低的key:全面梳理业务,根据命中率统计,一些命中率低的业务场景去除缓存逻辑;
2.优化粗粒度key:对于某些粗粒度的key,根据DDD思想,对粗粒度缓存进行瘦身,List实体列表改为List id列表。
3.打薄上层业务缓存:某些接口上层有业务缓存,依赖的service里又有缓存,可以考虑去除上层业务缓存。
4.只缓存一份实体,基于GraphQL进行查询:
在我们的业务场景里,某个实体有30个字段,实体有10000个,缓存的key是 ID-SceneType,
如 key1:1001-Simple,代表1001缓存实体 但是这个Bean里只有10个字段;key2:1001-Detail, 代表1001缓存实体,里面有全部30个字段;
类似的type有8种场景类型,也就是说在业务高点极端情况下,10000个实体最多会缓存80000个;这是极大的空间浪费。
针对这个场景,其优化思路就是缓存10000个实体的全量字段,查询时根据要的场景类似临时拿到字段即可。
这个场景的优化,帮我们大大降级了缓存空间。    
5.惰性过期释放优化
一般本地缓存都是惰性过期机制,即没有异步线程扫码,某key过期了,依赖用户侧线程,get到这个key,判断下这个key的ttl是否有效,没有则释放这个key;
也就是说,特别是冷端的key, 某个极其冷门的key被定向搜索时查询到了,缓存也构建了,如果key的ttl为3分钟,
但是如果这key后向一直没被访问到,虽然key在内存中早已过期,在应用重启之前,这个key会一直hold在内存中,
这便是极其典型的站着茅坑不LS啊。即便后面被访问到,也仅仅是判断过期释放了这个key,然后有会新构建一次这个key,恶性循环了....;
所以针对冷端key惰性过期不释放这个场景,不妨参考下redis的内存管理机制,如下图:
【原创】从4个9到5个9可用性调优-总章第8张
redis的内存管理机制我后面会单独开一篇文章来细讲【redis三级内存管理机制:详见这篇文章】
通过上面对比可知道,本地缓存的管理,对于冷端过期的key缺少了一道释放环节;
从中间件角度考虑,我想缓存中间件设计人员肯定也知道这个问题,但是要解决这个问题一定也是要一个异步线程来扫,
但这无疑提高了中间件的复杂度,至少ehcache中是没有这个异步线程的。
那么解决这个问题也是简单,就添加一个异步线程,周期性的触发,然后扫码ehcache中所有cache中的所有key。
在我们的业务场景中,这个触发机制会设计的稍微复杂一定,触发条件是if(距离上一次扫码间隔是否超过2小时 或 当前老年代占比是否66%)2个条件1个达到即可,
这里解释一下66%的设计思考,我们线上是使用的cms收集器,触发阈值是68%,
这个异步线程是5分钟调度触发去探测上面的2个条件,线上差不多是4小时触发一次CMS,老年代增长2%差不多需要10分钟,
即主要考虑在cms快要触发前能碰上一次ehcache的全量扫码来释放这些冷端过期的key,这样无疑能提高cms回收的效能。
6. 制定代码审核规范
添加cache要讨论及代码审核,特别是粗粒度。
之前就是添加缓存没有约束,大家自己觉得这里慢了需要缓存就缓存一下,结果就是我们ehcache里配置了近100个cache,显而易见很多cache并不是核心重要的,
特别是有些功能时间久了不维护了,但是在去除cache时往往大家又非常谨慎,导致不必要的内存浪费。
GC降空间调优-效果:
线上是4G内存3G老年代,parallelGC, 每次是3G到2.4G, 仅能释放0.6G
通过上面1-6的优化后效果是3G到0.8G,可以释放空间为2.2G,多释放了1.6G空间,效果是非常巨大和明显的。

4.2.2.降STW停顿时间

之前线上parallelGC的STW停顿时间,要5~6秒,吓人的很。
通过上面#1的优化,空间下降对于STW停顿时间是有很大帮助的,结果是5~6秒下降到3秒左右,原因是FGC时,存活对象少,意味着要计算的位置以及移动的对象就少,时间显然是变少的。
降低空间后带来的停顿时间的降低,具体原理详见:【原创】内存中存活对象少 是否有助于降低停顿时间
怎么从3秒到180ms的调优,分2步,第一步是parallelGC升级为CMSGC,这个效果非常明显了,3S直接到300ms,
后面就是调优CMS的参数了,经过调优最终到了180ms, 【【详见这篇文章:【原创】CMS垃圾收集器调优-总章】】

4.2.3.降频:

降频是顺势而为的,通过#1#2的调优,GC频次也由平均1小时一次降到平均6小时一次。效果也是显著。

4.2.4. 一句话总结:

优化key的设计,以及异步线程进行冷端key的释放,有效降低FGC回收后的空间,且STW从6秒到3秒;

然后通过gc垃圾回收器的升级及gc参数的调优,STW到180ms,gc次数1小时1次降低到6小时1次。


Part4.3.隔离改造

隔离和降级其实有点关联,不过隔离更侧重于两个系统、组件彼此不相互影响,而降级则是关注于一个系统、组件的返回
很多时候角度不一样这2个概念可以相互解释。隔离:其核心思想就是两者不相互影响,A系统异常不影响B系统的正常返回;更多时候是非核心逻辑异常不影响核心逻辑的返回。
改造从下述5个方向入手:
【原创】从4个9到5个9可用性调优-总章第9张

4.3.1.微服务拆分隔离

我们线上系统是14年开始建设,早期架构时是个单体,我也相信在那个时空背景下单体架构是比较合适的决策。
但是这个架构到了现在就不合适了,随着业务的发展规模的扩张,虽然我们有集群,但是问题也是显而易见的。
(关于DDD的是思考,为什么初创团队很难运用DDD进行落地,【【详见这篇文章】】)(这里有2本经典的书推荐大家,微服务架构设计模式 & 实现领域驱动设计);
关于单体架构的问题有很多,【【详见这篇文章】】,本质还是因为没有隔离带来各个维度上的问题,如可用性、可开发性、可维护性、可迭代性;
这里我们主要关注系统可用性,即因为多个业务领域的逻辑耦合在一起,导致非核心域的异常影响了核心域的可用性。
具体的做法是:基于DDD的思路梳理业务,分拆领域,规范限界上下文!
我们根据业务的梳理最终拆分了几个业务领域,然后进行拆分改造,当然改造的过程不会一蹴而就,会有很多问题,
具体问题详见(【【详见这篇文章】】:统一业务实体的困难,因为部分被外部使用;各过程初期拆的时候 因为时间 是简单复制,导致相同实体存在于各个工程中,基于sdk的方式解决)。
最终架构变成这样子(改造之前是所有逻辑耦合在一个系统中的):
【原创】从4个9到5个9可用性调优-总章第10张
改造过程虽然漫长,效果也不是马上显示的,但是这个意义对未来却是重大的。
除了技术层面可用性的提升,另一个层面,组内执行这个过程中,大家潜移默化地有了DDD的思想,这对于后面接到新需求,大家会去考虑:
这个业务是哪个领域的?聚合模型是否符合DDD规范?边界是否合理?代码应该放在哪个工程还是新建一个工程?等等,
也就是说 至少保证了新的需求代码都往微服务 DDD上面靠,而老的逻辑则慢慢改造或者下线摒弃,我相信未来总是长期向好的。

4.3.2.线程池隔离

线程池这个东西大家都熟悉,问题就是大家觉得自己熟悉,觉得仅仅只是个提升性能的工具,没有过多考虑,最终就成了滥用。
最典型的使用场景:某同学做一个需求,觉得有性能问题,然后决定用线程池,工程里找找是否有已有线程池,发现有一个,然后就复用这个线程池。【表情 捂脸流泪】
事物总是一分为二,在使用线程池享受到高性能的同时,也要考虑下面2个问题:1.线程池是否隔离?2.若新增线程池,参数配置口径是否上下游对齐;3.中间件线程池
#1隔离问题:很容易被忽略,比如某个线程池,承接了核心的首页计算;同事也承接了异步发送日志消息的计算,这显然是非常不合适的;
从业务侧,如果线程池饱和,我们肯定会选择有限保留首页的计算,首页的展示。
也就是说,不同的业务场景,使用相同的线程池,最好各业务场景在【重要程度】上是基本一致的,如果某个业务场景比较重要,
那么这个时候,注意不是【强烈建议】了,而是【严格要求】此时必须为这样重要场景独立配置一个单独的线程池。
#2线程池口径问题:在为某个场景配置一个新线程池时,线程池原理【【详见这篇文章】,单线程超时等待的原理 timeout的原理 执行线程还是hold住了】,
关于参数的配置想必大家都清楚,对于应用层很少有CPU密集计算的场景,绝大部分都是依赖外部IO操作的,此时可以将核心线程数大胆地调大一些,
队列必须指定有界队列,然后配置好饱和丢弃策略,丢弃时打印日志进行监控等。
这里我想额外表达的就是参数的配置除了基本的结合业务qps的考量之外,一定还要考虑上下游线程池,大家可以看下一个外部请求进来,可能会横穿很多个线程池:
【原创】从4个9到5个9可用性调优-总章第11张
也就是说多个线程直接,大小口径一定要对齐,比较合理的是漏斗状的,约接近请求入口处,线程池越大;
你下面口子开的很大,上面开的很小是没有意义的;特别要注意的是,上面开的大,但是下面的池口子开的小,这个时候就会饱和丢弃,一定要观察线上饱和丢弃的日志,
很多时候是一些老配置,很早之前的业务规模小时没啥问题,但是业务大起来,某些被饱和丢弃业务受损,类似问题一开始总容易被忽略,如果你还没饱和丢弃日志定位问题是很难的,
所以线程池上下游口径的我呢提是要在平时重点梳理排查的,问题就在于这个不用天天梳理,且总容易被忘记,
没有人会记着这个事情,你说气人不气人,只能说谁负责任期内出事情,谁摊上谁倒霉了,你说气人不气人【表情 捂脸流泪】。
#3中间件线程池问题:Hystrix的线程池问题,新手很容易犯错误,往往就是拷贝一个注解头,参数简单改几下,他也不知道改了这几个参数代表啥。
我认为Hystrix最要考虑的就是隔离,即你结合下游接口,梳理重要性,如果这个接口是重要接口,则GroupKey一定要单独一个,避免与其它线程池复用而相互影响。

4.3.3.读写隔离

在工程层面,往往会有对内使用的后台运营工程,主要是配置写入;其次是对外的api工程,主要是查询;
此时基于CQRS架构,做命令查询职责分离,也就是工程层面的读写隔离;commend工程往往改动比较频繁,这样可以避免commend工程改动发布对query工程的影响。
第二个就是用户侧的读写,最后也进行隔离,这个也要结合业务规模,比如我们的业务场景,用户侧的读相比用户侧的写会高几个数量级,用户侧读工程会做大量性能优化,
也就是说用户侧的读写在设计上会有很大差异,此时用户侧写的功能也是单独机器部署了;这样也是做到隔离不相互影响。系统架构如下:
【原创】从4个9到5个9可用性调优-总章第12张

4.3.4.代码主次隔离

这个其实说降级更合理一点,如下两个图:

【原创】从4个9到5个9可用性调优-总章第13张

首先有个原则就是28原则,核心逻辑总是少的,可能只占20%的代码量,非核心的总是复杂且多,因为复杂且容易出错。

上面这个改造就只是简单加2个trycatch,但是带来效果确实明显的,80%非核心逻辑往往容易出错,但是通过trycatch保护不会影响核心内容逻辑的返回;
还是那句话,思路是最重要的,有了这个结构思路,就可以保证后续新代码都是基于这个结构,大家去新增代码逻辑也会做这个方面的思考。
如果已有代码逻辑没这个结构,开发同学又没这个思维意识,这个就特别容易被忽略。
但是改造好了,虽然是细节,对可用性是又实打实的帮助的。
所以看到这里的同学,就这一点,赶紧去梳理下线上核心业务场景,核心业务功能,改起来几分钟,对系统可用性帮助是巨大的,
哪天系统非核心部分逻辑除了问题,然后通过你现在的改造救命了,嘿嘿嘿,你立功了。

4.3.5.物理隔离

这个就是双机房部署了,公司规模小业务规模小可以先不考虑,
到一定规模时,运维团队肯定也有了,运维团队会主动推进这个事情的,业务开发团队要做的就是配合,
讲这个点就是说业务团队要意识到双机房部署的重要性,这个是极端情况下救命的,所以涉及这个的事情一定要积极配合,
特别是你觉得你负责的业务非常重要,那更要积极配合,有必要还得跟在运维团队后面催着他们早点搞这个事情。这样在极端情况下你又多了一条退路了。

4.3.6.一句话总结

【通过多级隔离改造,有效规避非核心业务异常对核心业务的影响,间接提升了核心业务的可用性】

Part4.4.上下游防护:

【原创】从4个9到5个9可用性调优-总章第14张

4.4.1.上游限流(入口限流)

  限流背后的思想核心是外部请求超过当前系统的负载,在保证系统可用性的前提下要进行限流保护。  
限流一般配合降级来使用,即被限流的请求你也不能啥也不返回,降级为一个提示也好的。  
限流可以在多个地方来控制,前端,网关,接口侧,【【详见这篇文章】】;这里着重说后台应用层限流。
首先还是不要做过渡设计,一定要弄明白是否需要加这个限流逻辑,按照我的理解,大部分要限流的场景是某个时间点有流量数量级式的突增,
2个核心业务关键字【定时】【福利】;即广大互联网用户在某个固定时间来抢福利;
技术侧的接口则是 上游固定时间批量调接口获取资源;脱离这2个业务场景,一般的业务集群都是能满足业务高点的计算要求的,没必要加限流的,逻辑多了反而维护复杂。
在有【定时】【福利】这2个类似概念的业务场景下,面对突增的你无法估计的流量,此时务必要加限流逻辑,
这里就是秒杀系统设计了,要考虑的非常多,推荐几篇文章【【详见这篇文章】;
就限流而言,有很多技术中间件,单点限流,集群限流,相信都难不倒你们,关键就是要做到可配置,能做到自适应那就完美了
此外,需要限流的接口,往往是重要接口,对于重要接口的设计,注意一定要简单!!! 千万别把这个接口的逻辑整的异常复杂,一定要简单明了,业务清清楚楚;
我遇到类似场景就是这个接口业务过于复杂,技术测还用了多线程,后向维护遇到问题排除也有较高成本。

4.4.2.下游熔断

需要梳理依赖的下游接口,特别是一些强依赖的核心接口,务必做好熔断处理,这个在关键时能救命:
在下游系统出现不可用时,如果接口没做熔断控制,此时你的系统会慢慢被拖垮,
原因就是调用下游系统的线程会被hang住,然后线程池资源耗尽,导致你的系统不可用,这就是典型的服务雪崩。详见【【详见这篇文章】】。
这个点要做的事情3个:
1个就是全面梳理依赖的接口,安装业务维度配置熔断线程池,注意不同的业务一个池,相同的业务则复用熔断线程池,别开太多的池。
2个就是根据调用量配置好线程池参数,注意上下游口径对齐。
3个就是熔断时降级逻辑业务体验要优雅,降级方法开发时也要测试到位,别线上出了问题需要熔断时发现降级方法不存在,还报一堆问题,那就尴尬了...

4.4.3.一句话总结

上下游防护,核心就是保证系统不过载运行,以及在下游系统不可以时能独善其身。
此外这也是个重要的开发意识,组内要宣贯到位,新增接口时,特别是重要依赖的接口必须要有所防护,做到有备无患。

Part4.5.运维监控:

  【原创】从4个9到5个9可用性调优-总章第15张

4.5.1.灰发环境

  引入线上灰度点检机制,建立规范,任何api网关上线修改,必须进行灰度点检。   
之前的发布环境是 测试》预发》有流量1台机器隔夜观察》全量线上。
3个环境独立隔离的,但是隔离环境的数据一致性问题,一直是一个老大难问题;
并且随着数据安全问题、用户隐私问题的关注提升,线上数据也不能随便导出,往往会导致测试环境因数据不完整,一些功能测试用例测试不到位,回归不规范;
关于灰度发布问题, 相关解决方案【【详见这篇文章】】,
我们这边是简单加了一个线上的灰发环境,这里数据库和线上是同一个,
然后规范:一些核心接口重要接口的变更,必须要在灰度环境上点检回归过,才能发布线上
这个机制也是帮我们点出了一些问题,到不是测试同学不专业,确实有些逻辑分支可能线上环境才能显现。
然后灰度点检通过过,到线上环境,用户流量机器先灰一台,对于一些大的变更,这个特别有必要,做一个隔夜观察,
这个意义主要是用用户流量来回归验证所有老的功能,看新的需求逻辑变化是否有影响老的功能,
一般如果一个晚上过去没新增明显重大异常,后面可以放心灰度及全量了。
如果万一有问题,那也只是一个机器有问题,比如我们集群是100台,影响的也只是1%的流量,总比全量上线了 100%好吧。
流程如下:
【原创】从4个9到5个9可用性调优-总章第16张

4.5.2.巡检机制

就是每天对线上的运行情况进行例行巡检,比如域名可用性,核心接口的qps, rt情况,jvm GC情况,中间件(Mysql redis)的运行情况;线上运行时的报错情况,等等。
这个尽量做成自动运维,如果没能力也可以做成人力每天运维,比如团队按周来排序,轮到的同学负责巡检一周。
这个巡检的意义是为了发现问题并线上处理,但是具体执行效果实在是千差万别,并且到后期非常容易流于形式。
比如线上预警,一开始大家肯定会积极处理的,但是到了后面大家见怪不怪就不咋关注了。
因此,预警一定不要报了太频繁,或者说要捡重点,避免【狼来了】的情况

4.5.3.异常预案

  分2点,第一是通用的异常时处理规范,及操作流程步骤流程;比如出了问题该第一时间知会/调动 哪些人,怎么一个处理步骤,这个结合实际情况必须要有。
第二是针对一些重要的异常场景,做针对性的操作预案;比如双机房部署,要切流量大致是一个怎么操作步骤;重新全量刷缓存大致是一个怎么步骤;
这些预案要全组都知道,因为我们不知道什么时候会异常,也不知道异常的时候能调动到谁。

4.5.4.日志优化

分2点:1个是优化日志数量,我们改造之前核心业务集群一天生成千万日志,其实很多日志实在没啥意义,
warn和info日志 尽量不要打到线上,很多开发人员就觉得我打这个日志有利于后续排除问题,
但其实这个“后续”ta自己都忘记了;特别是qps非常高的接口(打的越多你越不容易定位 因此还不如不打),更加不要打日志。
然后还要就是一些异步刷新的线程的日志,可以有一个灰度ip打印工具【【详见这里】】,
在配置中心指定ip, 然后机器识别到是自己后,只在这个机器打印相关日志,在集群规模大的时候,这个方法特别有效,日志量噌噌噌下来。
2个就是error日志分类处理,一般异常日志,大致可以分为3类。1-系统异常 2-上下游接口异常 3-运营配置异常;
#3的问题更容易出一点,很多时候运营少配置一个排期 一个组件,这个时候异常报警就直接短信/钉钉 通知到对应业务运营同学,这样就可以节省开发同学时间了。
#2就是上下游异常,比如你依赖的接口大量超时,这类问题告警直接给到对应依赖方同学那里,让他们来处理。
剩下的#1就是自己内部技术异常了,这个系统稳定的话其实往往比较少的,可能在发布时会空指针,那是新增代码逻辑的问题。
总而言之,通过日志优化,特别是第二点,提高线上问题的处理及时性的同时也是系统可用性的提示,同时也让开发同学可以有更多时间专注于业务开发。

5.调优结果

帮我们规避了2次S0故障。年内没有自身原因导致重大异常,达成了5个9可用性目标。

6.总结&感悟

总结:

本文主要涉及 缓存、GC、隔离、上下游防护、运维优化 这5给方面;沉淀了一套互联网业务集群的通用可用性优化方案。  
希望大家能从中受获。

感悟1:必须关注可用性

serviceMesh这几年很火,其所倡导的其中极重要一点:专注业务开发!解放业务人员对非技术内容的关注,提高业务开发效率。
思路是非常对的,效率这个词,所有团队都在追寻,有些时候关乎团队发展生死。
回到本章可用性,高可用 这个东西,听起来比较虚,但主要还是不够理解,因为可用性涉及的东西太多,涉及方方面面,
一个业务开发同学要弄清楚这些东西要花大精力,在开发时要去关注也要额外的花时间,这似乎与效率背道而驰了。
说白了业务开发是短期战术层面的,可用性是远期战略层面的,做这些事一下看不到效果。
所以关注可用性的这个角色往往都是组长(负责人);或者说组长必须要有这方面的意识,明白可用性是非常重要的,
负责人要有一套关于可用性的体系化的思想,这对于平时安排需求 & 可用性 等的时间决策,人员调度 ,决策时多少会有影响;
有些负责人非技术出生,基本只关注需求的落地,追求业务需求的进度,这个固然好,但是我理解这样走不长吧,或者以后肯定会栽跟头的;
还是28原则,结合业务体量,业务发展阶段,可用性技术建设这一块,20%的投入应该算不错了,5%其实也行,
我上面梳理这么多点,很多方面改起来很容易的,但这个意识不能没有。
再回到serviceMesh所倡导的,其实要真正的让一个业务团队不去操心可用性的问题,特别是业务规模大起来后,是不可能不用去关注的,
哪怕你是severless云原生技术体系,毕竟业务代码还是你自己写的,在使用各个组件时,如果是简单堆在一起,肯定会有一些问题的。
最后给个总结:可用性这个东西:
1.在业务发展到一定规模是必须要引起足够重视
2.团队leader对可用性必须敏感,知道其战略意义
3.建议团队里最好要有一位老司机专门来负责系统可用性这方面的建设
4.投入精力不会很多,但是要持续关注,持续投入
5.云原生是未来趋势,但我以为最近几年还是要业务团队自己来负责系统的可用性

感悟2:效率 VS 规范

  写这篇文件,梳理可用性的思路,还是从技术侧角度梳理,技术的5个方面,大家在具体实践时要结合实际场景。  
这里提供另一个思维角度,可以围绕开发流程中的规范来管理可用性,如:    
开发规范》自测规范》测试规范》发布规范》运行中巡检规范 》异常处理规范
协同规范  
以上都是一些规范,规范这个东西,其实就是风控,与效率是一体的两面,让人又爱又恨。  
与上面感悟1里提到的,效率是战术层面,规范是战略层面,深层思想是呼应的。  
所以说,可用性还是要到业务发展到一定规模,规范慢慢搭建起来,leader们要逐步重视相关规范,从这个角度,感觉又是顺势而为了。

感悟3:系统优化像团队管理

一个系统就像一个团队,系统内的各个技术组件就像团队成员;  
技术组件不是简单地杂乱地堆在一起,而应该是通过精心设计、有序地契合在一起;这样一个系统才能体现出高性能 高可用;  
类似于一个团队,一帮人聚在一起能干事是最基本的要求;但是我们更希望这个团队 高效,健康,长久,  
所以团队管理人员需要额外花很多时间来营造团队氛围,“调优”团队效率;  
系统亦是如此。

版本日志:
日期内容
2021.3.3初始发布
2021.3.4

1.优化运维优化章节内容

2.添加适用场景,人员

3.补充总结&感悟

免责声明:文章转载自《【原创】从4个9到5个9可用性调优-总章》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇c# datagridviewokhttp添加自定义cookie下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

python之线程与线程池

# 进程是资源分配的最小单位,线程是CPU调度的最小单位.每一个进程中至少有一个线程。 # 传统的不确切使用线程的程序称为只含有一个线程或单线程程序,而可以使用线程的程序被称为多线程程序,在程序中使用一个线程的方法 # 被称为多线程 # 线程的模块: # thread >> 实现线程的低级接口 # threading>>>...

(转)mysql8.0配置文件优化

原文:https://www.cnblogs.com/john-xiong/p/12099842.html 原文:https://www.modb.pro/db/22572----MySQL8.0自适应参数innodb_dedicated_server 原文:https://www.cnblogs.com/zwbsoft/p/13791424.html--...

建立高并发模型需要考虑的点

1、能不能通过增加机群(应用机群,服务机群)的方式去解决?好比一台机器能抗200qps, 然后你就40000qps的业务,那你最少需要200台机器,如果考虑到有机器down掉的情况,还要加备用服务器,这个具体加多少台就得去评估了。     防止出现有机器down掉,还得在每台机器上更新列表的情况,我们最好访问服务名(类似于域名),这样的话的好处就是,如果出...

【JavaWeb学习】过滤器Filter

一、简介 Filter也称之为过滤器,它是Servlet技术中最激动人心的技术,WEB开发人员通过Filter技术,对web服务器管理的所有web资源:例如Jsp, Servlet, 静态图片文件或静态 html 文件等进行拦截,从而实现一些特殊的功能。例如实现URL级别的权限访问控制、过滤敏感词汇、压缩响应信息等一些高级功能。 Servlet API中提...

异步任务分发模块Celery

Celery简介 Celery是一个功能完备即插即用的任务队列。它使得我们不需要考虑复杂的问题,使用非常简单。 celery适用异步处理问题,当遇到发送邮件、或者文件上传, 图像处理等等一些比较耗时的操作,我们可将其异步执行,这样用户不需要等待很久,提高用户体验。 celery的特点是: 简单,易于使用和维护,有丰富的文档。 高效,单个celery进程每...

Django框架深入了解——Django中的缓存

Django框架深入了解——Django中的缓存 一、Django中的缓存: 前言: ​ 在动态网站中,用户所有的请求,服务器都会去数据库中进行相应的增,删,查,改,渲染模板,执行业务逻辑,最后生成用户看到的页面. 当一个网站的用户访问量很大的时候,每一次的的后台操作,都会消耗很多的服务端资源,所以必须使用缓存来减轻后端服务器的压力. 缓存是将一些常用的数...