当今大多数应用都是“数据密集型”,而非“计算密集型”。对这些应用而言,瓶颈通常不在于CPU性能,而在于:
- 数据量
- 数据的复杂度(如何建模?关联关系?)
- 数据的变化率
一个数据密集型应用通常基于通用的数据组件构建而成,比如:
- 数据库(database)
- 缓存(caches)
- 查询索引(search indexex)
- 流处理(stream processing)
- 批处理(batch processing)
这些数据组件提供了良好的抽象,使我们在开发应用时不用过多考虑内部细节。但是,每个数据组件都有多种不同的产品,它们各有各的特性,用于满足不同的需求。比如不同的数据库,不同的缓存。我们需要搞清楚每个产品的特性,并想办法将不同产品组合起来。
本书将结合理论与实践,教会如何使用这些数据组件来构建数据密集型应用。我们将探讨这些数据组件的相同点和不同点,以及它们如何实现各自的特性。
在本章,我们将开始探讨我们想要达成的基础目标:
- 目前很多新的工具同时具有数据存储和数据处理功能,不再适用于传统的分类方式。
- 越来越多的应用的需求难以依靠单一的数据组件满足,需要将需求分解为不同的子任务,然后由不同组件来满足,这些组件通过应用层代码粘合在一起。一个典型的示例如下:

我们将多种工具组合在一起构成一个服务,并对外提供服务接口或API,隐藏了内部组件细节。这个过程中,我们事实上基于通用数据组件,构建了一个专用的数据系统。此时我们不仅是应用开发者,也成了数据系统设计者。
而当我们设计一个数据系统或服务时,一系列困难的问题就会出现。三个通用的基本问题是:
- 可靠性:系统应该在面临错误(硬件故障、软件故障、人为错误)时正常工作(功能正常、性能达标)。
- 可扩展性:当系统增长时(数据量、数据复杂度、访问量),应该有办法扩展系统来支持增长。
- 可维护性:方便新人进入,方便运维。
可靠性
正常工作的含义:
- 功能正确
- 性能达标
- 权限控制
可靠性即是指:出现错误时依旧正常工作。
本书将讨论几种“基于不可靠的组件构建可靠系统”的技术。
硬件错误
应对硬件故障的第一个方案是增加硬件冗余。
随着数据量和计算量的增加,目前应用可能会采用大量机器部署,以及容器化部署。此时硬件故障指数级增加,随时都有可能某台机器故障导致部分服务实例不可用。与其依靠硬件冗余来确保每个服务实例都不挂,不如依靠软件容错技术,来确保即便部分实例挂了,服务整体也不受损。
软件错误
软件错误通常指系统内部由于bug、不当编码、依赖服务挂了等导致的错误。这些错误可能会产生“级联错误”,拖垮整个系统。
软件错误通常没有通用的解决方案,只能依照特定情况进行解决。常见的手段有:
- 认真考虑与第三方服务的交互;
- 完整测试;
- 进程隔离;
- 允许进程奔溃和重启;
-
人为错误
人是会犯错的。一些常见的避免人为错误的手段:
系统设计上,尽量降低用户出错的概率。
- 将容易出错的地方和不容易出错的地方分开。
- 各个层次完整测试。
- 提供快速回滚人为错误的手段。
- 监控告警。
- 良好的管理和培训。
可靠性有多重要
可扩展性
定义负载(指标)
定义性能(指标)
应对负载增长的方法
服务实例能够弹性伸缩的前提,是服务得是无状态的。而像数据库等服务天生就是有状态的。通常的做法是,数据库放在一个节点上,做纵向扩展。应用服务本身保持无状态,做横向扩展。
不过现在分布式数据库主键成熟,使得数据库也可以做横向扩展。
