这是why哥的第 71 篇原创文章 一道面试题兄弟们,怎么说? 我觉得如果你工作了两年左右的时间,或者是突击准备了面试,这题回答个八成上来,应该是手到擒来的事情。这题中规中矩,考点清晰,可以说的东西不是很多。 但是这都上血书了,那不得分析一波? 先把这个面试题拿出来一下: 1000 多个并发线程,10 台机器,每台机器 4 核,设计线程池大小。 这题给的信息非常的简陋,但是简陋的好处就是想象空间足够大。 第一眼看到这题的时候,我直观的感受到了两个考点:
我就开门见山的给你说了,这两个考点,刚好都在我之前的文章的射程范围之内: 《如何设置线程池参数?美团给出了一个让面试官虎躯一震的回答》 《吐血输出:2万字长文带你细细盘点五种负载均衡策略》 下面我会针对我感受到的这两个考点去进行分析。 线程池设计我们先想简单一点:1000 个并发线程交给 10 台机器去处理,那么 1 台机器就是承担 100 个并发请求。 100 个并发请求而已,确实不多。 而且他也没有说是每 1 秒都有 1000 个并发线程过来,还是偶尔会有一次 1000 个并发线程过来。 先从线程池设计的角度去回答这个题。 要回答好这个题目,你必须有两个最基本的知识贮备:
先说第一个,自定义线程池的 7 个参数。 java.util.concurrent.ThreadPoolExecutor#ThreadPoolExecutor 害,这 7 个参数我真的都不想说了,你去翻翻历史文章,我都写过多少次了。你要是再说不出个头头是道的,你都对不起我写的这些文章。 而且这个类上的 javadoc 已经写的非常的明白了。这个 javadoc 是 Doug Lea 老爷子亲自写的,你都不拜读拜读? 为了防止你偷懒,我把老爷子写的粘下来,我们一句句的看。 关于这几个参数,我通过这篇文章再说最后一次。 如果以后的文章我要是再讲这几个参数,我就不叫 why 哥,以后你们就叫我小王吧。 写着写着,怎么还有一种生气的感觉呢。似乎突然明白了当年在讲台上越讲越生气的数学老师说的:这题我都讲了多少遍了!还有人错? 好了,不生气了,说参数:
第一个知识贮备就讲完了,你先别开始背,这玩意你背下来有啥用,你得结合着执行流程去理解。 接下来我们看第二个: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 开始进行系统压测,通过压测结果的对比,从而确定最合适的设置。 同时,我觉得线程池的参数应该是随着系统流量的变化而变化的。 所以,对于核心服务中的线程池,我们应该是通过线程池监控,做到提前预警。同时可以通过手段对线程池响应参数,比如核心线程数、队列长度进行动态修改。 上面的回答总结起来就是四点:
前两个是教科书上的回答,记下来就行,面试官想听到这两个答案。 后两个是更具有实际意义的回答,让面试官眼前一亮。 基于这道面试题有限的信息,设计出来的线程池队列长度其实只要大于 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 参数配置的调优,我们可以适当调大其 maxThreads 等参数的值。 针对系统代码的优化,我们就可以引入线程池技术,或者引入消息队列。总之其目的是增加系统吞吐量。 同理,假设我们是一个 Dubbo 服务,对外提供的是 RPC 接口。 默认情况下,服务端使用的是 fixed 线程池,核心线程池数和最大线程数都是 200。队列长度默认为 0: 那么处理这个 100 个并发请求也是绰绰有余的。 同样,如果是每秒 100 个并发请求源源不断的过来,那么很快就会抛出线程池满的异常: 解决套路其实是和 Tomcat 的情况差不多的,调参数,改系统,加异步。 这个情况下的并发,大多数系统还是抗住的。 面试官还可以接着追问:如果这时由于搞促销活动,系统流量翻了好倍,那你说这种情况下最先出现性能瓶颈的地方是什么? 最先出问题的地方肯定是数据库嘛,对吧。 那么怎么办? 分散压力。分库分表、读写分离这些东西往上套就完事了。 然后在系统入口的地方削峰填谷,引入缓存,如果可以,把绝大部分流量拦截在入口处。 对于拦不住的大批流量,关键服务节点还需要支持服务熔断、服务降级。 实在不行,加钱,堆机器。没有问题是不能通过堆机器解决的,如果有,那么就是你堆的机器不够多。 面试反正也就是这样的套路。看似一个发散性的题目,其实都是有套路可寻的。 好了,第一个角度我觉得我能想到的就是这么多了。 首先正面回答了面试官线程池设计的问题。 然后分情况聊了一下如果我们项目中没有用线程池,能不能直接抗住这 1000 的并发。 最后简单讲了一下突发流量的情况。 接下来,我们聊聊负载均衡。 负载均衡策略我觉得这个考点虽然稍微隐藏了一下,但还是很容易就挖掘到的。 毕竟题目中已经说了:10 台机器。 而且我们也假设了平均 1 台处理 100 个情况。 这个假设的背后其实就是一个负载均衡策略:轮询负载均衡。 如果负载均衡策略不是轮询的话,那么我们前面的线程池队列长度设计也是有可能不成立的。 还是前面的场景,如果我们是运行在 Tomcat 容器中,假设前面是 nginx,那么 nginx 的负载均衡策略有如下几种:
如果是 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)次数平均是操作应用的三倍,具体频率根据系统的操作算平均值即可。一台应用、数据库能处理多少请求呢? 具体分析如下。
显然当前的用户数以及请求量达不到高并发的条件,如果活跃用户从 3.2 万扩大到 32 万,每秒处理 670 次请求,已经超过默认最大的 600 ,此时会出现高并发的情况,高并发分为高并发读操作和高并发写操作。 好了,书上分享的案例就是这样的。 荒腔走板上周五晚上去看了《金刚川》。 据说拍摄周期只有 2 个月,但是电影整体来说还是挺好看的。前半段比较的平缓,但是后半段高潮迭起。对我而言,是有好几个泪点的。 第一个泪点是“喀秋莎”火箭炮出来的时候,像烟花一样,战士们说:这就是我们的喀秋莎吧? 第二个泪点是张译扮演的张飞,一个人对抗美军侦察机,在高炮上高呼:“千古流芳莽撞人”的时候。 用老张的话说:不要以为这是神剧套路,历史现场比这还要惨烈。 张译的演技,没的说。一个人撑起了这个片段的一半。影帝预定一个。 第三个泪点就是燃烧弹落到江面,然后随之响起的《我的祖国》 BGM 了: 一条大河波浪宽 风吹稻花香两岸 我家就在岸上住 听惯了艄公的号子 看惯了船上的白帆 这是美丽的祖国 是我生长的地方 配合着整个修桥的故事,简直就是泪点暴击。 实话实话,《金刚川》这个电影不是特别的完美。但是我还是力荐大家去电影院支持这部电影。 因为这部电影,拍在抗美援朝 70 周年纪念的这个特殊节点。能让有幸生活在和平时代的我们知道,现在的和平长安,国富民强不是从天上掉下来的,是 70 年前,那一群只有十七八岁的“最可爱的人”用生命打下来的。 之前看《人民日报》里面的一个短视频,一位老兵说的话特别的感动,他说: 什么是祖国?当我们跨过鸭绿江,看到战火的时候,我身后就是祖国。 和平来之不易,吾辈自当珍惜。 向最可爱的人致敬。 最后说一句(求关注)好了,看到了这里安排个 “一键三连”吧,周更很累的,不要白嫖我,需要一点正反馈。 才疏学浅,难免会有纰漏,如果你发现了错误的地方,可以在留言区提出来,我对其加以修改。 感谢您的阅读,我坚持原创,十分欢迎并感谢您的关注。 我是 why,一个被代码耽误的文学创作者,不是大佬,但是喜欢分享,是一个又暖又有料的四川好男人。 欢迎关注我呀。 |
|
来自: 启云_9137 > 《计算机及软件应用》