(又)软件架构伸缩性的六大法则

数据层最难伸缩

数据库实则为每个系统的核心所在。

其一,通常包含 “事实来源” 的事务数据库,这里既有业务正常运转所需的核心数据,又有能向数据仓库提供临时数据项的运营数据源。核心业务数据涵盖客户概况、交易以及账户余额等,这些数据必须准确、一致且可用。运营数据则包括用户会话时长、每小时的访问者数量以及页面浏览计数等。此类数据一般有保质期,可基于时间段进行聚合与汇总,所以我们能较为容易地捕获并存储运营数据。随后,使用者会定期检索数据并写入数据存储。

其二,随着系统请求处理层的伸缩,共享事务数据库的负载会不断增加。伴随查询负载的增长,它们很快就会成为瓶颈。此时,查询优化极为有用,同时也需要增添更多内存,以便让数据库引擎能够缓存索引和表数据。然而,最终数据库引擎都会耗尽资源,这就需要进行更彻底的变革。首先要明确的是,在数据层进行数据结构变更绝非易事。倘若修改了关系型数据库的模式,可能就得运行脚本重新加载数据,以匹配新模式。在脚本运行期间,系统无法执行写操作,这很可能会使客户不满。NoSQL、无模式的数据库虽降低了对重新加载数据库的需求,但仍需修改查询代码以适配修改后的数据结构。若业务数据中有部分数据项的格式已被修改,而有些仍保留原始格式,那么可能还需要进行数据对象版本控制。

其三,进一步伸缩或许需要用到分布式数据库,或许包含只读副本的首领和追随者模型便已足够。大多数数据库都能轻松配置这种模式,但需要密切监控。当首领发生宕机时,故障转移需要耗费一定时间,有时还需手动干预。这些问题都高度依赖数据库引擎。若采用无首领模式,则必须做好跨节点的数据分布和分区工作。

缓存、缓存、再缓存

降低数据库负载的一种方式是尽可能减少对数据库的频繁访问,而此时缓存便有了用武之地。优秀的数据库引擎理应尽可能多地利用节点缓存。这是一个简单且实用的解决方案,但成本或许会很高。若并非必要,为何一定要查询数据库呢?对于那些经常被读取且很少发生变化的数据,可以先尝试从分布式缓存中获取。这虽然需要进行远程调用,可若你所需的数据正好存在于高速网络的缓存中,那可比查询数据库实例快得多。在引入缓存层后,我们需要对处理逻辑进行修改,先从缓存中获取数据。倘若你想要的内容不在缓存中,那就查询数据库,接着将结果放入缓存中,并将其返回给调用者。同时,你还得决定何时删除缓存或使其失效,这取决于应用程序对陈旧数据的容忍程度。在对系统进行伸缩时,设计良好的缓存方案绝对是无价之宝。如果你能够通过缓存处理很大比例的读取请求,那么就可以购买额外的数据库容量,因为它们无需处理大多数请求。这意味着在为越来越多的请求增加容量时,能够避免复杂且痛苦的数据层修改。

监控是可伸缩系统的基础

所有团队在面临大工作负载时都不得不解决一个问题,那就是进行大规模测试。真实的负载测试实施起来很困难。假设你想要测试一个已有的部署,看看当数据库大小增加 10 倍后是否依旧能够提供快速响应。首先,你需要生成大量的数据,而且这些数据最好与实际的数据集以及数据关系特征相契合。其次,你还需要生成一个真实的工作负载,确定是用于读取,还是用于读和写。接着,加载并部署数据集,进行负载测试,这可能需要用到负载测试工具。

另一种选择是进行监控。简单的系统监控包括对基础设施的监控。如果出现资源耗尽的情况,比如内存或磁盘空间不足,或者远程调用失败,你都应该收到报警,以便在糟糕的事情发生之前采取补救措施。监控是必要的,但仅仅监控还不够。随着系统的伸缩,你需要了解应用程序行为之间的关系。你还需要知道什么时候回路断路器会由于下游延迟增加而断开微服务连接,什么时候负载均衡器开始生成新实例,或者消息在队列中停留的时间是否超过了指定的阈值。

请使用浏览器的分享功能分享到微信等