一些概念
有合理的办法应对系统的增长(数据量、流量、复杂性)
一个良好适配应用的可扩展架构,是围绕着假设(assumption)建立的:哪些操作是常见的?哪些操作是罕见的?这就是所谓负载参数。如果假设最终是错误的,那么为扩展所做的工程投入就白费了,最糟糕的是适得其反。
数据库、缓存、依赖的第三方、复杂均衡、交换机带宽都是系统扩展时需要考虑的点
阻碍可扩展性可能的因素:
- 某个中间件(更有可能是)的连接数—-因为常规的app机器说是足够多的,而中间件(例如Mysql)大多数实例数量并不多
- 服务注册与发现中心出现性能问题(某一个namespace下的instance过多,而其内部算法性能有问题)
- 负载均衡算法出现性能问题(考虑你的负载均衡算法是$O(N^2)$,如果机器数量到达1w台,做一次负载均衡的计算量将不可忽视)
- 应用层访问存储的算法有问题(如果存储扩容了,上层应用更改的成本很大很大)
- 存储本身的问题,单库单表的设计,缺乏扩展性;正确的设计应该是:索引与数据尽早分离。例如用ES做索引,而DB仅作某主要维度的关系查询。
分层
首先要分层,分层是实现扩展的必要条件;分了层才能让系统有更好的可扩展能力;

我们将系统分为如下几层:
接入层
主要负责负载均衡,要求负载均衡策略的时间复杂度要尽量简单,追求$O(lgn)$的时间复杂度,最坏不能超过$O(n)$。对于更坏时间复杂度的均衡策略,性能上会扛不住。
应用层
业务层:按照业务拆分、按照重要性拆分(轻重分离,核心、非核心)、按照请求来源(客户端、web、内网等)—这个跑偏了,这些点主要服务于可用性。
这里侧重点在于无状态,我们一定要确保我们的服务无状态。
存储层
存储层做扩展会更麻烦一些。
实施分片策略时,要考虑的最重要的问题是选择什么分片键(ShardingKey)。分片键(也叫作分区键,Partition Key)由一个或者多个数据列组成,用来决定将数据分到哪个Shard。在图1-22所示的例子中,user_id被用作分片键。分片键可以把数据库查询路由到正确的数据库,使你高效地检索和修改数据。在选择分片键时,最重要的标准之一是选择一个可以让数据均匀分布的键。
一些挑战:
重分片数据:出现如下情况时,需要对数据重新分片。第一种是因为数据快速增长,单个Shard无法存储更多的数据。第二种是因为数据的分布不均匀,有些Shard的空间可能比其他的更快耗尽。当Shard被耗尽时,就需要更新用于分片的哈希函数,然后把数据移到别的地方去。我们会在第5章介绍一致性哈希算法,它是解决这个问题的常用技术。
名人问题:也叫作热点键问题。过多访问一个特定的Shard可能造成服务器过载。想象一下,把Katy Perry、Justin Bieber和Lady Gaga的数据都放在同一个Shard里,对于社交应用而言,这个Shard会因读操作太多而不堪重负。为了解决这个问题,我们可能需要为每个名人都分配一个Shard,而且每个Shard可能还需要进一步分区。
连接和去规范化(de-normalization):一旦数据库通过分片被划分到多个服务器上,就很难跨数据库分片执行连接(join)操作了。解决这个问题的常用方法就是对数据库去规范化,把数据冗余存储到多张表中,以便查询可以在一张表中执行。
一般情况下,要求我们根据业务量提前估计好存储容量。根据计算出来的总的存储量,提前做足够的数据分片。
换言之,对于存储层,我们的一种思路是:早做扩展,以确保将来不做扩展;
另外一种思路是,添加必要的路由层(数据访问路由层)或者路由算法(数据表倍增法),来确保将来的扩展对上层不可见。
manager层
- 抽象service层可能提供的一些原子能力
- 封装对第三方接口的调用