阿里面试:5000qps访问一个500ms的接口,如何设计线程池的核心线程数、最大线程数? 需要多少台机器?

本文涉及的产品
性能测试 PTS,5000VUM额度
简介: 本文由40岁老架构师尼恩撰写,针对一线互联网企业的高频面试题“如何确定系统的最佳线程数”进行系统化梳理。文章详细介绍了线程池设计的三个核心步骤:理论预估、压测验证和监控调整,并结合实际案例(5000qps、500ms响应时间、4核8G机器)给出具体参数设置建议。此外,还提供了《尼恩Java面试宝典PDF》等资源,帮助读者提升技术能力,顺利通过大厂面试。关注【技术自由圈】公众号,回复“领电子书”获取更多学习资料。

本文原文链接

尼恩说在前面

在40岁老架构师 尼恩的读者交流群(50+)中,最近有小伙伴拿到了一线互联网企业如得物、阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格,遇到很多很重要的面试题:

如何确定系统的最佳线程数?

5000qps,下游一个接口响应时间 500ms,接口超时时间 1S,一台机器4核8g,如何设计线程池的核心线程数、最大线程数、队列,需要多少台机器

5000qps访问 一个 500ms响应时间接口,如何设计线程池的核心线程数、最大线程数? 需要多少台机器?

最近有小伙伴在面试阿里,又遇到了相关的面试题。小伙伴懵了,因为没有遇到过,所以支支吾吾的说了几句,面试官不满意,面试挂了。

所以,尼恩给大家做一下系统化、体系化的梳理,使得大家内力猛增,可以充分展示一下大家雄厚的 “技术肌肉”,让面试官爱到 “不能自已、口水直流”,然后实现”offer直提”。

当然,这道面试题,以及参考答案,也会收入咱们的 《尼恩Java面试宝典PDF》V171版本,供后面的小伙伴参考,提升大家的 3高 架构、设计、开发水平。

最新《尼恩 架构笔记》《尼恩高并发三部曲》《尼恩Java面试宝典》的PDF,请关注本公众号【技术自由圈】获取,回复:领电子书

本文目录

本文的配套视频, 尼恩的参考答应

详见:https://mp.weixin.qq.com/s/JFWjDSQ4HRGbZhj9ei3t6Q

线程使用的两个核心规范

首先看编程规范中, 有两个很重要的,与线程有关的需要强制执行的规范:

规范一:【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。

说明:Java线程的创建非常昂贵,需要JVM和OS(操作系统)配合完成大量的工作:

1)消耗内存资源:必须为线程堆栈分配和初始化大量内存块,其中包含至少1MB的栈内存。

2)消耗CPU资源:需要进行系统调用,以便在OS(操作系统)中创建和注册内核线程,大量内核线程调度会导致CPU上下文过度切换。

所以,Java高并发应用频繁创建和销毁线程的操作将是非常低效的,而且是不被编程规范所允许的。

如何降低Java线程的创建成本?必须使用到线程池。使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。

以上的内容,在尼恩的 《Java 高并发核心编程 卷2》 进行了详细介绍。

规范二:【强制】 线程池不允许使用Executors去创建快捷线程池 ,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

说明:Executors返回的线程池对象的弊端如下:

  • FixedThreadPool和SingleThreadPool: 允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
  • CachedThreadPool和ScheduledThreadPool: 允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。

通过以上规范,说明我们应用中,需要用自定义线程池。然而,由于构造一个线程池竟然有7个参数

image.png

7个重要参数中,最为重要的三个是:核心,最大线程数量, BlockingQueue。前两个参数和线程数量有关系, 后一个和内存资源消耗有关。

线程数设置太少或者阻塞队列太小, 会导致大量任务被拒绝,抛出RejectedExecutionException,触发线上的接口降级,用户体验很差。

二线程数设置太多或者阻塞队列太长,会导致资源消费高而有效负荷很小, 特别是阻塞队列设置过长,会导致频繁FullGC,甚至OOM。

如何确定系统的最佳线程数?来一个牛逼轰轰的答案

如何确定系统的最佳线程数,大体上分三步:

第一步,理论预估;

第二步,压测验证;

第三步,监控调整。

image.png

这也是尼恩给大家归纳的,最为理想的:可监控/可弹性的 线程池模式

第一步: 完成线程数的理论预估 (设计阶段)

在尼恩的 《Java 高并发核心编程 卷2》 进行了详细介绍。

首先,按照任务类型对线程池进行分类, 分为三类,具体如下图:

image.png

具体,请参见在尼恩的 《Java 高并发核心编程 卷2》 1.7.1 小节。

核心线程数的设置

  • CPU 密集型任务:如果应用程序执行的是CPU密集型任务,通常情况下,核心线程数应该设置为等于CPU核心数。这可以充分利用CPU资源。
  • IO 密集型任务:如果应用程序执行的是IO密集型任务(例如,文件读写、网络通信等),通常情况下,核心线程数可以设置为 CPU核心数 2倍,以充分利用等待IO操作时的线程空闲时间。
  • 混合型任务:如果应用程序同时执行CPU密集型和IO密集型任务,核心线程数的 按照 线程等待时间 + 线程 工作时间的占比来计算。

第1类:IO 密集型线程池线程数预估

线程数就是 CPU的核数的2倍。

image.png

具体,请参见在尼恩的 《Java 高并发核心编程 卷2》 1.7.2 小节。

第2类:CPU密集线程池线程数预估

CPU密集型任务并行执行的数量应当等于CPU的核心数, 线程数就是 CPU的核数

image.png

具体,请参见在尼恩的 《Java 高并发核心编程 卷2》 1.7.3小节。

第3类:混合型线程池线程数预估

混合型线程池线程数预估, 参考下面的的公式:

最佳线程数 = ((线程等待时间 + 线程 CPU 时间) / 线程 CPU 时间 ) * CPU 核数

image.png

具体,请参见在尼恩的 《Java 高并发核心编程 卷2》 1.7.4小节。

第二步: 完成线程数的压测验证 (测试阶段)

过少的线程会造成任务拒绝,业务降级。

过多的线程会造成,额外的内存开销CPU开销,甚至会导致OOM。

所以,合理的线程池线程数,才是王道。

在设计阶段完成了step1的线程数的理论预估之后, 那么我们的理论值就出来了。

如何做验证呢?这里需要 压测。

根据公式:

服务器端最佳线程数量=((线程等待时间+线程cpu时间)/线程cpu时间) * cpu数量

前面线程等待时间,线程cpu时间都是 预估的 ,都是要验证的。

首先通过用户慢慢递增来进行性能压测,观察QPS。

持续大的增加用户数, 压测出最大的吞吐量。

然后再 收集 最大的吞吐量场景的 线程等待时间,线程cpu时间, 再计算出最佳线程数。

第三步: 完成线程数的线上调整 (生产阶段)

压测的场景,是有限的。而线上的业务, 是复杂的,多样的。

由于系统运行过程中存在的不确定性,很难一劳永逸地规划一个合理的线程数。

所以,需要进行生产阶段线程数的两个目标:

  • 可监控预警
  • 可在线调整

image.png

第1个维度:可在线监控预警

image.png

第2个维度:可在线调整

image.png

参数的在线动态调整:结合Nacos 实现动态化线程池

优秀的动态化线程池轮子,主要有:

  • Hippo4J
  • dynamic-tp

如果线上使用,可以使用这些轮子项目。

但是尼恩的是[技术自由圈]一个实战社群,必须自己从0到1,去撸一把代码,提升自己的水平。

大实操:5000qps 线程数的理论预估 大实操

1. 核心参数解释

  • 请求量 (QPS): 5000 QPS意味着每秒有5000个请求需要被处理。
  • 接口响应时间: 500ms,即每个请求的处理时间为500毫秒。
  • 接口超时时间: 1秒,表示如果请求处理时间超过1秒,接口就会超时。
  • 机器配置: 每台机器4个CPU核心,8GB内存。

2. 核心线程数 设计

为了设计合理的线程池, 需要考虑如何合理配置核心线程数最大线程数队列大小

首先是 核心线程数。

核心线程数决定了线程池中最小的线程数,线程池会始终保持这个数量的线程。

根据 面试的业务场景:

  • 每台机器有4个CPU核心,因此每个CPU核心可以并发处理多个请求。通常情况下,核心线程数会设置为与CPU核心数相同。
  • 由于接口响应时间是500ms,每个线程可以在500ms内处理完一个请求,因此设置核心线程数为4,或者根据实际负载情况适当调整。

建议: 核心线程数设置为 4(与CPU核心数一致)。

3. 最大线程数 设计

一般业务接口都是混合型 任务, 相对应的 ,线程池 属于 混合型线程池。

混合型任务 的工作,分为两个时间:

  • 等待时间,进行大量非 CPU 耗时操作 ,比如在 Web 应用处理 HTTP 请求处理时,一次请求处理会包括 DB 操作、 RPC 操作、缓存操作等多种耗时操作。
  • 工作时间, 执行简单的计算,一般是 几十个ms 搞定。

所以,混合型任务 CPU 利用率不是太高,非 CPU 耗时往往是 CPU 耗时的数10倍。

一般来说,一次 Web 请求的 CPU 计算耗时往往较少,大致在 20ms-50ms 之间,而其他耗时操作会占用 500ms-1000ms 甚至更多的时间。

在为混合型任务创建线程池时,如何确定线程数呢?

业界有一个比较成熟的估算公式,具体如下:

最佳线程数 = ((线程等待时间+线程CPU 时间) / 线程CPU 时间 ) * CPU 核数

比如在 Web 服务器处理 HTTP 请求时,假设平均线程 CPU 运行时间为 50ms,而线程等待时间(比如包括 DB 操作、 RPC 操作、缓存操作等)为500ms,如果 CPU核数为 4,那么根据上面这个公式,估算如下:

((500+50) / 50) * 450

建议:最大线程数50

4. 线程池 的队列大小 设计

队列的大小决定了等待处理的请求数量。

一般来说,线程池的队列大小设置与最大线程数成正比。

  • 如果队列太小,可能会导致请求被拒绝;

  • 如果队列太大,会导致内存压力增大。

在高并发场景下,为避免内存占用过高,队列的大小可以设置为最大线程数的2倍左右。

建议: 队列大小设置为 50*2 =100

5. 计算所需机器数量

为了计算所需机器数量,我们首先要计算每台机器能够处理的请求量,然后计算出总请求量所需的机器数。

a. 每台机器的处理能力

每台机器的4个CPU核心可以并行处理多个请求,假设每个线程的响应时间为500ms。

每个线程每秒能处理 2 个请求(500ms处理一个请求,1秒可以处理2个请求)。

每台机器的最大请求处理能力为:

  • 最大请求处理能力 = 每台机器最大线程数 * 每个线程每秒处理的请求数
  • 假设最大线程数为50,每台机器的最大处理能力为:

50 * 2 = 100 个请求/秒。

6. 所需机器数量

如果系统需要处理5000 QPS,每台机器可以处理100个请求/秒,计算所需机器数量:

  • 所需机器数量 = 总请求量 / 每台机器处理能力
  • 所需机器数量 = 5000 QPS / 100 请求/秒/机器 ≈ 50 台机器。

为了保证性能和冗余,建议多几台,因此需要 50台机器

7. 完成线程数的理论预估 (设计阶段) 总结

  • 核心线程数:4(与机器核心数一致)
  • 最大线程数:50(根据负载情况)
  • 队列大小:100(根据最大线程数设置)
  • 所需机器数量:大约 50 台机器

这些设置可以根据系统的实际负载和性能需求进一步调整。

相关面试题:

阿里面试:系统的最佳线程数,怎么确定?

说在最后:有问题找老架构取经‍

只要按照上面的 尼恩团队梳理的 方案去作答, 你的答案不是 100分,而是 120分。 面试官一定是 心满意足, 五体投地。

按照尼恩的梳理,进行 深度回答,可以充分展示一下大家雄厚的 “技术肌肉”,让面试官爱到 “不能自已、口水直流”,然后实现”offer直提”。

在面试之前,建议大家系统化的刷一波 5000页《尼恩Java面试宝典PDF》,里边有大量的大厂真题、面试难题、架构难题。

很多小伙伴刷完后, 吊打面试官, 大厂横着走。

在刷题过程中,如果有啥问题,大家可以来 找 40岁老架构师尼恩交流。

另外,如果没有面试机会, 可以找尼恩来改简历、做帮扶。前段时间,刚指导一个小伙 暴涨200%(涨2倍),29岁/7年/双非一本 , 从13K一次涨到 37K ,逆天改命

狠狠卷,实现 “offer自由” 很容易的, 前段时间一个武汉的跟着尼恩卷了2年的小伙伴, 在极度严寒/痛苦被裁的环境下, offer拿到手软, 实现真正的 “offer自由” 。

尼恩技术圣经系列PDF

……完整版尼恩技术圣经PDF集群,请找尼恩领取

相关实践学习
通过性能测试PTS对云服务器ECS进行规格选择与性能压测
本文为您介绍如何利用性能测试PTS对云服务器ECS进行规格选择与性能压测。
相关文章
|
27天前
|
存储 监控 Java
【Java并发】【线程池】带你从0-1入门线程池
欢迎来到我的技术博客!我是一名热爱编程的开发者,梦想是编写高端CRUD应用。2025年我正在沉淀中,博客更新速度加快,期待与你一起成长。 线程池是一种复用线程资源的机制,通过预先创建一定数量的线程并管理其生命周期,避免频繁创建/销毁线程带来的性能开销。它解决了线程创建成本高、资源耗尽风险、响应速度慢和任务执行缺乏管理等问题。
162 60
【Java并发】【线程池】带你从0-1入门线程池
|
26天前
|
存储 SQL 算法
阿里面试:每天新增100w订单,如何的分库分表?这份答案让我当场拿了offer
例如,在一个有 10 个节点的系统中,增加一个新节点,只会影响到该新节点在哈希环上相邻的部分数据,其他大部分数据仍然可以保持在原节点,大大减少了数据迁移的工作量和对系统的影响。狠狠卷,实现 “offer自由” 很容易的, 前段时间一个武汉的跟着尼恩卷了2年的小伙伴, 在极度严寒/痛苦被裁的环境下, offer拿到手软, 实现真正的 “offer自由”。在 3 - 5 年的中期阶段,随着业务的稳定发展和市场份额的进一步扩大,订单数据的增长速度可能会有所放缓,但仍然会保持在每年 20% - 30% 的水平。
阿里面试:每天新增100w订单,如何的分库分表?这份答案让我当场拿了offer
|
2月前
|
安全 Java 程序员
面试必看:如何设计一个可以优雅停止的线程?
嘿,大家好!我是小米。今天分享一篇关于“如何停止一个正在运行的线程”的面试干货。通过一次Java面试经历,我明白了停止线程不仅仅是技术问题,更是设计问题。Thread.stop()已被弃用,推荐使用Thread.interrupt()、标志位或ExecutorService来优雅地停止线程,避免资源泄漏和数据不一致。希望这篇文章能帮助你更好地理解Java多线程机制,面试顺利! 我是小米,喜欢分享技术的29岁程序员。欢迎关注我的微信公众号“软件求生”,获取更多技术干货!
111 53
|
1月前
|
缓存 NoSQL Java
阿里面试:DDD 落地,遇到哪些 “拦路虎”?如何破局?
为每个子领域定义限界上下文(bounded context),限界上下文是一个清晰定义了领域模型的边界的范围。在限界上下文内,领域模型的概念是一致的,但不同限界上下文之间可以有不同的模型和语言。界限上下文,基本可以对应到 落地层面的 微服务。这就是 DDD 建模和 微服务架构, 能够成为孪生兄弟、 天然统一的原因。具体的方法论和落地实操,请参考 《第34章视频 DDD学习圣经》DDD 战略设计的第一步就是统一语言,也叫通用语言(UBIQUITOUS LANGUAGE),用于定义上下文。
阿里面试:DDD 落地,遇到哪些 “拦路虎”?如何破局?
|
1月前
|
数据采集 Java Linux
面试大神教你:如何巧妙回答线程优先级这个经典考题?
大家好,我是小米。本文通过故事讲解Java面试中常见的线程优先级问题。小明和小华的故事帮助理解线程优先级:高优先级线程更可能被调度执行,但并非越高越好。实际开发需权衡业务需求,合理设置优先级。掌握线程优先级不仅能写出高效代码,还能在面试中脱颖而出。最后,小张因深入分析成功拿下Offer。希望这篇文章能助你在面试中游刃有余!
46 4
面试大神教你:如何巧妙回答线程优先级这个经典考题?
|
1月前
|
Java 程序员 开发者
Java社招面试题:一个线程运行时发生异常会怎样?
大家好,我是小米。今天分享一个经典的 Java 面试题:线程运行时发生异常,程序会怎样处理?此问题考察 Java 线程和异常处理机制的理解。线程发生异常,默认会导致线程终止,但可以通过 try-catch 捕获并处理,避免影响其他线程。未捕获的异常可通过 Thread.UncaughtExceptionHandler 处理。线程池中的异常会被自动处理,不影响任务执行。希望这篇文章能帮助你深入理解 Java 线程异常处理机制,为面试做好准备。如果你觉得有帮助,欢迎收藏、转发!
136 14
|
1月前
|
安全 Java 程序员
Java 面试必问!线程构造方法和静态块的执行线程到底是谁?
大家好,我是小米。今天聊聊Java多线程面试题:线程类的构造方法和静态块是由哪个线程调用的?构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节有助于掌握Java多线程机制。下期再见! 简介: 本文通过一个常见的Java多线程面试题,详细讲解了线程类的构造方法和静态块是由哪个线程调用的。构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节对掌握Java多线程编程至关重要。
61 13
|
1月前
|
算法 NoSQL 应用服务中间件
阿里面试:10WQPS高并发,怎么限流?这份答案让我当场拿了offer
在 Nacos 的配置管理界面或通过 Nacos 的 API,创建一个名为(与配置文件中 dataId 一致)的配置项,用于存储 Sentinel 的流量控制规则。上述规则表示对名为的资源进行流量控制,QPS 阈值为 10。resource:要保护的资源名称。limitApp:来源应用,default表示所有应用。grade:限流阈值类型,1 表示 QPS 限流,0 表示线程数限流。count:限流阈值。strategy:流控模式,0 为直接模式,1 为关联模式,2 为链路模式。
阿里面试:10WQPS高并发,怎么限流?这份答案让我当场拿了offer
|
1月前
|
缓存 安全 Java
面试中的难题:线程异步执行后如何共享数据?
本文通过一个面试故事,详细讲解了Java中线程内部开启异步操作后如何安全地共享数据。介绍了异步操作的基本概念及常见实现方式(如CompletableFuture、ExecutorService),并重点探讨了volatile关键字、CountDownLatch和CompletableFuture等工具在线程间数据共享中的应用,帮助读者理解线程安全和内存可见性问题。通过这些方法,可以有效解决多线程环境下的数据共享挑战,提升编程效率和代码健壮性。
93 6
|
1月前
|
安全 Java C#
Unity多线程使用(线程池)
在C#中使用线程池需引用`System.Threading`。创建单个线程时,务必在Unity程序停止前关闭线程(如使用`Thread.Abort()`),否则可能导致崩溃。示例代码展示了如何创建和管理线程,确保在线程中执行任务并在主线程中处理结果。完整代码包括线程池队列、主线程检查及线程安全的操作队列管理,确保多线程操作的稳定性和安全性。

热门文章

最新文章

相关实验场景

更多