大话接口性能优化策略
前导
接口性能优化,这个在长期的开发中都是一直持续要做的事情,因为随着业务增长,用户使用量增加,业务复杂度增加,业务表数据量和请求量的增加,接口从原来 100ms 左右,逐渐变成几百 ms,甚至个别请求到几 s,所以我们就需要对业务中,平均请求响应时间比较慢的接口进行性能优化
筛选性能问题的接口,我们的规则是所有请求的 95 分位中,超过 300ms 的占比较大的接口就需要进行优化了,至于占多少比列就要要开始优化,我觉得还是要看具体看接口在业务中的重要性,有时间竟可能的把 95 分位中 300ms+占比>=1%的接口,进行优化
先排查 mysql
在优化接口前,需要对导致接口性能问题的位置进行定位,根据经验,一般导致接口性能的很大一部分问题就是 sql 的慢查询,比如:部分用户产生的数据较多,导致这部分用户查询较慢,或者表数据量较大,没有设计索引,或者有索引,但是因为查询条件,排序条件等,无法有效的利用到索引
慢查询排查
我们的慢语句有进行 ELK 收集,mysql 配置了 slow_query_time>=1s 的 进行 slow_query_log 记录,在 kibana 中对 mysql_slow group 中进行查询当前接口设计的大表的慢语句查询,看看是否有相关慢语句记录,拿到慢语句后,进行 mysql explain 性能分析,看下查询使用哪个索引:key,然后看下查询是否高效:type,看下预估数据行数:rows,查询附加条件:extra,通过查询分析,对索引进行优化,或者对查询语句进行优化,充分利用索引,让查询更佳的高效
具体索引 explain 的详细说明和优化策略参考:
冗余常用关联表字段
对于部分业务接口中,涉及关联其他表查询部分字段等,将其中使用率较高,并且不会频繁更新的关联字段,适当设置冗余字段到业务表中,这样可以省去关联查询时间
存储统计数据
对于涉及表统计查询的接口,可以将查询中,统计结果变化频率不高的查询,进行统计结果的存储(表存储/redis 缓存),一旦相关数据产生变化就进行重置/删除,查询的时候优先从存储中获得统计结果,不存在进行 select 查询,并更新到存储中,存在就直接返回存储的统计结果,通过这种方式,可以省去频繁的统计查询时间
存储查询条件字段
存储查询条件的目的是,可以通过存储的查询条件字段,保障后续查询的时候利用上这个条件,将查询范围缩小,并且可以重复利用索引条件,进行高效查询,如:
-- 消息表 srv_msg,索引:idx_user_id_msg_at
-- status=1 新消息,2 待读,3 已读
-- 查询待读消息 + 已读的历史消息
SELECT id,user_id,msg,status,msg_at,create_at,update_at
FROM srv_msg
WHERE user_id=1 AND msg_at<:msg_at AND status IN(1,2)
ORDER BY msg_at DESC
-- 用户进入到消息列表,访问第一页时
-- 消息更新为待读
UPDATE srv_msg
SET status=3
WHERE user_id=1 AND status=2
-- 待读消息更新成已读
UPDATE srv_msg
SET status=2
WHERE user_id=1 AND status=1
当消息表到千万,并且部分用户的消息特别多,发现这部分用户 update 的效率很低,explain 下查询,type=ref,key=idx_user_id_msg_at,filtered=10.00, row=1w+,很明了,虽然有使用索引,但是查询涉及查询行数过多,所以导致更新查询效率很慢
索引只利用了 user_id,msg_at 没有用到,也不合适将 status 加入到索引中,那要怎么办呢? 最后的优化方案是,将用户进入到消息首页的时间,进行存储到用户相关附加表的一个字段中(last_msg_at), 然后 update 条件加上 msg_at<last_msg_at , 这样就可以充分利用索引,过滤掉大部分数据,加上这个条件后,再 explain row=个位数
大表归档
随着业务增长,数据量爆增,单表数据达到大几亿,并且占用存储空间也比较大,索引效率变低,增对这种场景,我们要如何节约服务器资源,并且又可以不影响查询效率呢?
方案 1,表归档+压缩
根据一定的纬度对表进行垂直拆分,归档,比如:消息表,用户一般就关心最近的新消息,很少会去查询历史较长的消息信息,可以将表拆分成主表和历史表
insert 写入到主表里,大于一个月的数据,DBA 通过数据迁移工具(pt-archiver 数据归档利器),定时将超出时间的数据迁移到历史消息表中
然后业务接口进行调整,优先从主表中查询消息,如果不足分页,在查询历史表
这样处理后,大部分的用户都是查看最近一个月的消息,所以大部分查询都是命中主表,并且主表的数量会比较少,提高了查询的效率
针对这种类型的业务大表,可以根据时间进行表拆分,然后查询的时候,进行年份,或者历史查询,再查询到对应的归档表中
对于大表归档后,因为表数量较多,但是使用率不高,同时又占用比较大的存储资源,这里可以同时对大表进行表压缩
压缩表从名字上来看,简单理解为压缩后的表,也就是把原始表根据一定的压缩算法按照一定的压缩比率压缩后生成的表,可以节省磁盘 IO,减少网络 IO
将表的 row_format 设置为 compressed,通过指定 key_block_size,来设置压缩表的页大小,设置后可以对大表压缩率大约为 50%
参考:
方案 2,表分片
通过一定的纬度,如:用户 ID 取模分表,将不同的模的数据分配到不同的表中,通过大表水平拆分多张小表,从而达到提高查询效率的效果
水平拆分会增加开发的复杂度,并且需要进行数据的迁移,以及考虑后续扩容,所以不到万不得已,不要过度设计
- proxy 层
- client 层
参考:
增加缓存
对于查询请求量较大的业务接口,并且数据变化频率不高,可以添加数据缓存,减少对数据库的频繁查询
缓存可以做多级,比如:服务端缓存+redis 缓存,优先从 web 应用所在的服务器缓存中获取数据,不存在再从 redis 中获取,然后再从 db 中获取,具体缓存方案和缓存策略,根据具体的业务设计解决方案
异步编程
通过异步线程/golang 协程,对查询较多并且相互之前不会互相依赖的查询操作,进行异步并行查询处理,然后等待多个数据查询都处理完成后,在聚合数据输出,通过并行的查询,减少了串行的相互等待的时间
异步消费
对于非主要流程的业务操作,并且可以允许异步处理,可以将数据入列到队列中,然后通过脚本进程进行异步消费,减少非主业务流程的一些附加业务操作,占用请求的时间,这样调整后,让接口功能佳清晰,同时可以达到业务之间的解耦效果
压测
最后呢,在优化前先进行几波不用强度的压测,然后记录下各项指标(qps,平均响应时间,等),优化完成后再进行压测,然后对比指标,看看优化后的效果是否达到期望,然后再上线,进行线上跟进,这样接口优化的整个过程就结束了