性能&稳定性

性能&稳定性

性能

  1. 上缓存:订阅binlog写redis,通过监控找出大流量的上游调用方,新增一套缓存接口,让他们切接口
  2. 清理慢SQL:是新建索引还是合并索引,如果MySQL搞不定就用ES,中间也曾探索过用MySQL解决深度分页
  3. 接口逻辑修改:商品的缓存查询接口用到redis mget去批量获取,之前不分批次一把梭,发现极端场景下,上游一次性查询200个商品会阻塞Redis造成毛刺,后来分成50个一批次好了很多;还有有些场景要去除事务,比如方法中有RPC请求的时候,会把事务拉的很长引发一些问题
  4. 日志处理:
    一开始的时候,公司common包的日志冗余度高,还有些日志超长不截断,一个小时单机就有七八个G的日志,写拦截器把日志精简到1G;
    还有异步打印日志,不过要处理好traceId的透传问题,之前线上也出过问题,商品中台一发版中央库存就超时报警,超时接口很简单就查一下redis,只有小部分机器接口超时,而Redis一切正常。经过仔细排查发现,有些容器的物理机磁盘有瑕疵,发版的时候磁盘IO一般比较高,磁盘问题暴露会严重阻塞,在同步打印日志时,把用户请求阻塞了,容器漂移到其他物理机就好了。
  5. gc调优:选择合适的垃圾回收器,合理调整gc线程数,还有新生代/老年代比例,Eden区和Survivor区的比例,得到一个可以接受的gc频次和暂停时间,C端要避免Full gc。
    比如:我刚来橙心的时候,有上游反馈接口经常超时,排查之后发现young gc频次平均20秒一次这个正常,但是每次young gc耗时都要上百ms这个不正常,一般来说4核8G的容器young gc耗时也就20ms。jstack发现gc线程数竟然有四五十个太多了,启动脚本没有主动配置gc线程数(ParallelGCThreads),那jdk8的老版本有自己默认的计算公式,跟cpu核数有关,但是docker容器化场景下,取的不是容器的cpu核数,而是物理机的cpu核数,而我们的物理机有64核,所以计算出来的gc线程数就是四五十个。把gc线程数写死成4个,问题立马就好了,停顿时间缩短到20ms

稳定性

所谓稳定性,就是不要被上游搞死,自己呢不作死,也不要把下游搞死

【压测&限流】
压测就是摸底,限流就是自我保护

集群压测:压一下核心接口性能瓶颈——到底数据库容量不够还是容器数量不够,摸个底

比如:

  1. 查询接口的瓶颈一般是docker容器不够,当大流量来临可以临时扩容:
  2. 写接口的瓶颈一般是数据库TPS不够。比如下单扣库存,我们以前的做法是Redis扣库存然后同步在MySQL插一条幂等记录,结果TPS压到3000多,主从延迟很严重了;后来改成Redis加锁Redis扣库存,可以压到1万多(数据库没有拆片,拆片之后还可以翻几倍)。
  3. 集群压测的细节也有很多:
    比如按照1000的步长逐步增压,还是一瞬间从2000涨到1万,都要测。因为秒杀场景就是徒增,日常场景就是稳步增压。很多时候逐步增压能到1万TPS,但是从2000直接攀升到1万,集群可能会卡顿,要找到原因做优化,优化不动了就限流兜底。

而且线上的流量模型是复杂多变的,单接口压测或者几个接口同时压测,并不能百分百模拟线上流量,这些需要专业的qa同学去搞,可以通过流量录制和回放做压测

哨兵压测:针对单个容器,压一下单机的极限,也可以用来预估集群数量。比如单机极限QPS500,那理论上集群要达到10万的QPS,只需要200台机器就够了,再预留30%的余量

比如:
其他99台机器都是100的流量权重,而某一台机器的权重加到300、400,慢慢增加,看一下cpu idle、Tomcat繁忙线程数、接口RT、GC等指标,在不影响上游的前提下他的极限。

【监控&有效报警】两类指标&3个大盘&灭火图
线上问题,尽量在用户或上游反馈之前发现问题,第一时间在稳定性大群里通报,让相关人员紧急介入。报警来源可以是监控指标,也可以是日志埋点,或者ERROR日志采集

监控:

  1. 系统指标监控:cpu idle,线程数,jvm指标,核心接口QPS/RT同比变化异常,mysql、Redis、MQ等监控
  2. 业务指标监控:下单成功率同比下降多少,一般通过错误码或日志埋点统计
  3. 监控大盘:通用的监控大盘(系统指标监控+业务指标监控)——日常巡检,发版监控大盘(因为机器数量太多,通用大盘看不清),快速排错的监控大盘(常见的问题指标都会配置到大盘里,快速解决过很多问题)

比如:有一次,发现一台C端的机器full gc异常,平均2小时一次full gc,而其他机器好几天都不会有一次full gc。解决过程很简单,先把大盘时间段缩小到第一次full gc前后两个小时,很快发现这台机器的线程数在某个时间点从200爆涨到1000;然后时间再聚焦到线程徒增的那几分钟,发现cpu假死了一会,把线程一下子卡满了。
如果没有排错大盘,这个问题排查比较困难,单凭经验,很难想到full gc异常是因为线程数过多导致的,而线程数过多是因为cpu假死导致的,而且在两天的时间内线程数一直降不下来,当然里边细节很多,还牵扯到gc日志和Tomcat线程回收机制,暂时不展开了。

【故障注入演练】
属于混沌工程的领域,在公司的放火平台做故障注入,并观测系统及上下游的反应,找出薄弱环节。
目标:提前暴露问题并给出预案,不要等到线上出事故了才被动止损,上医治未病,防患于未然。
比如:
1.切断第三方链路
2.人为把主从延迟加剧,看一下异常,很多人代码里喜欢先update再select,一旦没有事务select就容易查出旧数据
3.核心接口RT从正常20ms延迟到100ms,看下上游报警和降级机制是否完善,超时会不会把他们线程池打满项目拖垮
4.上游请求做高并发的重放校验幂等机制
总结:故障注入测试风险高,而且耗费精力,最好是技术评审的时候,通过经验指出问题,在代码开发和自测阶段尽量做好。很多厉害的系统,会不定时的自动做故障注入实验,校验系统的健壮性,倒逼着程序员在开发阶段就主动考虑这些场景,行成正向循环。

【平滑发版&灰度发布】

  1. 没有快速回滚的方案,不允许上线!重要变更要做好灰度发布!
  2. 发版不要有正常业务的请求毛刺,公司的LVS负载均衡还有开源的springboot都不支持流量的权重预热,导致项目启动成功后,流量是正常进入,之前单机200QPS那流量进场时也是200QPS,会有很多RT毛刺。我们一发版,上游就报警了。

我临时用了个笨方法,在发版脚本里边,把大流量的接口手动curl调用几轮,然后再退出发版脚本,流量再进场的时候,就没那么大的毛刺了,方法很笨但效果还不错

【熔断&降级】

  1. 之前在商品中台基本是C端请求的最下游了,主要考虑Redis高延迟场景下的降级就好,我们可以切换到集团自研的Fusion数据库。
  2. 我现在负责的奖励中心,是一个发奖的聚合中心,上游所有的任务和活动发奖,都走奖励中心,而奖励中心去统一对接下游的风控中心、触达中心,还有很多真正发奖的下游:权益、清结算、微信、网约车等。

作为一个发奖的聚合中心对熔断降级要求很高,因为下游很多,所以用了hystrix的线程池隔离方案,配好每一个下游的超时时间和错误窗口防止被下游拖死。
风控接口挂了,如果某些奖励在这种场景下是允许跳过风控的,那可以自动降级跳过
如果某类奖励在发放过程中出现问题,需要紧急拦截止损,就需要留下后门人工降级跳过
如果某类奖励在发放过程中一直失败,导致MQ不断重试积压,影响其他奖励的发放,也需要留下后门做人工降级跳过

【B/C端分离】
B端业务容易造成full gc,比如定时任务或者后门接口刷数据,处理不好就容易滋生问题。而且B端业务变化频繁,经常发版,影响C端正常业务的性能和稳定性,所以业务到一定规模,就要B/C端分离。甚至一个B端系统,还要拆分出稳定查询类的B端系统和需求变化频繁的B端系统,算是一种动静分离。

【复盘】
所有的线上事故,都要做深刻的复盘,所有时间轴和事件要描述清楚,类似问题不允许再发生

binlog方式更新缓存

背景:几十处商品修改的代码,业务发展前期工单刷数据的行为非常频繁。

  1. 消费速度3000QPS,生产速度200不到,正常延迟时间在50ms以内,控制好生产速度即可,提工单刷数据是500个一批次然后sleep1秒。传统方案中,改数据库删缓存,中间也会有毫秒级的时间差数据不一致
  2. 数据实时性要求高的接口不走缓存接口。比如下单结算接口
  3. 解决了传统方案中,提工单刷数据不走代码,缓存长时间不失效的问题,除非自己写后门接口刷
  4. 原则上,上架中的商品B端不允许修改,要修改需重新上架
  5. 下单扣的是Redis库存
  6. 未来重构的时候,可以考虑换成传统方案,但运行了1年发现还是挺稳的,或者两种方案可以混用,缓存延迟敏感的场景下使用传统方案

你可能感兴趣的:(性能&稳定性)