高并发系统的艺术:如何在流量洪峰中游刃有余
01
前言
理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将 我们经常会说互联网“三高”,那什么是三高呢?我们常说的三高,高并发、高可用、高性能,这些技术是构建现代互联网应用程序所必需的。对于京东618备战来说,所有的中台系统服务,无疑都是围绕着三高来展开的。而对于京东庞大的客户群体,高并发的要求尤为重要。用户对在线服务的需求和期望不断提高,系统的并发处理能力成为衡量其性能和用户体验的关键指标之一。高并发系统不仅仅是大型互联网企业的专利,对于任何希望在市场中占据一席之地的公司来说,能够处理大量并发请求的能力都是至关重要的。 高并发系统的设计和实现是一个复杂且多层次的过程,涉及到硬件资源的合理利用、系统架构的精心设计、并发控制技术的应用以及性能调优等多个方面。无论是电商平台在大促期间应对突发流量,还是社交媒体在热点事件发生时的流量激增,抑或是金融系统在交易高峰期的平稳运行,都需要一个高效、稳定、可扩展的高并发系统作为支撑。 接下来我通过一张思维导图展开我的分享,帮大家梳理一下一个高并发系统所需要考虑的技术点。
02
单机维度
理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将 在单机维度上, 我们一般分为硬件维度和代码维度两个方向考虑。硬件维度比较简单,就是提升单机的硬件性能和网络带宽。而代码维度,则是在高并发系统架构设计时,最容易被大家忽视的,尤其是现在大量的互联网架构师脱离一线研发并进化成PPT架构师的今天,单机维度基本不在考量范围。 但不积跬步无以至千里,有的时候单机接口的的性能优化,会带来很高的经济成本价值。在代码维度,我这里重点介绍一种情况,关于多线程和异步方法。
2.1 多线程和异步方法的误区
关于多线程和异步方法的概念,我在面试候选人的时候,发现很多人对此都有误区。在此,我先详细的一下他俩的概念: 多线程:多线程是指在一个进程中可以同时运行多个线程,每个线程执行不同的任务。Java通过java.lang.Thread类和java.util.concurrent包提供了多线程编程的支持。多线程的主要目的是为了充分利用CPU资源,提高程序的执行效率。 异步方法:异步方法是指在调用某个方法时,不需要等待该方法执行完成即可继续执行后续代码。Java通过CompletableFuture和异步回调机制提供了异步编程的支持。异步方法的主要目的是为了提高系统的响应能力和资源利用率。2.2 多线程能够解决高并发场景么
当大家了解了多线程和异步方法的概念后,那么我们就可以认真思考一下,多线程一定能提升系统的并发能力么? 我的结论是:多线程可以提升不分服务的并发能力,但并不能显著提高性能。 首先我们先了解,Tomcat的Servlet机制是基于多线程实现的,而如果你在单次请求中在此开辟线程池进行多线程处理,在一定的并发情况下,你可能只是改善了单次请求的TP99,但无法有效提升系统的并发能力。因为多线程的性能提升与CPU核心数密切相关。如果系统只有一个CPU核心,那么多个线程只能在该核心上轮流执行,无法实现真正的并行处理。而我们的宿主机一般也就是8C或者16C,在面单机上千的QPS请求时,多线程只会增加CPU上下文切换的负担。 举个简单并且常见的例子,批量下单接口。我们常见的做法就是在批量下单接口中开辟线程池,然后建个多个下单在线程池中并行处理。这样做的结果是,在请求量低的情况下,效果还是可以的,单次请求的QPS也会很低,但如果单机面临每秒上千次的下单请求,这种实现方式就会出现问题。最直观的观察,可以通过TP99的监控曲线发现,就是请求量跟TP99呈现严重的正相关性。 而真正有效的提升下单接口的并发能力,是通过异步方式实现。但异步方式又会增加系统的设计复杂度,比如下单失败,回调设计和数据一致性设计等等,也在考量范围之内,这里就不详细展开说明。 2.3 小结 多线程和异步方法是Java开发中两种重要的并发处理技术,它们在提高系统性能和响应能力方面各有优势。多线程通过并行处理任务,充分利用CPU资源,适用于CPU密集型任务和需要并行处理的场景。异步方法通过非阻塞I/O操作和异步回调机制,提高系统的响应能力和资源利用率,适用于I/O密集型任务和事件驱动架构。03
多机维度
理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将 在多机维度考虑系统的高并发性能,应该是大家最长能够想到的场景了,也是架构师们最热衷讨论的点。
首先是对系统的拆分角度来说,第一个是单体应用的水平扩展问题,就是我们所说的负载均衡集群,换成我们经常听到的一个词:扩容。扩容一般针对负载均衡集群进行水平扩展,用于解决单机无法承载高并发的情况,这也是互联网公司解决高并发场景的最常用手段,就比如每次双十一或者618前夕,我们都会成倍的扩容我们的服务实例。 对系统的另一个拆分角度,叫做垂直拆分,也就是我们常见的分布式系统。比如按照领域划分,我们将一个大的单体服务,拆分成不同的子领域系统,然后每个子领域系统单独承担各自的流量,而不会相互影响。还比如说长江的CQRS设计架构,翻译过来是指令查询分离的设计方式,通过查询和指令服务拆分,来讲高并发的查询场景单独拆分出来进行设计。既然采用了分布式的微服务架构,那么分布式系统的一些常见痛点也是高 并发要考虑的 ,比如熔断,降级,限流,超时等设计,这些本身是为了增强分布式系统的鲁棒性,从而简介的增强系统的高并发承载能力。关于微服务架构,在此处不再赘述,有兴趣的同学可以看我的另一篇文章:《【实践篇】教你玩转微服务--基于DDD的微服务架构落地实践之路》(https://developer.jdcloud.com/article/2899?mid=30)
04 垂直维度
理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将 所谓垂直维度,是区分于单机维度和多机维度的,垂直的意思是针对一个业务系统在系统层级的垂直划分,包括业务应用和数据库。要知道,很多高并发场景,不管是写场景还是读场景,当数据库维度出现瓶颈,扩容就不想业务应用服务那么简单了,所以要区分来说。 4.1 数据库 在存储媒介这块其实高并发是不好设计的。比如关系型数据库MySQL, 在进行扩展要比业务应用复杂不少,涉及到的就是数据库的分库分表逻辑。 这块可以参考之前我写过的一篇文章:《分而治之--浅谈分库分表及实践之路》(https://developer.jdcloud.com/article/2963?mid=30)。 而对于读场景下的高并发请求,还有一种最常见的处理手段,就是异构存储介质,实现读写分离,最常见的就是MySQL关系型数据库负责写,ES这种文档类数据库负责读。而他的技术难点则在于数据的同步和数据一致性上。 4.2 业务应用 唯物辩证法中有一个重要概念,就是一切从实际出发,具体问题具体分析。对于高并发系统的构建,虽然有通用的手段和方法论,但没有统一的落地方案,必须根据具体的业务应用场景进行分析和设计。比如你的系统是高并发读还是高并发写,处理思路也是完全不一样的。当然常见的手段和方法论核心包括两点:缓存和异步。但具体到相应的业务,需要仔细思考缓存逻辑怎么设计,异步流程怎么设计,如何保证数据一致性等等。接下来,我也将通过实战场景,对这一理论进行更为直观的解析。
05
理论和实战兼得
理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将 接下来讲述一个真实的业务场景。 随着互联网业务的不断发展,选择在网上购物的人群不断增加,这种情况下,会衍生出一些促销活动,类似抢购场景或者热销热卖场景,在高峰时段的下单数量会非常大,也意味着对数据库中畅销商品的库存操作十分频繁,需要频繁查库存和更新库存。这属于高读写场景,比起单独的并发读和并发写来说,业务场景更复杂一些。那么这种高并发为了保证库存数据一致性,一般会在数据库更新时进行加锁操作,以保证系统不会发生超卖情况。 我们应该如何应对呢?大家可以根据我之前的思维导图,跟随我的思路,一起来看如何解决当前场景下的高并发问题。
5.1 小试牛刀
面对库存扣减的场景,我们第一个考虑到是数据一致性问题,因为超卖会对我们的履约和客户信誉造成影响。所以一般情况下,在数据库更新时进行加锁操作,以保证系统不会发生超卖情况。所以更多方案是提高数据库性能方法,比如增加硬件性能,优化乐观锁,提升锁效率,优化SQL性能等。对于一些大型系统,也衍生出一些基于分片的库存方案,通过分库分表增加并发吞吐量。 当然那这样不够,因为MySQL数据库的读写的并发上线能力是有限的,我们还是需要再进一步优化我们的方案。这里就要参考之前我写的那篇文章中的思维导论了,这里常见解决方案就是,引入缓存机制。 如下图所示,我们把读请求进行缓存,每次库存校验时,我们引入redis缓存,读请求通过缓存,增加接口性能,然后库存扣减时,在进行缓存同步。5.2 循序渐进
为此,我们可以进行一次优化,通过架构维度进行调整。 在这个方案中,我们将库存操作封装成一个单独模块,这个方案的优化点在于,所有库存的查询和扣减都围绕redis进行。当发生库存扣减操作时,会直接更新redis,同时采用异步流程,更新MySQL数据库。这样以来,我们的性能会比直接访问MySQL数据库高效不少,并发能力会有不少提升。 流程如下:
5.3 大显身手
那么接下来,我们需要考虑的是如何可以实现我们业务系统并发能力的水平扩展能力。当然这里也不是凭空来想,我们可以思考一下,业内成熟的一些中间件是如何实现高并发的,这里我们可以两个我们常见的框架:kafka和elasticsearch。 上述我们常见的两个中间件框架,都以可以水平能力扩展著称。那么仔细思考一下他们的技术架构不难发现,他们的核心其实都是采用了一种所谓的分片实现的。那么问题来了,我们的库存扣减,能不能实现分片呢?或者换一个思路思考这个问题:我们的库存逻辑是否可以转化为分布式库存进行存储和扩展呢? 有了以上的思路,我们就可以开始构建我们的架构方案了。接下来,我先把架构图贴出来:
06
总结
理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将 再回到文章开篇的那张思维导图,它表达的是当你遇到一个高并发场景时,你需要经历的思考过程。但做好以上这些也并不能说明你的系统一定能承载高并发了。还是那句话,具体问题具体分析。 构建高并发系统的核心在于如何高效地处理大量同时发生的请求,保证系统的稳定性、性能和可扩展性。这里涉及到合理的业务架构设计,以及高效的并发编程模型。 除此之外,也要考虑到数据一致性和事务管理,配合合理的监控和自动化运维,保证及时出现系统资源紧张和崩溃时,可以快速反应和解决问题。
推荐阅读 引入JaCoCo导致的类型转换问题分析
如何手搓一个自定义的RPC(远程过程调用框架)
从C端到B端:我的前端技术进阶之路
Code Review:提升代码质量与团队能力的利器
底层能力:维护用户基础数据、行为数据建模、用户画像分析、精准营销策略的制定 ▪ 功能支撑:会员成长体系、等级计算策略、权益体系、营销底层能力支持 ▪用户活跃:会员关怀、用户触达、活跃活动、业务线交叉获客、拉新促活