Java接口全链路优化:如何下降接口RT时长

背景:因为之前的应用多且杂,因此最近对公司的应用进行优化改造,须要全部接口RT达到xxx值如下。java

1、监控

那么问题来了~如今应用都是放养式的,几乎没有什么监控工具,不可能根据log一个接口一个接口去捞日志,那怎么知道哪些接口rt长,须要优化呢。 因此第一步咱们作的事情就是上监控。git

监控工具:pinpoint。程序员

选择pinpoint有几个方面的考量:github

1.对应用代码0侵入,这个固然是咱们程序员最关心的,谁都不喜欢由于附加功能在本身的应用大动干戈,万一影响原有业务就不值得了。。算法

2.应用依赖关系,调用关系一目了然,经过一个traceId将全部流程串起来,方便排查问题。这一点也是很是重要,在分布式系统中,调用关系请求太复杂,若是没有traceId标识,你是很难找到这个请求的上层调用关系的。sql

3.接口各类监控图表,固然也包括RT图表,便于快速定位须要优化的链路。数据库

pinpoint的源码及教程可参考https://github.com/naver/pinpoint。编程

咱们能够大体看一下pinpoint的监控页面,以下图缓存

从图中咱们能得出:

调用链路一列看出该请求实际执行了2次SQL。并发

执行时间一列看出第一次耗时19ms,第二次722ms。

traceId是这次请求的惟一标识,若是咱们在应用须要记录该值,也可经过对应API获取。

固然这个示例只是一次很简单的请求,他还能监控dubbo调用、http请求、Redis、db等等操做,因此只要应用配置了它,那么该请求执行的调用细节就一目了然,尽在咱们掌握之中,而不是放养任其发展。

2、优化

既然目标已经被咱们揪出来了,那咱们下一步固然就是把它解决掉,优化它。我从优化过程当中得出一些很广泛,实用的优化经验,特别是对于初学者,能够多多参考。由于对于刚开始编程的来讲这些就是习惯,正常的逻辑,但并不表明它就是最优的。

1.循环SQL操做

这类状况对于新手来讲很容易犯,好比就建立交易订单。咱们主子单关系以下:

那么建立订单须要生成1个主单,N个子单,这时不少这么写(代码仅表示执行流程):

insert(主单);
for(子单:子单列表){
    insert(子单);
}
复制代码

因此就出现循环SQL操做,由于SQL操做比较耗时,循环的话就会大大拉大整个接口的rt时长,这种状况咱们应该一次性把全部子单insert,而不是一次一次地操做,优化后代码相似为这样:

insert(主单);
batchInsert(子单列表);
复制代码

Mybatits是支持循环标签的,因此在sqlMap文件里改造一下SQL就能够了。另外批量update也是能够的,执行批量操做须要在数据库连接加上参数allowMultiQueries=true

2.数据库索引

索引固然是必需要建的,否则得查到何时。不过建归建,可是咱们还要正确的运用它。 咱们能够经过explain命令检测SQL是否使用索引。 EXPLAIN SELECT * FROM article WHERE id=10;

key和rows反应了使用的索引以及预计须要扫描的行数。 还有一些咱们须要注意的:

在咱们优化Query语句中的ORDER BY的时候,尽量利用已有的索引来避免实际的排序计算,能够很大幅度的提高 ORDER BY操做的性能。在有些 Query 的优化过程当中,即便为了不实际的排序操做而调整索引字段的顺序,甚至是增长索引字段也是值得的 由于MySQL中,order by的实现有两种类型:

1).一种是经过有序索引而直接取得有序的数据,这样不用进行任何排序操做便可获得知足客户端 要求的有序数据返回给客户端;

2).一种则须要经过 MySQL 的排序算法将存储引擎中返回的数据进行排序而后再将排序后的数 据返回给客户端

咱们建的索引为 user_id, define_id, seq联合索引,能够看出第一张图排序define_id直接走索引,第二张图走不了索引须要额外的排序操做Using filesort.group by 其实也是进行了order by操做 而后进行分组,因此group by也是相似的优化方式。

DISTINCT尽可能少用,DISTINCT其实也是进行了一次group by操做,而后每一组取的第一条记录。

java应用的类型必定和数据库索引列的类型匹配,例如java类型为long,数据库类型为varchar,这样去查询是用不了索引的,可是不会报错。 下图是两种方式的对比:

3.count计数千万不能用

计数也是咱们常常用到的,好比优惠券领取数量,统计活动参加人数。这些有时是和业务强耦合的。好比秒杀只能卖出多少件等等。这类场景咱们千万不能使用count来计数。咱们来分析一下为何不使用count:

1).count会查询全部知足条件的记录,若是表很是大,这将可能致使全表扫描,这后果你们都知道的吧。 2).若是使用count来控制,那么在业务逻辑执行期间,确定要加锁,不然刚才count的结果就白操做了,这将也会阻止全部对该活动的请求。

这种场景咱们通常都是在须要控制的记录上加个计数字段,好比控制最大领取值num=10个。那在业务逻辑里面可将对总数控制转为经过SQL的方式, update xxx set num=num-1 where id=xx and num>0; 这是原子操做,可保证必定不会超领。根据返回结果判断是否执行成功(返回结果为影响的行数)。

4.锁

单机锁和分布式锁,锁其实咱们可以避免就不要使用它,由于加锁就表明只能串行执行,并发数降为1,这确定影响性能。

若是是相似库存扣减的场景,可参考第3条。经过数据库的原子操做来避免。
若是是更新等操做,可经过乐观锁来避免长时间的阻塞。
若是非要使用锁,不论是单机锁仍是分布式锁,咱们必定要评估该锁的影响范围,是针对单个用户userId仍是全部的用户userId,单个用户是能够采用的,由于单个用户并发度极低,可是若是是全部用户的操做加锁,那必定要好好评估,这个操做会致使全部用户的相似操做阻塞。

5.适当冗余

在分布式系统中,冗余应该是很是常见的状况。咱们这个时候就不要追求数据库范式的标准了,由于按照数据库第几范式来设计,全部字段都不冗余,可是这给咱们查询带来很大麻烦。可能咱们查一个订单,须要关联查询,查子单信息,查商品信息,查支付信息等等,查询这么多,想一想咱们接口的rt能快吗,qps能高吗。将商品名称等基本信息冗余可减小对其实模块的查询,何尝不是一种好的方式。

6.Redis缓存

其实缓存在咱们系统广泛都用到了,因此对这部分优化很少。仍是总结一些经验。 缓存的刷新策略选择:失效刷新仍是定时刷新。 由于监控到不少接口RT老是有规律的变慢,这是由于都是在缓存失效的时候,须要从db及其余模块组装数据,而后推到缓存,这时全部请求都走不了缓存,在流量大的时候也有可能成为致命的因素。若是是这种状况,例如首页推荐商品、推荐帖子等等访问量大且相同的场景能够经过定时刷新的方式。 Keys*命令线上严禁使用:Redis是单线程,该命令的执行将会致使全部后续请求阻塞,影响整个系统性能。

7.搜索引擎

可能看到这个比较疑惑,搜索引擎怎么还能优化RT,他比DB固然慢,可是在某些场景他能够比DB更快。例如一个社区论坛,须要对文章进行筛选排序,总共十来个字段,并且自由组合,这时DB就无能为力,由于条件太多,各类排序操做,关联操做,数据库没办法建索引。咱们能够经过将A、B表中的字段构建成完整的信息,推送到搜索引擎,查询的时候直接根据条件搜索。这样既能保证rt,又能避免DB被复杂查询拖垮。

相关文章
相关标签/搜索