AKF 扩展立方体
AKF 扩展立方体
AKF 扩展立方体(AKF Scale Cube)是一个描述从单体应用到可扩展架构的模型,见 (https://akfpartners.com/growth-blog/scale-cube)[https://akfpartners.com/growth-blog/scale-cube]。这个模型在 Scalability Rules (架构真经)一书中进行了描述。
AKF 扩展立方体把单体应用扩展到可扩展架构的方式的过程划分为3个正交维度:
x 轴:水平复制
y 轴:业务拆分
z 轴:数据分片
这 3 个维度描述了从单体到可扩展架构的方法,在实践中有比较大的作用,x、y、z组合而成的向量大小正是系统的扩展性。
x 轴扩展
x 轴扩展描述了比较简单的扩展方式,又称水平扩展,通过复制节点,实现多个节点同时提供服务,从而大大提高系统的总体容量、解决单点问题等。
水平扩展是比较理想的扩展方式,一般来说开发难度不大,但增加部署复杂度。这种扩展最为典型的例子是数据库的主从复制和读写分离。
典型的 web 应用读写比例非常悬殊,通常是 10:1
、100:1
甚至是 1000:1
,因此需要更多的资源来支持读操作。同时,从数据的时效性上看,大部分数据并不需要非常高的实时性,3、5s 的少量的延迟往往是可被接受的。从数据的访问频率上看,基本上数据会遵守局部性法则(8020 法则),即大部分访问都是访问热点数据。从这些业务上的特点出发,往往有 2 类的手段来进行数据层面上的优化:加缓存、复制节点读写分离。
加缓存是非常有效的优化手段。数据库本身也带有缓存,像 mysql 的 innodb_buffer_pool
,用提高响应速度,除此之外,还可以:
本地内存、文件缓存,如 ehcache
中央缓存,如 redis / memcached
既然有缓存,就有缓存的有效性、有效时间,就会有缓存相关的问题,包括:
缓存穿透,用不存在的 id 访问系统,达成攻击
缓存击穿,特别的热的某个数据失效,所有的流量瞬间到了后端
缓存雪崩,某个时间缓存批量失效,所有流量到后端
读写分离通过加强基础设施的容量进行扩展。大部分的数据库,mysql、oracle、sqlserver 会支持某种主从备份(甚至是双主),这使得在基础设施层面能实施非常有效的扩展。以 mysql 为例,可把事务型的sql 放到主库执行,简单查询型的 sql 分布到多个从库读取数据,这一下子就把原来的负载分配到了几个数据库实例来承担,容量放大 n 倍。这种方式需要实现上做一些改动,在数据库访问层上需要支持某种形式的 sql 路由,而这些在成熟的 orm 、sqlmap 框架都已经提供,可看对应的 mybaits、hibernate 等文档。
当然 x 轴扩展不单只是加缓存或读写分离,像 dns 负载均衡、多接入服务器等都是 x 轴扩展方法。
y 轴扩展
y 轴扩展从业务层面来考虑扩展方式,因此又称为业务扩展或资源扩展,这点基本上就等同于微服务化。系统从业务层面拆分为多个子系统,各子系统由单独的团队负责整个生命周期的维护,单独部署运行,子系统间具备故障隔离的能力。
拆分的方式有水平的拆分和垂直拆分。垂直拆分是把接入、前端、安全、监控等不同技术层面的组件进行拆分,让各组件更加专注于自己的工作,体现在结构图上就是在垂直方向上进行了划分;水平拆分是按照不同的业务线,各业务线拆分开来,结构图上是按照水平方向上进行划分。y 轴的扩展更多是水平拆分这种方式。
每个子系统拥有自己的运行资源,可以有针对性地采用更加激进的优化手段。像与文档相关的子系统,可以减少使用关系式数据库,转而采用对分布式支持更好的文档型数据库,进行热点预测,对热点数据提前进行预处理等等。
拆分子系统后,通常数据库要进行拆分(分库)。分库带来了事务处理能力的直接提升,对于高事务型应用,可在数据库层面用一些比较高的配置,非事务型应用使用低点的配置,使得整体的运行成本更加合理,让好钢用到刀刃处。
需要注意的是,分库将无可避免地导致需要使用分布式事务。典型的分布式事务模式,目前常见的有数据库自带的 XA,java 中的 JTA,TCC,支持长时间业务的 Saga 等。这里给一个警告,尽可能避免分布式事务,目前并没有 100% 可靠而又高效的分布式事务解决方案。因此在进行 y 轴扩展拆分时,一个很有效的方法是事务应该尽量集中在子系统中,只有必须无可避免的时候才采用分布式事务,同时对分布式事务必须使用先写 undo_log
日志,以便在必要时进行自动甚至人工恢复。
拆分后同时带来了跨业务集成的问题。在《微服务设计》书中提到有两种风格的集成方式:请求响应、事件。请求响应有 RPC、REST方式,事件式可用各种消息队列(kafka、rabbitmq、activemq)等进行事件消息传递(openstack 里面的集成方式比较有意思,采用事件队列来进行 RPC)。
y 轴扩展需要比 x 轴扩展花费更多的精力,从开发、运维甚至组织架构都需要做出相应的调整。大家熟知的康威定律,系统的架构反映了组织的沟通结构,以技术至上的角度来实现 y 轴扩展,到头来只会让业务层面无法适应新系统而导致效率低下。
z 轴扩展
z 轴扩展是基于数据集本身的特性来进行扩展的方式,也即数据分片,在实践中,就是分表了。
对于关系型数据库而言,拆分数据意味着需要进行反模式的设计,依赖于数据库自身机制的完整性约束(主键、外键、域、唯一性)的设计也需要拆开。
数据的拆分需要对业务领域有较高的认识才好处理,拆分的代价相当高,需要引入一系列支持拆分的底层框架,像 sharding-jdbc、mycat 等,在数据层面需要配置相应的分片逻辑。正确的拆分对提高系统的容量有很大的帮助,失败的拆分可能会造成热点集中,得不偿失。
数据拆分可以和业务扩展共同使用,让业务跑在部分的数据上,实现故障隔离。在升级时,先对小范围进行升级,在出错时小范围的数据更加容易修复,待验证之后再进行整个系统层面的升级,这种控制范围的灰度发布在实践中非常实用,在各种云平台、 k8s 上都可以实现。
最后...
所有的扩展都会带了复杂度、成本提升的代价,要视业务的真正需要来进行扩展设计,避免过度的扩展。
通常来说,在设计阶段要设计 10 到 100 倍左右的逻辑容量,因为在后期修改设计会带来很高的难度,所以要提前设计好冗余容量。在实现阶段,通常要按照 5 到 10 倍左右的容量进行实现,实现阶段需要付出一定的成本,没有必要为非常长远的将来实现,可以随业务的进展而逐步实现设计时最大容量。部署阶段,以 2 到 5 倍的容量进行部署,可以应付一段时间内的容量增长,但如果是预期的增长非常大,就要采用更加灵活的云部署手段,并为突发峰值做好预案准备。