在这篇文章中,我将尽量涵盖Web应用扩展或性能调优时可能会遇到的一些架构问题。
首先,让我们来统一一些名词或项目的概念,下文中我将列举在扩展Web应用时可能会遇到的多种问题,包括:
- 架构瓶颈
- 数据库扩展
- CPU依赖的应用
- IO依赖的应用
而如何确定优化Web应用线程池大小的内容将会在下一篇文章中给出。
性能(performance)这个词对于Web应用来说通常意味着一些指标,对于大部分的开发者而言,他们主要关心的是响应时间(response time)以及扩展性(scalability)。
- 响应时间
响应时间指的是自Web应用接收到请求到其返回所用的时间。应用的响应时间应该控制在合理的范围之内,如果一个应用的相应时间超出了可接受的时间范围,那么这个应用就不是一个好的应用。
- 扩展性
如果一个应用在增加资源的情况下,其服务请求的能力能够按线性增长,那么这个应用就可以说是可扩展的。有两种增加资源的模式:
- 纵向扩展(Scaling Up) :- 在单个服务单元增加计算能力数量或计算能力性能。
- 横向扩展(Scaling Out) :- 增加更多的服务单元。
横向扩展 vs 纵向扩展
横向扩展考虑的是发挥相对廉价的硬件数量优势,以取代某些需要特别购置的昂贵设备(比如超算)。但是,在单个廉价硬件单元上提升一个服务所能响应的请求数也同样重要。我们说一个应用的性能优异是指它能够在不延长响应时间的情况下通过增加更多的资源而处理更多的请求。
响应时间 vs 扩展性
响应时间和扩展性不会都朝着你所希望的方向变化。举个例子,有些应用可能有不错的响应时间,但是只能处理一定数量的请求;而其他有些应用能够处理不断增长的请求,但却需要消耗比较长的时间。所以我们必须要在这两者之间做出平衡以得到最好的应用性能。
能力规划
能力规划(capacity planning)是弄清产品需要多少硬件资源以处理期望的负载的一种活动。通常它涉及到弄清应用产品性能的极限,即基于更少的服务单元最多能响应多少请求以及单个服务单元的性能。最终,通过负载性能测试来进行验证。
可扩展架构
如果一个应用是多层次架构,并且在每个层次上都是可扩展的(横向扩展),那么这个应用的架构就说是具有扩展性的。举个例子:如下图所示,我们可以在应用层或数据库层线性地增加额外的计算单元。
扩展负载均衡器
负载均衡器能够通过指定DNS到多个IP地址,或是使用DNS轮询的办法进行扩展。还有一种方法是使用分级的的负载均衡器,由前端负载均衡器将请求分发到下一级的负载均衡器。
增加多个负载均衡器对使用nginx或HAProxy的单个计算单元来说是十分罕见的,这类强大的软件能够在单个单元上同时处理超过两万个并发连接,这比其他的web应用容器要强大得多。所以通过它们单个的负载均衡单元能够处理多个web应用单元的请求。
扩展数据库
数据库的扩展是一种最常见的扩展问题。在数据库层中增加额外的业务(存储过程或函数)会带来额外的负担和复杂性。
关系型数据库
关系型数据库可以通过主-从模式进行扩展,这样做可以实现读写分离,即主库负责读写,而从库负责读。主-从模式提供了读的有限扩展,除此之外,开发者们通常将数据库进行划分来进行横向扩展。
NoSQL
CAP理论说得明白,不可能同时满足一致性、可用性以及分区容错性。NoSQL数据库通常牺牲一致性来获取高可用及分区容错性。
数据库划分
数据库的划分可以采用纵向划分(分区)或横向划分(分片)的模式。
- 纵向划分(分区):数据库能够基于领域概念而被划分成为多个松散的子库。举个例子,客户数据库、产品数据库诸如此类。还可以将一个实体少量的列移动到不同的数据库中。比如客户数据库、客户联系方式数据库以及客户订单数据库。
- 横向划分(分片):数据库能够基于一些离散的属性被横向的划分为多个库。比如美国客户数据库、欧洲客户数据库等。
使用分区或分片,将数据库从单库模式转换到多库模式是一项具有挑战性的工作。
架构瓶颈
扩展的瓶颈主要因为两个问题而存在:
- 中心化组件:应用架构中的某个组件,它不能够通过增加整个架构或请求通道处理的请求数量上限来进行横向扩展。
- 高延迟组件:一个速度较慢的组件,拉低了应用请求通道响应时间的下界。通常的解决方式是将此类高延迟组件作为后台任务执行,或是异步地使用队列的方法来执行。
CPU密集型应用
如果一个应用的吞吐量被CPU所限制,那么我们称这个应用为CPU密集型应用(CPU Bound Application)。通过增加CPU的运行速度,可以使应用的响应时间减少。
一些可能的CPU密集型应用场景:
- 应用是用来计算或处理数据的,而不会发生IO操作(金融或交易应用)
- 应用严重依赖缓存,而不会产生IO操作
- 应用是异步的(非阻塞式),不需要等待外部资源(被动模式的应用,NodeJS应用)
在上面提到的场景中,应用已经在高效的运行了,不过有少数应用实例可能是因为代码层面的原因,发生了一些完全不必要的操作去计算或轮询每个请求,进而使CPU利用率很高。通过分析可以很容易的发现低效之处并作出改进。
这些问题可以通过以下方式解决:
- 缓存住先前的计算结果
- 在互相分离的后台任务中运行计算
使用不同的方式进行缓存,缓存如何能够减少负载以提升效能和web应用的扩展性在接下来的博文中会有有详述。
IO密集型应用
如果一个应用的吞吐量被IO或网络能力所限制,无法通过增加CPU速度的方法带来应用性能提升的话,那么这个应用就被称为IO密集型应用(IO Bound Application)。大部分的应用都是IO密集型应用,因为大部分应用都依赖于CURD操作,在这些应用中提升性能或扩展都是一件很困难的事情因为 它严重依赖于其他的下游系统。
下面一些是可能的IO密集型应用场景:
- 应用依赖于数据库与大量的CRUD操作
- 应用使用web服务来完成它自身的操作
下篇博文将会聊聊如何优化设置Web应用的线程池大小。