高可扩展性是个设计指标:表示可通过加机器线性提高系统处理能力,承担更高流量和并发。
架构设计之初,为什么不预先考虑好使用多少台机器,支持现有并发呢?因为峰值流量不可控。
一般基于成本考虑,业务平稳期会预留30%~50%冗余,以应对运营活动或推广可能带来的峰值流量,但当有个突发事件,流量可能瞬间2~3倍甚至更高。如明星出事,其微博下或围观,或互动,微博流量短时间内增长迅速,微博信息流也短暂出现无法刷新。
架构改造已来不及,最快的就是堆机器。不过需保证,扩容三倍机器后,相应的我们的系统也能支撑三倍流量。
1 提升扩展性会很复杂
可在单机系统中通过增加处理核心的方式,增加系统并行处理能力,但这不总生效。 因为当并行任务数较多,系统会因争抢资源而达到性能拐点,系统处理能力不升反降。
集群系统也如此。集群系统中,不同系统分层上可能存在一些“瓶颈点”,这些瓶颈点制约系统横向扩展能力。如:
- 你系统的流量QPS1000,对数据库请求量也是1000qps。若流量增加10倍,虽然系统可扩容正常服务,DB却成瓶颈
- 单机网络带宽是50Mbps,如果扩容到30台机器,前端负载均衡的带宽就超过千兆带宽限制,也成为瓶颈
无状态的服务和组件易于扩展,而MySQL这种存储服务有状态,难扩展。因为向存储集群中增加或者减少机器时,会涉及大量数据的迁移,而一般传统的关系型数据库都不支持。所以提升系统扩展性很复杂。
需要站在整体架构的角度,而不仅仅是业务服务器的角度来考虑系统的扩展性 。所以说,数据库、缓存、依赖的第三方、负载均衡、交换机带宽等等都是系统扩展时需要考虑的因素。我们要知道系统并发到了某一个量级之后,哪一个因素会成为我们的瓶颈点,从而针对性地进行扩展。
2 高可扩展性的设计思路
拆分,提升系统扩展性最重要思路,把庞杂系统拆分成独立、单一职责模块。 相对于大系统,考虑一个个小模块扩展性更简单。复杂问题简单化就是思路。
不同类型模块,拆分原则不同。如设计一个社区,可能5个模块:
- 用户 负责维护社区用户信息,注册,登录
- 关系 用户之间关注、好友、拉黑等关系维护
- 内容 社区发的内容,就像朋友圈或微博内容
- 评论、赞 用户可能会有的两种常规互动操作
- 搜索 用户搜索,内容搜索
而部署方式遵照最简单三层部署架构:
- 负载均衡负责请求的分发
- 应用服务器负责业务逻辑的处理
- 数据库负责数据的存储落地
这时,所有模块的业务代码都混合在一起了,数据也都存储在一个库里。
3 存储层扩展性
无论存储数据量,还是并发访问量,不同业务模块间量级相差很大,如:
- 成熟社区的关系的数据量>>用户数据量
- 但用户数据访问量>>关系数据
所以,假如存储目前瓶颈点是容量,只需针对关系模块的数据做拆分,无需拆分用户模块的数据。所以存储拆分首先考虑的维度是
3.1 业务维度
拆分后,社区系统就有用户库、内容库、评论库、点赞库和关系库。这还能隔离故障,某库“挂了”不影响其它库。
按业务拆分,一定程度提升系统扩展性,但系统运行时间久后,单一业务DB在容量、并发请求量仍会超过单机限制。就需对DB二次拆分。
这次拆分按
3.2 数据特征水平拆分
如给用户库增加两个节点,然后按算法将用户数据拆分到这三个库。
水平拆分后,即可让DB突破单机限制。但不能随意增加节点,因为增加节点就需手动迁移数据,成本很高。
长远考虑,最好一次性增加足够数量节点,避免后续频繁扩容。
当DB按照业务、数据维度拆分后,尽量不要使用事务。因为当一个事务中同时更新不同数据库时,需使用二阶段提交。协调成本随资源扩展不断升高,最终无法承受。
4 业务层扩展性(服务拆分)
如下维度考虑业务层拆分:
4.1 业务
相同业务的服务拆成单独业务池,如社区系统按业务维度拆分成:
- 用户池
- 内容池
- 关系池
- 评论池
- 点赞池
- 搜索池
每个业务依赖独立DB资源,不依赖其它业务DB资源。当某业务的接口成为瓶颈,只需扩展业务池,确认上下游的依赖方,就能大大减少扩容复杂度。
4.2 业务接口的重要程度
把业务分为:核心池,非核心池。
如关系池:
- 关注、取消关注接口相对重要,放在核心池
- 拉黑和取消拉黑操作相对不重要,可放在非核心池
优先保证核心池性能,当整体流量上升时优先扩容核心池,降级部分非核心池的接口,从而保证整体系统稳定性。
4.3 接入客户端类型的不同
如:
- 服务于客户端接口的业务,定义为外网池
- 服务于小程序或者HTML5页面的业务,定义为H5池
- 服务于内部其它部门的业务,定义为内网池
5 DB 扩展性
传统关系型数据库可扩展性很差,NoSQL如何解决扩展性?
- memcached没有提供集群特性,一般第三方会使用一致性哈希算法负载均衡,实现集群扩展
- redis集群选择把数据根据K来哈希分配到固定数量的 slot,然后把 slot 再分配到具体redis实例,来实现集群。需扩展时,只需把 slot 分配一些到新加入的 redis 实例。这种多加一层来实现集群扩展的方式,类似DB分表时的索引表