You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

104 lines
12 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 21 | AKF立方体怎样通过可扩展性来提高性能
你好,我是陶辉。
上一讲我们谈到,调低一致性可以提升有状态服务的性能。这一讲我们扩大范围,结合无状态服务,看看怎样提高分布式系统的整体性能。
当你接收到运维系统的短信告警,得知系统性能即将达到瓶颈,或者会议上收到老板兴奋的通知,接下来市场开缰拓土,业务访问量将要上一个大台阶时,一定会马上拿起计算器,算算要加多少台机器,系统才能扛得住新增的流量。
然而,有些服务虽然可以通过加机器提升性能,但可能你加了一倍的服务器,却发现系统的吞吐量没有翻一倍。甚至有些服务无论你如何扩容,性能都没有半点提升。这缘于我们扩展分布式系统的方向发生了错误。
当我们需要分布式系统提供更强的性能时,该怎样扩展系统呢?什么时候该加机器?什么时候该重构代码?扩容时,究竟该选择哈希算法还是最小连接数算法,才能有效提升性能?
在面对Scalability可伸缩性问题时我们必须有一个系统的方法论才能应对日益复杂的分布式系统。这一讲我将介绍AKF立方体理论它定义了扩展系统的3个维度我们可以综合使用它们来优化性能。
## 如何基于AKF X轴扩展系统
AKF立方体也叫做[scale cube](https://en.wikipedia.org/wiki/Scale_cube)它在《The Art of Scalability》一书中被首次提出旨在提供一个系统化的扩展思路。AKF把系统扩展分为以下三个维度
* X轴直接水平复制应用进程来扩展系统。
* Y轴将功能拆分出来扩展系统。
* Z轴基于用户信息扩展系统。
如下图所示:
![](https://static001.geekbang.org/resource/image/61/aa/61633c7e7679fd10b915494b72abb3aa.jpg?wh=1218*974)
我们日常见到的各种系统扩展方案都可以归结到AKF立方体的这三个维度上。而且我们可以同时组合这3个方向上的扩展动作使得系统可以近乎无限地提升性能。为了避免对AKF的介绍过于抽象下面我用一个实际的例子带你看看这3个方向的扩展到底该如何应用。
假定我们开发一个博客平台用户可以申请自己的博客帐号并在其上发布文章。最初的系统考虑了MVC架构将数据状态及关系模型交给数据库实现应用进程通过SQL语言操作数据模型经由HTTP协议对浏览器客户端提供服务如下图所示
![](https://static001.geekbang.org/resource/image/cd/e0/cda814dcfca8820c81024808fe96b1e0.jpg?wh=876*874)
在这个架构中处理业务的应用进程属于无状态服务用户数据全部放在了关系数据库中。因此当我们在应用进程前加1个负载均衡服务后就可以通过部署更多的应用进程提供更大的吞吐量。而且初期增加应用进程RPS可以获得线性增长很实用。
![](https://static001.geekbang.org/resource/image/d7/62/d7f75f3f5a5e6f8a07b1c47501606962.png?wh=879*856)
这就叫做沿AKF X轴扩展系统。这种扩展方式最大的优点就是开发成本近乎为零而且实施起来速度快在搭建好负载均衡后只需要在新的物理机、虚拟机或者微服务上复制程序就可以让新进程分担请求流量而且不会影响事务Transaction的处理。
当然AKF X轴扩展最大的问题是只能扩展无状态服务当有状态的数据库出现性能瓶颈时X轴是无能为力的。例如当用户数据量持续增长关系数据库中的表就会达到百万、千万行数据SQL语句会越来越慢这时可以沿着AKF Z轴去分库分表提升性能。又比如当请求用户频率越来越高那么可以把单实例数据库扩展为主备多实例沿Y轴把读写功能分离提升性能。下面我们先来看AKF Y轴如何扩展系统。
## 如何基于AKF Y轴扩展系统
当数据库的CPU、网络带宽、内存、磁盘IO等某个指标率先达到上限后系统的吞吐量就达到了瓶颈此时沿着AKF X轴扩展系统是没有办法提升性能的。
在现代经济中更细分、更专业的产业化、供应链分工可以给社会带来更高的效率而AKF Y轴与之相似当遇到上述性能瓶颈后拆分系统功能使得各组件的职责、分工更细也可以提升系统的效率。比如当我们将应用进程对数据库的读写操作拆分后就可以扩展单机数据库为主备分布式系统使得主库支持读写两种SQL而备库只支持读SQL。这样主库可以轻松地支持事务操作且它将数据同步到备库中也并不复杂如下图所示
![](https://static001.geekbang.org/resource/image/86/74/865885bb7213e62b8e1b715d85c9a974.png?wh=834*791)
当然上图中如果读性能达到了瓶颈我们可以继续沿着AKF X轴用复制的方式扩展多个备库提升读SQL的性能可见AKF多个轴完全可以搭配着协同使用。
拆分功能是需要重构代码的它的实施成本比沿X轴简单复制扩展要高得多。在上图中通常关系数据库的客户端SDK已经支持读写分离所以实施成本由中间件承担了这对我们理解Y轴的实施代价意义不大所以我们再来看从业务上拆分功能的例子。
当这个博客平台访问量越来越大时,一台主库是无法扛住所有写流量的。因此,基于业务特性拆分功能,就是必须要做的工作。比如,把用户的个人信息、身份验证等功能拆分出一个子系统,再把文章、留言发布等功能拆分到另一个子系统,由无状态的业务层代码分开调用,并通过事务组合在一起,如下图所示:
![](https://static001.geekbang.org/resource/image/3b/af/3bba7bc19965bb9b01c058e67a6471af.png?wh=644*833)
这样每个后端的子应用更加聚焦于细分的功能它的数据库规模会变小也更容易优化性能。比如针对用户登录功能你可以再次基于Y轴将身份验证功能拆分用Redis等服务搭建一个基于LRU算法淘汰的缓存系统快速验证用户身份。
然而沿Y轴做功能拆分实施成本非常高需要重构代码并做大量测试工作上线部署也很复杂。比如上例中要对数据模型做拆分如同一个库中的表拆分到多个库中或者表中的字段拆到多张表中设计组件之间的API交互协议重构无状态应用进程中的代码为了完成升级还要做数据迁移等等。
解决数据增长引发的性能下降问题除了成本较高的AKF Y轴扩展方式外沿Z轴扩展系统也很有效它的实施成本更低一些下面我们具体看一下。
## 如何基于AKF Z轴扩展系统
不同于站在服务角度扩展系统的X轴和Y轴AKF Z轴则从用户维度拆分系统它不仅可以提升数据持续增长降低的性能还能基于用户的地理位置获得额外收益。
仍然以上面虚拟的博客平台为例当注册用户数量上亿后无论你如何基于Y轴的功能去拆分表即“垂直”地拆分表中的字段都无法使得关系数据库单个表的行数在千万级以下这样表字段的B树索引非常庞大难以完全放在内存中最后大量的磁盘IO操作会拖慢SQL语句的执行。
这个时候关系数据库最常用的分库分表操作就登场了它正是AKF沿Z轴拆分系统的实践。比如已经含有上亿行数据的User用户信息表可以分成10个库每个库再分成10张表利用固定的哈希函数就可以把每个用户的数据映射到某个库的某张表中。这样单张表的数据量就可以降低到1百万行左右如果每个库部署在不同的服务器上具体的部署方式视访问吞吐量以及服务器的配置而定它们处理的数据量减少了很多却可以独占服务器的硬件资源性能自然就有了提升。如下图所示
![](https://static001.geekbang.org/resource/image/dc/83/dc9e29827c26f89ff3459b5c99313583.png?wh=852*815)
分库分表是关系数据库中解决数据增长压力的最有效办法但分库分表同时也导致跨表的查询语句复杂许多而跨库的事务几乎难以实现因此这种扩展的代价非常高。当然如果你使用的是类似MySQL这些成熟的关系数据库整个生态中会有厂商提供相应的中间件层使用它们可以降低Z轴扩展的代价。
再比如最开始我们采用X轴复制扩展的服务它们的负载均衡策略很简单只需要选择负载最小的上游服务器即可比如RoundRobin或者最小连接算法都可以达到目的。但若上游服务器通过Y轴扩展开启了缓存功能那么考虑到缓存的命中率就必须改用Z轴扩展的方式基于用户信息做哈希规则下的新路由尽量将同一个用户的请求命中相同的上游服务器才能充分提高缓存命中率。
Z轴扩展还有一个好处就是可以充分利用IDC与用户间的网速差选择更快的IDC为用户提供高性能服务。网络是基于光速传播的当IDC跨城市、国家甚至大洲时用户访问不同IDC的网速就会有很大差异。当然同一地域内不同的网络运营商之间也会有很大的网速差。
例如你在全球都有IDC或者公有云服务器时就可以通过域名为当地用户就近提供服务这样性能会高很多。事实上CDN技术就基于IP地址的位置信息就近为用户提供静态资源的高速访问。
下图中我使用了2种Z轴扩展系统的方式。首先是基于客户端的地理位置选择不同的IDC就近提供服务。其次是将不同的用户分组比如免费用户组与付费用户组这样在业务上分离用户群体后还可以有针对性地提供不同水准的服务。
![](https://static001.geekbang.org/resource/image/35/3b/353d8515d40db25eebee23889a3ecd3b.png?wh=1061*766)
沿AKF Z轴扩展系统可以解决数据增长带来的性能瓶颈也可以基于数据的空间位置提升系统性能然而它的实施成本比较高尤其是在系统宕机、扩容时一旦路由规则发生变化会带来很大的数据迁移成本\[第24讲\] 我将要介绍的一致性哈希算法,其实就是用来解决这一问题的。
## 小结
这一讲我们介绍了如何基于AKF立方体的X、Y、Z三个轴扩展系统提升性能。
X轴扩展系统时实施成本最低只需要将程序复制到不同的服务器上运行再用下游的负载均衡分配流量即可。X轴只能应用在无状态进程上故无法解决数据增长引入的性能瓶颈。
Y轴扩展系统时实施成本最高通常涉及到部分代码的重构但它通过拆分功能使系统中的组件分工更细因此可以解决数据增长带来的性能压力也可以提升系统的总体效率。比如关系数据库的读写分离、表字段的垂直拆分或者引入缓存都属于沿Y轴扩展系统。
Z轴扩展系统时实施成本也比较高但它基于用户信息拆分数据后可以在解决数据增长问题的同时基于地理位置就近提供服务进而大幅度降低请求的时延比如常见的CDN就是这么提升用户体验的。但Z轴扩展系统后一旦发生路由规则的变动导致数据迁移时运维成本就会比较高。
当然X、Y、Z轴的扩展并不是孤立的我们可以同时应用这3个维度扩展系统。分布式系统非常复杂AKF给我们提供了一种自上而下的方法论让我们能够针对不同场景下的性能瓶颈以最低的成本提升性能。
## 思考题
最后给你留一道思考题我们在谈到Z轴扩展时比如关系数据库的分库分表提到了基于哈希函数来设置路由规则请结合[\[第3讲\]](https://time.geekbang.org/column/article/232351) 的内容谈谈你认为应该如何设计哈希函数才能使它满足符合Z轴扩展的预期期待你的总结。
感谢阅读,如果你觉得这节课对你有一些启发,也欢迎把它分享给你的朋友。