❝
尝试优化业务中发现,服务的心跳信息中有很多线程都是处于
waiting
,如下图所示:❞
thread count="608"
daemon-count="420"
peak-count="611"
total-started-count="13722"
deadlocked="0" new="0" runnable="169" blocked="0"
waiting="314"
复制代码
❝然后看了CPU的使用率,从左到右分别表示
❞CPU的任务等待数/CPU核数
,CPU的执行时间占比总时间(CPU执行时间+CPU空闲时间+ CPU等待时间)
,当前JAVA进程执行时间占比总时间
❝图中可以清晰地看到,并不是计算型业务导致了线程等待,而是极大可能由于服务到底层数据查询的网络IO等待使得排队的线程增加,因此决定考虑优化这一部分。优化的目标,在保证服务和底层存储的心跳信息在一个安全的范围内,尽可能的增加服务吞吐能力。
❞
思路一协程
当时优化的第一时间就想到了大名鼎鼎的quasar
三方库。quasar
可以理解为轻量级的线程实现,熟悉go语言一定知道goroutine
,我们知道Java语言中不支持协程,业务中很多场景都需要用线程池进行优化,但是使用线程池的成本也很高,无论是内存占用还是线程之间的切换消耗,都限制了一个应用不能无限制的创建线程。
好在社区开源了一款Java coroutine
框架quasar
,容我先吐槽一下,这个框架真的是直男程序员写的(已经被拉去写JDK的协程,十分期待JDK能早点支持协程),文档十分匮乏,导致我本地开始搞得时候就报错了一把,开局体验不是很舒服。
当然优点也十分突出,应用中网络IO耗时占比比较突出的场景中,使用quasar
可以极大的提高CPU
的吞吐率。简单描述就是可以在更短的时间内处理更多的请求。不会因为一个线程中的网络IO
堵塞而让后面的线程处于waiting
中,堵塞的时候CPU
是不干活的,因此将整个系统的吞吐率拉胯。
官网的文档中提供了两种使用方式,为了节约篇幅先用第1种方式示范一下使用方式:
-
Running the Instrumentation Java Agent(加载器织入)
-
Ahead-of-Time (AOT) Instrumentation(预编译织入)
这里我先用Gradle
项目作为🌰来详解一下怎么使用。
一、Gradle
配置模块
configurations {
quasar
}
//
tasks.withType(JavaExec) {
jvmArgs "-javaagent:${configurations.quasar.iterator().next()}"
}
//
dependencies {
compile "org.antlr:antlr4:4.7.2"
compile "co.paralleluniverse:quasar-core:0.7.5"
quasar "co.paralleluniverse:quasar-core:0.7.5:jdk8@jar"
testCompile group: 'junit', name: 'junit', version: '4.12'
}
复制代码
复制代码
二、实现一个耳熟能详的echo
服务器
两个Fiber
(相当于是Java的Thread
)相互通信,increasing
发送一个int
数字给echo
,echo
收到之后返回给increasing
,increasing
接收到echo
返回的消息,先打印,在执行++
操作,然后打印出最后的结果。代码示例如下:
-
increasing