1 背景
性能优化是咱们日常工作中很重要的一部分,次要有以下起因:
- 升高服务器和带宽等硬件老本:用更少的资源解决更多的申请
- 进步事实世界的运行效率:人机处理效率存在数量级的偏差,同样机器世界的效率晋升能带来事实世界效率晋升的办法成果
- 进步用户的体验:解决响应迟缓、宕机等问题
而并行优化在改善程序接口响应工夫和吞吐量指标方面是个利器,所以本次联合前段时间做的一段长链路执行逻辑代码的优化,给大家讲讲程序并行优化的步骤及方法论。
2 多线程优化六步法
2.1 定位优化点
个别是通过全链路监控、火焰图、自定义打点、生产报警等先找到耗时长的性能问题点,之后通过多线程并行化的形式达到优化程序响应时长和吞吐量的目标。
2.2 执行链路剖析
对问题点的执行链路进行剖析,次要分几方面:
- 链路里波及的操作节点;
- 节点本身的耗时;是 io 密集型还是 cpu 密集型;是否依赖和批改内部变量;此节点是否是外围门路;
- 节点间彼此依赖关系;
2.3 异步链路设计
- 将链路依据依赖关系进行重排,把被依赖的放在后面;
- 彼此不依赖有雷同终点的节点并行化;设计并行任务后果获取及后续依赖节点的告诉机制
- 如果有指定响应工夫指标的链路,为外围门路节点设计降级计划;依据响应工夫要求及已耗时数据对非核心门路节点调用进行舍弃;
- 将对变量批改的逻辑收拢,且尽量在主线程中解决,防止须要做的多线程变量可见性和时序性同步
2.4 并发框架抉择
1. 线程池
形容:具体业务工作继承接口 Runnable、Callable,在调用 ExecutorService.submit 接口时,会提交工作到 ExecutorService 外部的一个工作队列中。同时,在 ExecutorService 外部还存在一个事后申请的线程池(Thread Pool),线程池中的线程会从工作队列中支付一个工作来执行。
长处:复用线程,缩小线程创立销毁老本及缩小申请时延
留神点:cpu 密集型和 io 密集型工作应进行不同的线程池配置;为防止不同工作互相烦扰重要业务最好独立应用线程池;不同线程之间要留神操作的有序和数据的可见性
2.AKKA
形容:每个 Actor 代表的是能够被调度执行的轻量单元。如图中所示,Actor A 和 Actor C 在向 Actor B 发送音讯时,所有音讯会被底层框架发送到 Actor B 的 Mailbox 中,而后底层的 Akka 框架调度代码会触发 Actor B,来接管并执行音讯的后续解决。这样,基于 Actor 模型的这套并发框架,首先就保障了音讯能够被平安地在各个 Actor 之间传递,同时也保障了每个 Actor 实例能够串行解决接管到的所有音讯。
长处:不须要关注多线程之间并发同步和数据一致性;轻量级高并发
留神点:actor 工作粒度要小,防止承接太多业务逻辑;计算密集型工作更能施展出 AKKA 的劣势
3.REACTOR
形容:输出流 Flux 就是 Reactor 中典型的异步音讯流,它代表了一个蕴含 0 个到 N 个的音讯序列。另外,图中的 Rule 代表的是一个基于音讯的解决逻辑或规定,输出流中的音讯能够被两头多个解决逻辑组合间断加工之后,再生成一个蕴含 0 个到 N 个的输入音讯流 Flux。
长处:rule 采纳 pull 解决音讯,防止音讯积压;异步非阻塞 io,防止阻塞以后线程
留神点:函数式编程,会有肯定的语法学习老本和了解老本;针对音讯流解决的、基于 IO 密集型的异步交互场景比拟有劣势
2.5 并发工具抉择
多线程执行波及到一系列细节问题,如共享变量可见性,执行程序,后果的获取、后续操作的告诉等,所以要联合需要应用一系列相干的并发工具类做多线程执行正确性的保障
2.6 成果验证
1. 压测
个别通过 jmeter、loadrunner 等后端性能测试软件,一直对系统施加压力,并验证系统化处于或长期处于临界饱和阶段的稳定性以及性能指标,并试图找到零碎处于临界状态时的次要瓶颈点。
留神点:
- 完全相同的环境以及测试负载
- 留神混部状况其余服务可能对验证服务造成的影响
- 通过加压减压调整申请量察看服务器解决能力的变动及稳定性
2. 性能指标验证
- 验证并发用户数、响应工夫及吞吐量这种调优指标量;
- 察看服务器的负载指标,避免因优化带来服务器超出负载能力;
- 察看上下游服务的业务指标和服务器负载,避免因优化带来上下游超出负载能力
3. 业务后果验证
个别通过 diff 工具通过采集雷同申请的响应比照判断是否影响业务;也可通过 qa 辅助构建针对改变的测试集去做验证
3 举例
以咱们前段时间进行的商品主数据下发生产能力调优进行举例说明整个优化过程:
3.1 优化点定位
主数据程序接管商品批量下发解决迟缓,触发下发积压报警
3.2 执行链路剖析
梳理各步骤对入参和保留时须要的变量的解决,剖析各步骤相互依赖关系,是否可并行,进行执行过程优化调整。
商品主数据处理步骤剖析:
3.3 异步链路设计
- 1、3、4、5 异步并行处理,且因对其余变量批改逻辑无依赖,放在最后面提交。
- 2、7、8、9、10、11 依据依赖关系,把相关性的逻辑收拢,把被依赖的逻辑提前。
- 13 也异步提交。最初通过 completionService.take().get() 遍历获取各工作执行后果进行合并返回最终后果
3.4 并发框架抉择
出于团队常识栈及框架利用场景综合思考,这里抉择了线程池作为并发框架,并联合多 io 场景做了线程池参数配置。
/**
* io 工作线程池
*/
public static ThreadPoolExecutor threadPoolExecutorForIO= new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(),Runtime.getRuntime().availableProcessors()*2,1, TimeUnit.MINUTES,new ArrayBlockingQueue(2014),new ThreadPoolExecutor.CallerRunsPolicy());
3.5 并发工具抉择
这里应用 CompletionService 来获取多线程的执行后果,并进行后果归集。
CompletionService 通过在线程后果实现时提交到阻塞队列,防止通过遍历 future 后果的形式导致先提交的工作耗时长造成的阻塞期待。
CountingExecutorCompletionService<Boolean> completionService= new CountingExecutorCompletionService(ExecutorCollector.threadPoolExecutorForIO);
// 工作提交
completionService.submit(callableA);
// 后果归集
boolean result=true;
for(int i = 0; i<completionService.getSubmittedTaskCount(); i++)
{result&=completionService.take().get();}
3.6 成果验证
1. 压测
采纳 jmeter 对两台雷同配置的服务器(别离部署优化版本和原始版本)加压,察看服务负载状况
2. 性能指标验证
1)耗时和吞吐量异步版本要优于同步版本
异步版本耗时在 80-100ms,同步版本耗时在 120-160ms
异步版本吞吐量在 17000/ 5 分钟,同步版本吞吐量在 15000/ 5 分钟
2)cpu 使用率异步版本略高一点,线程数异步版本比拟高
线程数高的起因:用到了线程池,预置的外围线程数为逻辑核数 64,因为波及到 io 操作较多,最大线程数配成了 128。
3. 业务后果验证
因为公司框架不反对 http 的 diff,此处采纳了本人抽检申请后果及 qa 帮助走查和 code review 的形式保障业务后果的准确性
4 总结
程序性能优化办法关系到方方面面,而多线程异步优化无疑是其中很重要的一种路径。它不光关系到并发框架的抉择、多种线程工具类的应用,还关系到对整个解决链路的业务了解和编排剖析。心愿通过这一课能够帮大家理清相干的思路,作为日常优化工作的一个参考。
作者:京东物流 冯鸿儒
内容起源:京东云开发者社区