高性能设计会涉及到几个名词:IO多路复用、零拷贝、线程池、冗余等,其本质上是一个系统性的问题,可以从计算机体系结构的底层原来去思考,系统优化离不开CPU和IO两个维度,具体如下:
- 如何设计高性能计算(CPU):减少计算成本,合理使用同步/异步、限流减少请求次数等;让更多的核参与计算: 多线程代替单线程、集群代替单机等等;
- 如何提升系统IO:加快IO速度,顺序读写代替随机读写、硬件上SSD提升等;减少IO次数,索引/分布式计算代替全表扫描、零拷贝减少IO复制次数、DB批量读写、分库分表增加连接数等;减少IO存储,数据过期策略、合理使用内存、缓存、DB等中间件,做好消息压缩等。
1、减少程序计算复杂度
boolean result = true;
// 循环遍历请求的requests, 判断如果是A业务且A业务未达到终态返回false, 否则返回true
for(Requet request: requests){
// 1. query DB 获取TestDO
String id = request.getId();
TestDO testDO = queryDOById(id);
// 2. 如果是A业务且testDO未到达中态记录为false
if(StringUtils.equals("A", request.getBizType())){
// check是否到达终态
if(!StringUtils.equals("FINISHED", testDO.getStatus)){
result = result && false;
}
}
}
return result;
代码中存在很明显的几个问题:
1.每次请求过来在第6行都去查询DB,但是在第8行对请求做了判断和筛选,导致第6行的代码计算资源浪费,而且第6行访问DAO数据,是一个比较耗时的操作,可以先判断业务是否属于A再去查询DB;
2.当前的需求是只要有一个A业务未到达终态即可返回false, 11行可以在拿到false之后,直接break,减少计算次数;
优化后的代码:
boolean result = true;
// 循环遍历请求的requests, 判断如果是A业务且A业务未达到终态返回false, 否则返回true
for(Requet request: requests){
// 1. 不是A业务的不走查询DB的逻辑
if(!StringUtils.equals("A", request.getBizType())){
continue;
}
// 2. query DB 获取TestDO
String id = request.getId();
TestDO testDO = queryDOById(id);
// check是否到达终态
if(!StringUtils.equals("FINISHED", testDO.getStatus)){
result = false;
break;
}
}
return result;
优化之后的计算耗时从平均270.75ms-->40.5ms
日常优化代码可以用ARTHAS工具分析下程序的调用耗时,耗时大的任务尽可能做好过滤,减少不必要的系统调用。
2、合理使用同步异步
分析业务链路中,哪些需要同步等待结果,哪些不需要,核心依赖的调度可以同步,非核心依赖尽量异步。
场景:从链路上看A系统调用B系统,B系统调用C系统完成计算再把结论返回给A,A系统超时时间400ms,通常A系统调用B系统300ms,B系统调用C系统200ms。
现在C系统需要将调用结论返回给D系统,耗时150ms
此时A系统- B系统- C系统已有的调用链路可能会超时失败,因为引入D系统之后,耗时增加了150ms,整个过程是同步调用的,因此需要C系统将调用D系统更新结论的非强依赖改成异步调用。
// C系统调用D系统更新结果
featureThreadPool.execute(()->{
try{
dSystemClient.updateResult(resultDTO);
}catch (Exception exception){
LogUtil.error(exception, logger, "dSystemClient.updateResult failed! resultDTO = {0}", JSON.toJSONString(resultDTO));
}
});
3、做好限流保护
故障场景:A系统调用B系统查询异常数据,日常10TPS左右甚至更少,某一天A系统改了定时任务触发逻辑,加上代码bug,调用频率达到了500TPS,并且由于ID传错,绕过了缓存直接查询了DB和Hbase, 造成了Hbase读热点,拖垮集群,存储和查询都受到了影响。