共计 6987 个字符,预计需要花费 18 分钟才能阅读完成。
这是 why 哥的第 71 篇原创文章
一道面试题
兄弟们,怎么说?
我感觉如果你工作了两年左右的工夫,或者是突击筹备了面试,这题答复个八成上来,应该是手到擒来的事件。这题中规中矩,考点清晰,能够说的货色不是很多。
然而这都上血书了,那不得剖析一波?
先把这个面试题拿进去一下:
1000 多个并发线程,10 台机器,每台机器 4 核,设计线程池大小。
这题给的信息十分的简陋,然而简陋的益处就是设想空间足够大。
第一眼看到这题的时候,我直观的感触到了两个考点:
- 线程池设计。
- 负载平衡策略。
我就单刀直入的给你说了,这两个考点,刚好都在我之前的文章的射程范畴之内:
《如何设置线程池参数?美团给出了一个让面试官虎躯一震的答复》
《吐血输入:2 万字长文带你细细盘点五种负载平衡策略》
上面我会针对我感触到的这两个考点去进行剖析。
线程池设计
咱们先想简略一点:1000 个并发线程交给 10 台机器去解决,那么 1 台机器就是承当 100 个并发申请。
100 个并发申请而已,的确不多。
而且他也没有说是每 1 秒都有 1000 个并发线程过去,还是偶然会有一次 1000 个并发线程过去。
先从线程池设计的角度去答复这个题。
要答复好这个题目,你必须有两个最根本的常识贮备:
- 自定义线程池的 7 个参数。
- JDK 线程池的执行流程。
先说第一个,自定义线程池的 7 个参数。
java.util.concurrent.ThreadPoolExecutor#ThreadPoolExecutor
害,这 7 个参数我真的都不想说了,你去翻翻历史文章,我都写过多少次了。你要是再说不出个有条有理的,你都对不起我写的这些文章。
而且这个类上的 javadoc 曾经写的十分的明确了。这个 javadoc 是 Doug Lea 老爷子亲自写的,你都不拜读拜读?
为了避免你偷懒,我把老爷子写的粘下来,咱们一句句的看。
对于这几个参数,我通过这篇文章再说最初一次。
如果当前的文章我要是再讲这几个参数,我就不叫 why 哥,当前你们就叫我小王吧。
写着写着,怎么还有一种怄气的感觉呢。仿佛忽然明确了当年在讲台上越讲越怄气的数学老师说的:这题我都讲了多少遍了!还有人错?
好了,不怄气了,说参数:
- corePoolSize:the number of threads to keep in the pool, even if they are idle, unless {@code allowCoreThreadTimeOut} is set(外围线程数大小:不论它们创立当前是不是闲暇的。线程池须要放弃 corePoolSize 数量的线程,除非设置了 allowCoreThreadTimeOut。)
- maximumPoolSize:the maximum number of threads to allow in the pool。(最大线程数:线程池中最多容许创立 maximumPoolSize 个线程。)
- keepAliveTime:when the number of threads is greater than the core, this is the maximum time that excess idle threads will wait for new tasks before terminating。(存活工夫:如果通过 keepAliveTime 工夫后,超过外围线程数的线程还没有承受到新的工作,那就回收。)
- unit:the time unit for the {@code keepAliveTime} argument(keepAliveTime 的工夫单位。)
- workQueue:the queue to use for holding tasks before they are executed. This queue will hold only the {@code Runnable} tasks submitted by the {@code execute} method。(寄存待执行工作的队列:当提交的工作数超过外围线程数大小后,再提交的工作就寄存在这里。它仅仅用来寄存被 execute 办法提交的 Runnable 工作。所以这里就不要翻译为工作队列了,好吗?不要本人给本人挖坑。)
- threadFactory:the factory to use when the executor creates a new thread。(线程工程:用来创立线程工厂。比方这外面能够自定义线程名称,当进行虚拟机栈剖析时,看着名字就晓得这个线程是哪里来的,不会懵逼。)
- handler:the handler to use when execution is blocked because the thread bounds and queue capacities are reached。(回绝策略:当队列外面放满了工作、最大线程数的线程都在工作时,这时持续提交的工作线程池就解决不了,应该执行怎么样的回绝策略。)
第一个常识贮备就讲完了,你先别开始背,这玩意你背下来有啥用,你得联合着执行流程去了解。
接下来咱们看第二个:JDK 线程池的执行流程。
一图胜千言:
对于 JDK 线程池的 7 个参数和执行流程。
尽管我很久没有加入面试了,然而我感觉这题属于必考题吧。
所以如果你真的还不会,麻烦你写个 Demo,换几个参数调试一下。把它给把握了。
而且还得多留神由这些知识点引申进去的面试题。
比方从图片也能够看进去,JDK 线程池中如果外围线程数曾经满了的话,那么前面再来的申请都是放到阻塞队列外面去,阻塞队列再满了,才会启用最大线程数。
然而你得晓得,如果咱们是 web 服务,申请是通过 Tomcat 进来的话,那么 Tomcat 线程池的执行流程可不是这样的。
Tomcat 外面的线程池的运行过程是:如果外围线程数用完了,接着用最大线程数,最初才提交工作到队列外面去的。这样是为了保障响应工夫优先。
所以,Tomcat 的执行流程是这样的:
其技术细节就是本人重写了队列的 offer 办法。在这篇文章外面说的很分明了,大家能够看看:
《每天都在用,但你晓得 Tomcat 的线程池有多致力吗?》
好的,后面两个知识点铺垫实现了。
这个题,从线程池设计的角度,我会这样去答复:
后面咱们说了,10 个机器,1000 个申请并发,均匀每个服务承当 100 个申请。服务器是 4 核的配置。
那么如果是 CPU 密集型的工作,咱们应该尽量的缩小上下文切换,所以外围线程数能够设置为 5,队列的长度能够设置为 100,最大线程数放弃和外围线程数统一。
如果是 IO 密集型的工作,咱们能够适当的多调配一点外围线程数,更好的利用 CPU,所以外围线程数能够设置为 8,队列长度还是 100,最大线程池设置为 10。
当然,下面都是实践上的值。
咱们也能够从外围线程数等于 5 开始进行零碎压测,通过压测后果的比照,从而确定最合适的设置。
同时,我感觉线程池的参数应该是随着零碎流量的变动而变动的。
所以,对于外围服务中的线程池,咱们应该是通过线程池监控,做到提前预警。同时能够通过伎俩对线程池响应参数,比方外围线程数、队列长度进行动静批改。
下面的答复总结起来就是四点:
- CPU 密集型的状况。
- IO 密集型的状况。
- 通过压测失去正当的参数配置。
- 线程池动静调整。
前两个是教科书上的答复,记下来就行,面试官想听到这两个答案。
后两个是更具备实际意义的答复,让面试官眼前一亮。
基于这道面试题无限的信息,设计进去的线程池队列长度其实只有大于 100 就能够。
甚至还能够设置的极限一点,比方外围线程数和最大线程数都是 4,队列长度为 96,刚好能够承当这 100 个申请,多一个都不行了。
所以这题我感觉从这个角度来说, 并不是要让你给出一个完满的解决方案,而是考查你对于线程池参数的了解和技术的使用。
面试的时候我感觉这个题答到这里就差不多了。
接下来,咱们再发散一下。
比方面试官问:如果咱们的零碎外面没有使用线程池,那么会是怎么样的呢?
首先假如咱们开发的零碎是一个运行在 Tomcat 容器外面的,对外提供 http 接口的 web 服务。
零碎中没有使用线程池相干技术。那么咱们能够间接抗住这 100 个并发申请吗?
答案是能够的。
Tomcat 外面有一个线程池。其 maxThreads 默认值是 200(假设 BIO 模式):
maxThreads 用完了之后,进队列。队列长度(acceptCount)默认是 100:
在 BIO 的模式下,Tomcat 的默认配置,最多能够承受到 300(200+100)个申请。再多就是连贯回绝,connection refused。
所以,你要说解决这 100 个并发申请,那不是入不敷出吗?
然而,如果是每秒 100 个并发申请,源源不断的过去,那就必定是吃不消了。
这里就波及到两个层面的批改:
- Tomcat 参数配置的调优。
- 零碎代码的优化。
针对 Tomcat 参数配置的调优,咱们能够适当调大其 maxThreads 等参数的值。
针对零碎代码的优化,咱们就能够引入线程池技术,或者引入音讯队列。总之其目标是减少零碎吞吐量。
同理,假如咱们是一个 Dubbo 服务,对外提供的是 RPC 接口。
默认状况下,服务端应用的是 fixed 线程池,外围线程池数和最大线程数都是 200。队列长度默认为 0:
那么解决这个 100 个并发申请也是入不敷出的。
同样,如果是每秒 100 个并发申请源源不断的过去,那么很快就会抛出线程池满的异样:
解决套路其实是和 Tomcat 的状况差不多的,调参数,改零碎,加异步。
这个状况下的并发,大多数零碎还是抗住的。
面试官还能够接着诘问:如果这时因为搞促销流动,零碎流量翻了好倍,那你说这种状况下最先呈现性能瓶颈的中央是什么?
最先出问题的中央必定是数据库嘛,对吧。
那么怎么办?
扩散压力。分库分表、读写拆散这些货色往上套就完事了。
而后在零碎入口的中央削峰填谷,引入缓存,如果能够,把绝大部分流量拦挡在入口处。
对于拦不住的少量流量,要害服务节点还须要反对服务熔断、服务降级。
切实不行,加钱,堆机器。没有问题是不能通过堆机器解决的,如果有,那么就是你堆的机器不够多。
面试反正也就是这样的套路。看似一个发散性的题目,其实都是有套路可寻的。
好了,第一个角度我感觉我能想到的就是这么多了。
首先侧面答复了面试官线程池设计的问题。
而后分状况聊了一下如果咱们我的项目中没有用线程池,能不能间接抗住这 1000 的并发。
最初简略讲了一下突发流量的状况。
接下来,咱们聊聊负载平衡。
负载平衡策略
我感觉这个考点尽管略微暗藏了一下,但还是很容易就开掘到的。
毕竟题目中曾经说了:10 台机器。
而且咱们也假如了均匀 1 台解决 100 个状况。
这个假如的背地其实就是一个负载平衡策略:轮询负载平衡。
如果负载平衡策略不是轮询的话,那么咱们后面的线程池队列长度设计也是有可能不成立的。
还是后面的场景,如果咱们是运行在 Tomcat 容器中,假如后面是 nginx,那么 nginx 的负载平衡策略有如下几种:
- (加权)轮询负载平衡
- 随机负载平衡
- 起码连接数负载平衡
- 最小响应工夫负载平衡
- ip_hash 负载平衡
- url_hash 负载平衡
如果是 RPC 服务,以 Dubbo 为例,有上面几种负载平衡策略:
- (加权)轮询负载平衡
- 随机负载平衡
- 起码沉闷数负载平衡
- 最小响应工夫负载平衡
- 一致性哈希负载平衡
哦,对了。记得之前还有一个小伙伴问我, 在 Dubbo + zookeeper 的场景下,负载平衡是 Dubbo 做的还是 zk 做的?
必定是 Dubbo 啊,敌人。源码都写在 Dubbo 外面的,zk 只是一个注册核心,关怀的是本人治理着几个服务,和这几个服务的高低线。
你要用的时候,我把所有能用的都给你,至于你到底要用那个服务,也就是所谓的负载平衡策略,这不是 zk 关怀的事件。
不扯远了,说回来。
假如咱们用的是随机负载平衡,咱们就不能保障每台机器各自承当 100 个申请了。
这时候咱们后面给出的线程池设置就是不合理的。
常见的负载平衡策略对应的优缺点、实用场景能够看这个表格:
对于负载平衡策略,我的《吐血输入:2 万字长文带你细细盘点五种负载平衡策略》这篇文章,写了 2 万多字,算是写的很分明了,这里就不赘述了。
说起负载平衡,我还想起了之前阿里举办的一个程序设计大赛。赛题是《自适应负载平衡的设计实现》。
赛题的背景是这样的:
负载平衡是大规模计算机系统中的一个根底问题。灵便的负载平衡算法能够将申请正当地调配到负载较少的服务器上。
现实状态下,一个负载平衡算法应该可能最小化服务响应工夫 (RTT),使零碎吞吐量最高,放弃高性能服务能力。
自适应负载平衡是指无论处在闲暇、稳固还是忙碌状态,负载平衡算法都会主动评估零碎的服务能力,更好的进行流量调配,使整个零碎始终保持较好的性能,不产生饥饿或者过载、宕机。
具体题目和获奖团队问难能够看这里:
题目:https://tianchi.aliyun.com/competition/entrance/231714/information?spm=a2c22.12849246.1359729.1.6b0d372cO8oYGK
问难:https://tianchi.aliyun.com/course/video?spm=5176.12586971.1001.1.32de8188ivjLZj&liveId=41090
举荐大家有趣味的去看一下,还是很有意思的,能够学到很多的货色。
扩大浏览
这一大节,我截取自《分布式系统架构》这本书外面,我感觉这个示例写的还不错,分享给大家:
这是一个购物商场的例子:
零碎部署在一台 4C/8G 的应用服务器上、数据在一台 8C/16G 的数据库上,都是虚拟机。
假如零碎总用户量是 20 万,日均沉闷用户依据不同零碎场景稍有区别,此处取 20%,就是 4 万。
依照零碎划分二八法令,零碎每天顶峰算 4 小时,高峰期沉闷用户占比 80%, 顶峰 4 小时内有 3.2 万沉闷用户。
每个用户对系统发送申请,如每个用户发送 30 次,顶峰期间 3.2 万用户发动的申请是 96 万次,QPS=960 000/(4x60x60)≈67 次申请,每秒解决 67 次申请,解决流程如下图有所示:
一次利用操作数据库增删改查(CRUD)次数均匀是操作利用的三倍,具体频率依据零碎的操作算平均值即可。一台利用、数据库能解决多少申请呢?
具体分析如下。
- 首先利用、数据库都别离部署在服务器,所以和服务器的性能有间接关系,如 CPU、内存、磁盘存储等。
- 利用须要部署在容器外面,如 Tomcat、Jetty、JBoss 等,所以和容器有关系,容器的零碎参数、配置能减少或缩小解决申请的数目。
- Tomcat 部署利用。Tomcat 外面须要分配内存,服务器共 8GB 内存,服务器次要用来部署利用,无其余用处,所以设计 Tomcat 的可用内存为 8 /2=4GB (物理内存的 1 /2),同时设置一个线程须要 128KB 的内存。因为应用服务器默认的最大线程数是 1000(能够参考零碎配置文件),思考到零碎本身解决能力,调整 Tomcat 的默认线程数至 600,达到零碎的最大解决线程能力。到此一台利用最大能够解决 1000 次申请,当超过 1000 次申请时,暂存到队列中,期待线程实现后进行解决。
- 数据库用 MySQL。MySQL 中有连接数这个概念,默认是 100 个,1 个申请连贯一次数据库就占用 1 个连贯,如果 100 个申请同时连贯数据库,数据库的连接数将被占满,后续的连贯须要期待,期待之前的连贯开释掉。依据数据库的配置及性能,可适当调整默认的连接数,本次调整到 500,即能够解决 500 次申请。
显然以后的用户数以及申请量达不到高并发的条件,如果沉闷用户从 3.2 万扩充到 32 万,每秒解决 670 次申请,曾经超过默认最大的 600,此时会呈现高并发的状况,高并发分为高并发读操作和高并发写操作。
好了,书上分享的案例就是这样的。
荒腔走板
上周五早晨去看了《金刚川》。
据说拍摄周期只有 2 个月,然而电影整体来说还是挺难看的。前半段比拟的平缓,然而后半段高潮迭起。对我而言,是有好几个泪点的。
第一个泪点是“喀秋莎”火箭炮进去的时候,像烟花一样,兵士们说:这就是咱们的喀秋莎吧?
第二个泪点是张译表演的张飞,一个人反抗美军侦察机,在高炮上高呼:“千古流芳鲁莽人”的时候。
用老张的话说:不要认为这是神剧套路,历史现场比这还要惨烈。
张译的演技,没的说。一个人撑起了这个片段的一半。影帝预约一个。
第三个泪点就是燃烧弹落到江面,而后随之响起的《我的祖国》BGM 了:
一条大河波浪宽
风吹稻花香两岸
我家就在岸上住
听惯了艄公的号子
看惯了船上的白帆
这是漂亮的祖国
是我成长的中央
配合着整个修桥的故事,几乎就是泪点暴击。
瞎话瞎话,《金刚川》这个电影不是特地的完满。然而我还是力荐大家去电影院反对这部电影。
因为这部电影,拍在抗美援朝 70 周年纪念的这个非凡节点。能让有幸生存在战争时代的咱们晓得,当初的战争长安,国富民强不是从天上掉下来的,是 70 年前,那一群只有十七八岁的“最可恶的人”用生命打下来的。
之前看《人民日报》外面的一个短视频,一位老兵说的话特地的打动,他说:
什么是祖国?当咱们跨过鸭绿江,看到战火的时候,我身后就是祖国。
战争来之不易,吾辈自当珍惜。
向最可恶的人致敬。
最初说一句 (求关注)
好了,看到了这里安顿个“一键三连”吧,周更很累的,不要白嫖我,须要一点正反馈。
满腹经纶,难免会有纰漏,如果你发现了谬误的中央,能够在留言区提出来,我对其加以批改。感谢您的浏览,我保持原创,非常欢送并感谢您的关注。
我是 why,一个被代码耽搁的文学创作者,不是大佬,然而喜爱分享,是一个又暖又有料的四川好男人。
还有,欢送关注我呀。