接口优化
线上接口很慢,线上生产问题,我们绝对不能马虎放过抱着侥幸心理,必须要找到根本原因及时处理,防止下次留下更大的坑.大致思路要定位接口问题,然后具体问题具体分析,讨论不同解决方案.
定位问题
要快速定位接口哪一个环节比较慢,性能瓶颈在哪里,可以使用应用性能监控工具(APM)定位问题。常见工具: skywalking、pinpoint、cat、zipkin。
如果应用程序没有接入APM,可以在生产环境装一下arthas,利用trace接口方法和火焰图,大概能分析是那一块比较慢,定位能力稍微有点粗糙。亦可以利用程序中的告警日志定位问题。
解决办法
- 扩容;哪里扛不住了哪里扩容,应用自动扩容、redis扩容、mysql在线扩容、kafka分区扩容等;
- 应用重启;如果部分节点线程已经扛不住了,就需要重启释放对应资源;
- 优化代码逻辑;上面两种是比较应急的做法,如果已经定位出来,就需要优化代码逻辑,完成后走hotfix灰度发版;
常见优化接口性能方案
数据库慢sql
如果是数据库sql慢,可以使用执行计划去分析一下,常见sql慢的几种情况:
- 锁表;先把锁表的sqlkill一波,在分析具体原因;
- 未加索引;添加索引,有可能会锁表,引发一系列问题,需要综合评估;
- 索引失效;分析索引失效原因,如:索引列区分度(值大都相同)很低、索引列大量空值、对所索引列加方法转换等;
- 小表驱动大表;在连接查询时尽量过滤数据,使用小表驱动大表,使笛卡尔积尽量小一些;
- sql太复杂;join超过3张表或者子查询比较多,建议拆分为多个sql,接口间相互调用;比如先从某个著接口查询某个表数据,然后关联字段作为条件从另一个表查询,进行内存拼接;
- 返回的数据量数据太多;当超过数据库一定限制的时候返回大量数据就会很慢,可以使用分页多批次完成,针对访问量不多的接口可使用多线程批量查询;
- 单表数据量太大;(mysql超500w较慢)如果单表数据量较大,考虑在数据库设计做文章,如:分片分库、利用es存储等;
调用第三方接口慢
- 设置合理的超时时间;调用第三方接口一定要设置合理的超时时间,在设置时一定要大于调用接口的平均相应时间;
- 第三方接口大量超时;可以集成sentinel或hystrix限流熔断框架,防止第三方接口拖垮自己的接口(兜底逻辑);
- 事务型操作根据实际情况决定是否采用补偿机制(本地消息表);比如新增、修改等操作要考虑对方接口是否支持幂等,防止超发;
- 循环调用,改为单次批量调用,减少IO损耗;如:调用根据id查询单条数据的接口,可优化为批量查询接口;
- 缓存查询结果;考虑当前查询结果是否能做缓存,如用户信息等短时间内不会变化的信息,根据业务形态来决定;
中间件慢
- redis慢;是否有大key、热key,可接入hotkeys监控;针对热key可以使用本地缓存来抗,针对大key可以将其拆分,采用set结构的sismember等方法
- kafka慢;生产端慢:可以使用堵塞队列接收,批量丢消息;消费端慢:消费端慢会造成消息积压,可以扩分区、增加消费节点、增加消费线程,用数据机构接受批量写入库;
程序逻辑慢
- 非法校验逻辑前置;避免无用数据穿透小号系统资源,减少无效调用;
- 循环调用改为单次批量调用;在查询数据库或调用第三方接口,能批量就批量,数据在内存组装处理;
- 同步调用改为异步调用;在接口没有相互依赖的关系的时候可以将其优化为异步查询;
- 非核心逻辑剥离;将接口的大事务拆分为小事务,一些非核心逻辑可以异步处理,可以使用mq异步解耦;
- 线程池合理设置参数;不要使用JDK默认参数,如果在高并发的情况下容易OOM,线程池满了以后要重写拒绝策略,考虑告警加数据持久化处理;
- 锁合理设置;本地读写锁设计使用不合理,要控制锁的力度,尽量小一些;分布式锁合理使用防止热key;
- 优化GC参数;考虑GC是否频繁,调整GC算法,新生代老年代比例,根据长时间观察可以设置出来;
- 只打印必要日志;当并发量比较高的时候打印日志也会损耗性能,所以日志应加上开关能不打就不打;
架构优化
- 高并发读逻辑走redis,尽可能不要穿透到DB;redis查询不到也不要查DB,可通过定时任务,MQ写入redis。尽量不要把风险给DB,DB如果挂了整个应用就用不了了;
- 设计写逻辑数据,尽量异步、批量处理、分库分表提升写入性能;
- 接口接入限流熔断兜底;
- 接入监控告警;error日志告警、接口慢查询或者不可用或限流熔断告警、DB告警、中间件告警、应用系统告警等;
- 接口需要加动态配置开关;能够快速切断流量或降级某些非核心服务调用;
- 设计程序自愈能力;比如如果数据有问题,用配置好的程序逻辑自动去修复;