阿里面试: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进行规格选择与性能压测。
相关文章
|
5天前
|
人工智能 缓存 Ubuntu
AI+树莓派=阿里P8技术专家。模拟面试、学技术真的太香了 | 手把手教学
本课程由阿里P8技术专家分享,介绍如何使用树莓派和阿里云服务构建AI面试助手。通过模拟面试场景,讲解了Java中`==`与`equals`的区别,并演示了从硬件搭建、语音识别、AI Agent配置到代码实现的完整流程。项目利用树莓派作为核心,结合阿里云的实时语音识别、AI Agent和文字转语音服务,实现了一个能够回答面试问题的智能玩偶。课程展示了AI应用的简易构建过程,适合初学者学习和实践。
53 22
|
1月前
|
安全 Java API
java如何请求接口然后终止某个线程
通过本文的介绍,您应该能够理解如何在Java中请求接口并根据返回结果终止某个线程。合理使用标志位或 `interrupt`方法可以确保线程的安全终止,而处理好网络请求中的各种异常情况,可以提高程序的稳定性和可靠性。
51 6
|
1月前
|
存储 NoSQL 架构师
阿里面试:聊聊 CAP 定理?哪些中间件是AP?为什么?
本文深入探讨了分布式系统中的“不可能三角”——CAP定理,即一致性(C)、可用性(A)和分区容错性(P)三者无法兼得。通过实例分析了不同场景下如何权衡CAP,并介绍了几种典型分布式中间件的CAP策略,强调了理解CAP定理对于架构设计的重要性。
87 4
|
2月前
|
存储 NoSQL 算法
阿里面试:亿级 redis 排行榜,如何设计?
本文由40岁老架构师尼恩撰写,针对近期读者在一线互联网企业面试中遇到的高频面试题进行系统化梳理,如使用ZSET排序统计、亿级用户排行榜设计等。文章详细介绍了Redis的四大统计(基数统计、二值统计、排序统计、聚合统计)原理和应用场景,重点讲解了Redis有序集合(Sorted Set)的使用方法和命令,以及如何设计社交点赞系统和游戏玩家排行榜。此外,还探讨了超高并发下Redis热key分治原理、亿级用户排行榜的范围分片设计、Redis Cluster集群持久化方式等内容。文章最后提供了大量面试真题和解决方案,帮助读者提升技术实力,顺利通过面试。
|
2月前
|
SQL 关系型数据库 MySQL
阿里面试:1000万级大表, 如何 加索引?
45岁老架构师尼恩在其读者交流群中分享了如何在生产环境中给大表加索引的方法。文章详细介绍了两种索引构建方式:在线模式(Online DDL)和离线模式(Offline DDL),并深入探讨了 MySQL 5.6.7 之前的“影子策略”和 pt-online-schema-change 方案,以及 MySQL 5.6.7 之后的内部 Online DDL 特性。通过这些方法,可以有效地减少 DDL 操作对业务的影响,确保数据的一致性和完整性。尼恩还提供了大量面试题和解决方案,帮助读者在面试中充分展示技术实力。
|
2月前
|
Java
为什么一般采用实现Runnable接口创建线程?
因为使用实现Runnable接口的同时我们也能够继承其他类,并且可以拥有多个实现类,那么我们在拥有了Runable方法的同时也可以使用父类的方法;而在Java中,一个类只能继承一个父类,那么在继承了Thread类后我们就不能再继承其他类了。
29 0
|
5月前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
2月前
|
存储 缓存 算法
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
本文介绍了多线程环境下的几个关键概念,包括时间片、超线程、上下文切换及其影响因素,以及线程调度的两种方式——抢占式调度和协同式调度。文章还讨论了减少上下文切换次数以提高多线程程序效率的方法,如无锁并发编程、使用CAS算法等,并提出了合理的线程数量配置策略,以平衡CPU利用率和线程切换开销。
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
|
2月前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
2月前
|
存储 缓存 Java
大厂面试必看!Java基本数据类型和包装类的那些坑
本文介绍了Java中的基本数据类型和包装类,包括整数类型、浮点数类型、字符类型和布尔类型。详细讲解了每种类型的特性和应用场景,并探讨了包装类的引入原因、装箱与拆箱机制以及缓存机制。最后总结了面试中常见的相关考点,帮助读者更好地理解和应对面试中的问题。
84 4

相关实验场景

更多