加入收藏 | 设为首页 | 会员中心 | 我要投稿 源码网 (https://www.900php.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 站长资讯 > 外闻 > 正文

为何服务器QPS上不去?Java线程调优权威指南

发布时间:2019-07-16 16:20:21 所属栏目:外闻 来源:今日头条
导读:副标题#e# 从刚问世起,Java 的部分魅力就来自其多线程。即便在多核和多 CPU 系统司空见惯之前,能够轻松编写多线程程序也是 Java 的一个标志性特征。 Java 性能方面的吸引力显而易见:如果有两个 CPU 可用,那么一个应用能够完成的工作量可能是原来的 2 倍
副标题[/!--empirenews.page--]

从刚问世起,Java 的部分魅力就来自其多线程。即便在多核和多 CPU 系统司空见惯之前,能够轻松编写多线程程序也是 Java 的一个标志性特征。

Java 性能方面的吸引力显而易见:如果有两个 CPU 可用,那么一个应用能够完成的工作量可能是原来的 2 倍。当然这是在假设任务可以分解成离散的片段的前提之下的,因为 Java 不能自动找出算法性部分并实现并行化的语言。幸运的是,今日所见之计算,往往是离散性的任务。

为何服务器QPS上不去?Java线程调优权威指南

下面探讨的主题是,如何挖掘出 Java 线程和同步设施的最大性能。

线程池与 ThreadPoolExecutor

为何服务器QPS上不去?Java线程调优权威指南

(Thread Pool 示意图,来源 wikipedia)

在 Java 中,线程可以使用自己代码来管理,也可以利用线程池,使用 ThreadPoolExecutor 并行执行任务。

在使用线程池时,有一个因素非常关键:调节线程池的大小对获得最好的性能至关重要。线程池的性能会随线程池大小这一基本选择而有所不同,在某些条件下,线程池过大对性能也有很大的不利影响。

所有线程池的工作方式本质是一样的:

有一个队列,任务被提交到这个队列中。一定数量的线程会从该队列中取任务,然后执行。

任务的结果可以发回客户端(比如应用服务器的情况下),或保存到数据库中,或保存到某个内部数据结构中,等等。但是在执行完任务后,这个线程会返回任务队列,检索另一个任务并执行,如果没有更多任务要执行,该线程会等待下一个任务。

线程池有最小线程数和最大线程数。池中会有最小数目的线程随时待命,等待任务指派给它们。因为创建线程的成本非常高昂,这样可以提高任务提交时的整体性能:已有的线程会拿到该任务并处理。另一方面,线程需要一些系统资源,包括栈所需的原生内存,如果空闲线程太多,就会消耗本来可以分配给其他进程的资源。最大线程数还是一个必要的限流阀,防止一次执行太多线程。

ThreadPoolExecutor 和相关的类将最小线程数称作核心池大小,如果有个任务要执行,而所有的并发线程都在忙于执行另一个任务,就启动一个新线程,直到创建的线程达到最大线程数。

设置最大线程数

对于给定硬件上的给定负载,最大线程数设置为多少最好呢?

这个问题回答起来并不简单;它取决于负载特性以及底层硬件。特别是,最优线程数还与每个任务阻塞的频率有关。

为方便讨论,假设 JVM 有 4 个 CPU 可用。我们的目标就是最大化这 4 个 CPU 的利用率。

很明显,最大线程数至少要设置为 4。的确,除了处理这些任务,JVM 中还有些线程要做其他的事,但是它们几乎从来不会占用一个完整的 CPU。如果使用的是并发垃圾收集器,这是个例外,后台线程必须有足够的 CPU 来运行,以免在处理堆这方面落后。

如果线程数多于 4,会有帮助吗?这时就要看负载特性了。考虑最简单的情况,假定任务都是计算密集型的:没有外部网络调用(比如不会访问数据库),也不会激烈地竞争内部锁。在使用模实体管理器(mock entity manager)的情况下,股价历史批处理程序就是一个这样的应用:实体上的数据完全可以并行计算。

下面就使用线程池计算一下 10,000 个模股票实体的历史,假设机器有 4 个 CPU,使用不同的线程数测试,具体的性能数据见表1。如果池中只有 1 个线程,计算数据集需要 255.6 秒;用 4 个线程,则只需要 77 秒。如果线程数超过 4 个,随着线程数的增加,需要的时间会稍多一些。

表1:计算 10,000 个模的价格历史所需时间

为何服务器QPS上不去?Java线程调优权威指南

如果应用中的任务是完全并行的,则在有 2 个线程时,“与基准的百分比”这列为 50%;在有 4 个线程时,这列为 25%。但是这种完全线性的比例不可能出现,原因有这么几点:如果没有其他线程帮助,这些线程必须自己来协同,实现从运行队列中选取任务(一般而言,通常会有更多同步)。到了使用 4 个线程的时候,系统会 100% 消耗可用的 CPU,尽管机器可能没有运行其他用户级的应用,但是会有各种系统级的进程进来,并使用 CPU,从而使得 JVM 无法 100% 地使用所有 CPU 周期。

尽管如此,这个应用在伸缩性方面表现还不错,且即使池中的线程数被显著高估,性能损失也比较轻微。

不过在其他情况下,性能损失可能会很大。在 Servlet 版的股票历史计算程序中,线程太多的话,影响会很大,如表2 所示。应用服务器分别配置成不同的线程数,有一个负载生成器会向该服务器发送 20 个同步的(simultaneous)请求。

表2:每秒通过 Servlet 的操作

为何服务器QPS上不去?Java线程调优权威指南

鉴于应用服务器有 4 个 CPU 可用,最大吞吐量可以通过将池中的线程数设置为 4 来实现。

在研究性能问题时确定瓶颈在哪儿比较重要。在这个例子中,瓶颈很明显是 CPU:4 个线程时,CPU 利用率为 100%。不过加入更多线程的影响其实很小,至少当线程数是原来的 8 倍时才会有明显的差别。

如果瓶颈在其他地方呢?这个例子有点不同寻常,任务完全是 CPU 密集型的:没有 I/O。一般来说,线程有可能会调用数据库,或者把输出写到某个地方,甚至是会合其他某些资源。在那种情况下,瓶颈未必是 CPU,而可能是外部资源。

对于此类情况,添加线程非常有害。虽然我们经常说数据库总是瓶颈,但是瓶颈可能是任何外部资源。

仍以股票 Servlet 为例,我们把目标变一下:如果目标是最大限度地利用负载生成器机器,又会如何,是简单地运行一个多线程的 Java 程序吗?

(编辑:源码网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

热点阅读