在高并发系统的设计中,面对大流量的挑战,我们通常需要运用一些巧妙的方案来有效地分流和处理这些流量,从而保证系统的稳定性和用户体验。可以通过一个比喻来帮助理解:就像古代治水一样,我们在高并发系统中采用的策略,也旨在将洪水引流、分担压力、提高系统的承载能力。
例如,在古代的治水实践中,大禹通过拓宽河道清除淤沙,让水流更加顺畅;都江堰则利用引流技术将岷江的水分流到多个支流,从而减少水流的压力;而三门峡和葛洲坝通过建造水库来暂时存储水源,再通过控制水库的排水来缓解下游的水患。这些治水的思想,实际上在现代高并发系统的设计中也能找到类似的解决方案。
总结起来,处理高并发的流量,我们主要会采取三种策略:
这三种策略是高并发系统设计中常见的手段,当然,每种方法背后还有更为复杂和细化的技术细节,之后会根据具体场景进一步讲解。这些策略的灵活运用,可以帮助我们设计出既高效又可靠的高并发系统,确保系统在面对大流量时能够从容应对。
首先,我们先来了解第一种方法:Scale-out。
Scale-up vs Scale-out
“摩尔定律”由Intel创始人之一戈登·摩尔在1965年提出,最初是指集成电路上晶体管的数量大约每两年会翻一倍。这一理论后来被Intel CEO大卫·豪斯用“18个月翻一倍”的说法进一步简化,并广泛流传。摩尔定律主要描述了芯片技术的发展速度,但它也可以延伸到整体硬件性能的提升。从20世纪下半叶开始,计算机硬件的性能一直呈现指数级的增长,这一趋势持续至今。摩尔定律指引下的技术突破,使得我们从最初只有十几个晶体管的集成电路,到今天每颗芯片上拥有数十亿晶体管,芯片厂商在有限的空间内,通过减小晶体管的尺寸,不断提升硬件性能。
然而,随着芯片工艺的进步,已有专家预测摩尔定律可能会在未来几年失效。如今的芯片已经进入了5nm制程,进一步减小晶体管尺寸的空间变得非常有限,可能无法继续以每18个月翻倍的速度提升性能。在这种背景下,双核和多核技术的出现为延续摩尔定律的理念提供了新的方向。通过将多个CPU核心集成到同一芯片上,CPU的并行处理能力得到了大幅提升,这一思路在高并发系统设计中也得到了应用。
在高并发系统中,类似于摩尔定律不断提升单机性能的做法,被称为Scale-up(纵向扩展)。而将多个处理单元组合成一个集群来提升性能,则被称为Scale-out(横向扩展)。这两者在实现方式上有着明显不同。
在实际的系统设计中,通常在初期阶段选择Scale-up,因为它能够通过提升单机硬件性能解决问题,操作简单;但随着系统并发量的增长,单机的性能瓶颈逐渐显现,Scale-out成为应对更高并发流量的解决方案。尽管Scale-out具有更强的扩展性,但也需要解决更复杂的技术挑战。
说完了 Scale-out,我们再来看看高并发系统设计的另一种方法:缓存。
使用缓存提升性能
Web 2.0 被称为“缓存的时代”,这一点几乎是公认的。如今,缓存已经渗透到系统设计的各个层面,从操作系统到浏览器,从数据库到消息队列,任何稍微复杂的服务和组件中都能看到缓存的身影。缓存的主要作用是提高系统的访问性能,尤其是在高并发场景中,缓存能显著提升系统的响应速度,从而支持更多用户同时访问。
那么,为什么缓存能大幅度提升系统的性能呢?我们知道,数据通常存储在持久化存储中,且大多数持久化存储使用磁盘作为存储介质。传统磁盘由机械手臂、磁头、转轴和盘片组成,盘片被划分为多个同心圆,称为磁道。这些磁道用来存储数据。磁盘工作时,盘片高速旋转,机械手臂带动磁头沿着径向移动,定位到需要读取的磁道。在此过程中,磁头找到数据的时间被称为寻道时间,这是磁盘存取数据的瓶颈之一。
普通磁盘的寻道时间大约是10毫秒,而与此相比,CPU执行指令和内存寻址的速度通常在纳秒(ns)级别,从千兆网卡读取数据的时间则是在微秒(μs)级别。可以看出,磁盘在整个计算机系统中是最慢的组件,甚至比其他部件慢上几个数量级。因此,为了提升性能,我们通常采用内存作为存储介质来实现缓存。
缓存的作用不仅仅限于简单的存储,它的语义已经变得更加丰富。在现代系统设计中,任何可以显著降低响应时间的中间存储都可以被称为缓存。例如,在操作系统中,CPU就有多级缓存来减少访问主内存的时间;文件系统中也有Page Cache缓存,用来提高文件读取的效率。缓存的思想已经广泛应用于多个领域,不仅限于传统的数据库缓存,而是渗透到计算机系统的方方面面。
异步处理
异步是一种常见的高并发设计方法,我们在很多技术文章和演讲中常常会看到这个术语,同时它也经常与“同步”相提并论。例如,在分布式服务框架Dubbo中,我们可以看到同步方法调用和异步方法调用;在IO模型中,也有同步IO和异步IO。那么,什么是同步,什么是异步呢?
以方法调用为例,同步调用意味着调用方必须等待被调用的方法执行完成后才能继续执行下一步操作。在同步调用的情况下,如果被调用的方法响应时间较长,调用方会一直阻塞,直到方法执行完成。这种方式在高并发场景下会导致大量阻塞,最终可能引发整体系统性能下降,甚至发生“雪崩效应”。
异步调用则正好相反,调用方不需要等待被调用方法的逻辑执行完成,而是可以立刻返回并继续执行其他操作。当被调用的方法完成后,结果会通过回调、事件通知等机制反馈给调用方。异步调用非常适合大规模高并发的系统中使用。例如,我们都知道的12306网站,订票时页面会显示“系统正在排队”的提示,这其实就表示系统在异步处理我们的订票请求。
在12306系统中,查询余票、下单、以及更新余票状态等操作都是比较耗时的,可能涉及多个内部系统的调用。如果这些操作是同步的,那么在高并发的情况下,就像12306刚上线时那样,很多用户在高峰期永远无法成功下单。而采用异步处理后,系统能够更有效地分担负载,提升整体处理能力和用户体验。
而采用异步的方式,后端处理时会把请求丢到消息队列中,同时快速响应用户,告诉用户我们正在排队处理,然后释放出资源来处理更多的请求。订票请求处理完之后,再通知用户订票成功或者失败。处理逻辑后移到异步处理程序中,Web 服务的压力小了,资源占用的少了,自然就能接收更多的用户订票请求,系统承受高并发的能力也就提升了。
了解了这些方法后,我们可能会问,是否在高并发系统设计中就必须把这些方法都应用到位?答案是否定的。系统设计是一个不断演进的过程,"罗马不是一天建成的",系统的设计也是如此。不同规模的系统会面临不同的痛点,进而会有不同的架构设计侧重点。如果在一开始就按照百万、千万并发量来设计系统,盲目追求像淘宝、微信那样的架构,那么这些系统的命运很可能会是失败的。
为什么呢?因为尽管像淘宝、微信这样的大型系统能够处理百万、千万并发的需求,但它们内部的复杂性是我们难以想象的。盲目地追随这些大系统的设计思路,只会让我们的架构变得异常复杂,最终导致难以维护和扩展。比如说,淘宝在经历多年的发展后,才发现自己在整体扩展能力上的瓶颈,这时他们才开始进行服务化改造。
我自己也曾经历过类似的情况。在参与的一个创业项目中,我们在初期就选择了服务化架构。但由于当时人力有限,团队的技术积累不足,在实际开发过程中我们发现无法驾驭如此复杂的架构,导致了很多问题:比如问题定位困难、系统性能下降,甚至系统宕机时都很难找到根本原因。最终,我们不得不将服务进行整合,回归到更简单的单体架构中。
这也提醒我们,在设计高并发系统时,必须根据实际的业务需求、技术储备和团队能力来选择合适的架构,并随系统的发展不断调整和优化,而不是一开始就过于复杂化。
所以我建议一般系统的演进过程应该遵循下面的思路:
最初的系统设计应当以满足当前的业务需求和流量情况为目标,并选择团队最熟悉的技术体系。在实际开发过程中,随着流量的增加和业务的发展,我们可能会发现架构中的一些问题,比如单点故障、横向扩展的瓶颈,或是性能无法满足需求的组件。此时,我们需要逐步修正这些问题。
在解决问题时,我们首先会考虑选择那些社区中已经成熟并且团队熟悉的组件,这样可以更高效地应对问题并避免重复造轮子。如果社区没有合适的解决方案,才会考虑自行开发或定制解决方案。
然而,当对现有架构进行的小修小补已无法解决问题时,我们需要考虑更大规模的调整,如架构重构或重写。通过这些更为深度的调整,才能有效解决现有架构无法满足业务需求的难题。总之,系统架构的设计和演进是一个不断调整和优化的过程,应根据实际需求逐步推进。
以淘宝为例,最初在业务从0到1的阶段,淘宝通过购买现成的硬件和软件快速搭建了系统。然而,随着流量的不断增长,淘宝开始进行一系列的技术改造,以提高系统的高并发处理能力。这些改造包括将数据库存储引擎从MyISAM迁移到InnoDB,实施数据库的分库分表,增加缓存,并开发中间件等。当这些优化手段不再满足需求时,淘宝进一步对整体架构进行大规模重构,例如著名的“五彩石”项目,这使得淘宝的架构从单体架构逐步演进为服务化架构。
正是通过这些逐步的技术演进,淘宝才最终构建了能够支撑过亿QPS的技术架构。归根结底,高并发系统的演进应当是循序渐进的,始终围绕解决系统中存在的问题展开,技术的不断演化和优化才是推动架构进步的真正驱动力。