我们了解到,在软件系统的性能建模分析设计里,并行架构设计、IO 模型设计、内存模型设计是最为核心的三个维度,它们决定了最终产品的性能基础。而在互联网应用服务产品当中,内存模型设计与 IO 模型设计的大部分职责,实际上在很大程度上都沉淀到了数据库服务与消息中间件当中来予以实现。所以,在这节课我会将重点放在数据库选型上,以一个真实的性能需求案例作为引导,带领您了解在设计或者性能优化阶段,怎样寻找备选数据库并展开初步的分析、筛选,然后基于性能评估与软件设计的权衡,来实施数据库选型与方案设计的过程方法。
不过我们也要明白,数据库的种类极为繁多,想要了解每一款数据库的功能与性能是不太现实的,所以这节课我的主要目的是帮助您构建基于性能的数据库选型思路。如此一来,当您面对不熟悉的性能需求或者问题时,也能够有针对性地采取措施,并能够借助这套数据库选型的过程方法,找到适宜的数据库与设计方案。现在,我先为您介绍一下这个性能需求案例。这是一个 SaaS 服务产品中的分析服务,其主要功能是针对客户提交的数据进行查询搜索并生成报表数据。通过对产品线上的数据规模和特征进行分析,以及对系统的核心业务流程展开分析,我们能够挖掘出其最为核心的性能需求:

实际上,数据查询与搜索、报表生成都是互联网产品的核心业务场景,也是性能问题频发的重灾区。而在软件设计阶段从性能的角度出发,选择合适的数据库与架构设计,就可以在很大程度上避免出现这样的问题。OK,在识别出典型的性能需求之后,我们接下来的工作就是寻找到所有满足这些条件的备选数据库列表了。
基于性能寻找备选数据库列表
在寻找备选数据库的进程中,我们首先应当注重的是广度。然而由于数据库的种类繁多,并且人都存在认知的局限性,很容易遗漏一些极具价值的数据库产品,进而致使无法找到一个最优的方案。所以在此,我为您推荐一个网站,这是一个专门收集并展示数据库管理系统信息的数据库引擎排名,其中罗列了超过 300 多种数据库产品,大部分的开源和商业数据库均在其中。但是,直接在这个数据库列表中进行大规模筛选,工作量依旧比较庞大,所以我们能够通过认识和学习数据库的大致分类,来提高筛选的效率。
在行业内,对数据库的通用分类主要涵盖 5 个大类:
关系数据库:以 MySQL、Oracle、PostgreSQL 为代表,这些属于结构化的关系型数据库,主要基于 SQL 进行操作;
文档数据库:以 MongoDB、Cassandra、Elasticsearch 为代表,它支持灵活的半结构化数据存储,例如 JSON 等;时序数据库:主要用于监控、日志类的数据存储,它支持依照时间维度进行存储与分析;
Key-Value 数据库:以 Aerospike、Redis 等为代表,它支持典型数据结构的快速存储访问;图数据库:支持图的存储,典型的应用场景包含知识图谱、关键路径搜索等。
理论评估筛选出待分析数据库
筛选待分析数据库的过程实际上颇为复杂,有些数据库或许您已经深入使用过,对其功能与性能有着深刻的理解,那是比较幸运的。但倘若您遇到的并非十分熟悉的数据库,那您可以基于以下方式来进行初步的理论评估,识别或过滤掉一部分数据库产品:
数据库官网所提供的性能指标;
数据库官网的存储模型与架构视图;
第三方给出的性能测试分析报告;
网上部分用户的性能测试分析结论。
相对来说,官网提供的性能指标、存储模型及架构视图是较为权威的,您在选择数据库之前需要深入去了解和学习。就拿我们前面提及的性能需求案例来讲,基于官方文档的分析,您会发现 Hlive 和 SparkSQL 数据库依赖 Map-Reduce,在动态创建分析请求的任务并上传时,准备时间会比较长,而当在业务中实时动态创建性能分析的请求时,性能很可能不太理想;而 MemSQL 的开源版本存在容量规模的限制,也很可能与产品业务的数据规模需求不相符,因此同样需要排除。
除此之外,进一步理解和学习一些典型的数据库相关实现架构,例如 MPP(Massively Parallel Processing)架构、Hadoop 生态架构等,也能够助力您更好地识别数据库。当然,仅仅从数据库的角度进行理论分析评估是不足够的,您可能还需要分析、评估潜在的外部性能影响因素,比如,网络传输带宽的影响、跨地区或者跨云之间的传输时延开销等等。
这里我给您举个例子,Redshift 和阿里云 ADB 均为云服务所提供的数据库,如果您的产品业务部署在阿里云上,那么使用 AWS 云上的 Redshift 就会导致额外的传输延迟抖动,甚至可能影响产品的稳定性。如此一来,当您的产品没有极致、严格的性能需求时,基于上述的搜索与理论评估,您就能够锁定具体的数据库产品,接下来的重点工作只需进行原型验证,测试性能是否满足需求就行。
不过在不少的业务场景中,很多产品实际上有着极致的性能要求,而且这通常也是产品在行业内的核心竞争力体现。那么此时,您就需要对备选数据库进行更为深入的性能评估分析,甚至还需要调整软件设计与优化来应对。
好,针对前面我所介绍的典型性能需求的案例,假设基于理论分析之后,我们筛选出了四个待分析的数据库,分别是 ClickHouse、 阿里云 ADB、 Redshift、 Greenplum(详细的筛选过程与业务的相关性较大,就不做深入介绍了,但使用的方法就是基于上述的手段),那接下来我们就能够针对这四个数据库进行更深度的性能评估分析。
挖掘全量的性能需求点
对数据库的操作除了查询、分析操作外,还包括插入、删除、更新等。所以在对数据库进行性能评估分析时,我们还需要顾及各个性能维度,以防因为遗漏了个别性能需求致使最终的评估结果无效。但令人遗憾的是,在众多的产品业务里,由于性能需求考虑不周全,导致性能评估结果不可靠的问题频繁出现。例如,在我前面介绍的性能优化案例中,原本的解决方案是引入 Elasticsearch 来优化数据库的查询性能,然而这种方案会致使很多业务的复杂度提升,并且它只能解决一小部分现网的性能问题。因此,为了防止因性能需求考虑不完整,造成性能评估结果不可靠的情况发生,在展开深度性能评估之前,挖掘出全部的性能需求点就显得格外重要了。下面我就依据前面的性能案例,为您介绍挖掘全量性能需求点的过程。
首先针对案例中的性能需求,我们可以做进一步细化,会扩展出很多的性能场景,如下面的思维脑图所示:

从图上我们可以发现,各个树干上的叶子节点之间其实是可以组合的,比如说:
分库、1000 万条、20 列字段、插入、单条数据、时延;
分库、1000 万条、20 列字段、插入、单条数据、TPS;
分表、1000 万条、20 列字段、插入、单条数据、生效时延;
……
如此组合之后能够生成众多的性能场景,因而您需要根据实际业务的性能需求进行一些等价类划分,剖析出一些较为关键的场景,然后再开展接下来的性能评估分析。那么在这个性能案例场景中,我们起初仅仅挖掘出了最为核心且具有挑战性的性能需求,然而产品对于数据库的性能需求涵盖诸多维度,所以我们能够在进一步完善对插入、更新、删除的性能需求之后,再额外增添一些具体的性能需求,如下表所示:

深度性能评估分析并发掘潜在性能冲突
到这里,我们已经完成了备选数据库的理论筛选流程,接下来就需要展开深度性能评估分析,也就是性能测试分析的流程。
具体的性能测试理论与方法,我会在“性能看护”这个模块为您详细讲解,这里我们只需要关注性能测试中较为重要的几点:
测试数据要尽可能接近真实数据;
尽量自动化执行;
尽量代码脚本化执行。
在明确了性能测试的重点之后,还需要尽可能获取真实的被测数据集。假设我们通过在现网中采集或构造仿真数据,创建了一个待评测数据集:

基于这个待评测数据集和一致化的业务分析请求,我们对上述四个数据库进行性能测试的对比分析,获取真实的性能评测数据如下:

基于这个待评测数据集和一致化的业务分析请求,我们对上述四个数据库进行性能测试的对比分析,获取真实的性能评测数据如下:

从结果能够看出,在真实领域的业务场景当中,ClickHouse 的时延仅为 50 至 100ms,具备极为卓越的性能。如此,在基于真实数据的性能评测之后,您会发现获取的性能对比结果和在网上获取的信息并非完全一致,这是由于您的性能评估数据已经携带了领域数据特征,其分析请求也是与领域特性相关的。
此外,在这个阶段所进行的性能评估分析,还有一个重要的目标是挖掘潜在的性能冲突点。针对案例扩展出来的性能需求点,经过评估测试后我们会发现部分数据库产品在一些性能场景下,并不能满足需求,比如说:

如果你在数据库选型阶段忽略了这些性能冲突点,带来的后果往往是致命的,因为这可能会导致产品上线交付后,浪费很大的修改解决成本,甚至可能导致产品最终失败。所以,如果没有百分百满足你所有性能需求的数据库产品,可以考虑通过软件设计方案去权衡解决发掘出的性能冲突点,不过这个方法只是在数据库选型不理想的场景下的一种弥补方法,希望你不会用到。
基于性能的软件设计权衡
幸运的话,您能够找到在各个方面都能满足性能需求的数据库产品,然而实际中所面对的业务通常都是复杂多变的,而且当下在数百种数据库不断迭代更新的场景下,确实存在找不到那个性能完美适配的数据库的情况。那么面对这个问题,有没有什么良好的解决办法呢?依我的经验而言,当出现性能冲突点时,我们能够在软件设计层面,考虑一些折中的办法来规避和解决问题,这里我为您举几个例子。
假设受限于数据库的单条数据插入 TPS 的上限为 10 条,而产品业务每秒插入的数据是 500 条,那我们是不是就无法选择这款数据库产品了?并非如此,针对这个场景,我们可以采用批处理模式(即将众多个单次处理操作整合起来,一次性执行处理的性能优化模式,我在第 11 讲中会为您详细介绍),将每秒内的 500 条数据整合到一起插入(注意,在运用这个性能模式时,我们还需要构建单独的业务代码逻辑来实现这个功能)。
不过,这种实现方式也可能会带来一些潜在的问题,例如:插入数据的潜在生效时延被人为延长了;插入数据在整合批量插入的间隔时间内,又发生了变更,需要开发复杂的逻辑来处理这种场景。所以您需要明白的是,软件设计上的权衡是有利有弊的,在通过提升某些操作上的性能来解决性能冲突的同时,必然会在某些方面引起性能的下降。这个时候,您还需要评估设计权衡导致的这些性能下降点是否能够被产品业务所接受,才能确定该设计权衡的可行性。
我再为您举个例子,有些 OLAP 分析数据库不提供删除操作,但当产品业务需求存在删除操作时,是否存在其他变通实现的手段呢?答案是有的,我们可以将删除操作转变为对数据条目的状态位更新操作,然后通过状态位标记软删除来实现业务中的删除能力。再比如说,MongoDB 数据库不提供跨文档操作的事务一致性机制,但在产品业务中存在这样的事务性要求时,该如何解决呢?如果业务中存在的事务逻辑的切换频率极低,其实有些逻辑是能够通过加锁来实现的,或者是可以通过实现事务机制来支撑。