05 | 系统设计目标(三):如何让系统易于扩展?
下载APP
关闭
渠道合作
推荐作者
05 | 系统设计目标(三):如何让系统易于扩展?
2019-09-27 唐扬 来自北京
《高并发系统设计40问》
课程介绍
讲述:唐扬
时长08:52大小8.12M
从架构设计上来说,高可扩展性是一个设计的指标,它表示可以通过增加机器的方式来线性提高系统的处理能力,从而承担更高的流量和并发。
一般来说,基于成本考虑,在业务平稳期,我们会预留 30%~50% 的冗余以应对运营活动或者推广可能带来的峰值流量,但是当有一个突发事件发生时,流量可能瞬间提升到 2~3 倍甚至更高,我们还是以微博为例。
鹿晗和关晓彤互圈公布恋情,大家会在两个人的微博下面,或围观,或互动,微博的流量短时间内增长迅速,微博信息流也短暂出现无法刷出新的消息的情况。
那我们要如何应对突发的流量呢?架构的改造已经来不及了,最快的方式就是堆机器。不过我们需要保证,扩容了三倍的机器之后,相应的我们的系统也能支撑三倍的流量。有的人可能会产生疑问:“这不是显而易见的吗?很简单啊。”真的是这样吗?我们来看看做这件事儿难在哪儿。
为什么提升扩展性会很复杂
在上一讲中,我提到可以在单机系统中通过增加处理核心的方式,来增加系统的并行处理能力,但这个方式并不总奏效。因为当并行的任务数较多时,系统会因为争抢资源而达到性能上的拐点,系统处理能力不升反降。
而对于由多台机器组成的集群系统来说也是如此。集群系统中,不同的系统分层上可能存在一些“瓶颈点”,这些瓶颈点制约着系统的横向扩展能力。这句话比较抽象,我举个例子你就明白了。
比方说,你系统的流量是每秒 1000 次请求,对数据库的请求量也是每秒 1000 次。如果流量增加 10 倍,虽然系统可以通过扩容正常服务,数据库却成了瓶颈。再比方说,单机网络带宽是 50Mbps,那么如果扩容到 30 台机器,前端负载均衡的带宽就超过了千兆带宽的限制,也会成为瓶颈点。那么,我们的系统中存在哪些服务会成为制约系统扩展的重要因素呢?
其实,无状态的服务和组件更易于扩展,而像 MySQL 这种存储服务是有状态的,就比较难以扩展。因为向存储集群中增加或者减少机器时,会涉及大量数据的迁移,而一般传统的关系型数据库都不支持。这就是为什么提升系统扩展性会很复杂的主要原因。
除此之外,从例子中你可以看到,我们需要站在整体架构的角度,而不仅仅是业务服务器的角度来考虑系统的扩展性 。所以说,数据库、缓存、依赖的第三方、负载均衡、交换机带宽等等都是系统扩展时需要考虑的因素。我们要知道系统并发到了某一个量级之后,哪一个因素会成为我们的瓶颈点,从而针对性地进行扩展。
针对这些复杂的扩展性问题,我提炼了一些系统设计思路,供你了解。
高可扩展性的设计思路
拆分是提升系统扩展性最重要的一个思路,它会把庞杂的系统拆分成独立的,有单一职责的模块。相对于大系统来说,考虑一个一个小模块的扩展性当然会简单一些。将复杂的问题简单化,这就是我们的思路。
但对于不同类型的模块,我们在拆分上遵循的原则是不一样的。我给你举一个简单的例子,假如你要设计一个社区,那么社区会有几个模块呢?可能有 5 个模块。
用户:负责维护社区用户信息,注册,登陆等;
关系:用户之间关注、好友、拉黑等关系的维护;
内容:社区发的内容,就像朋友圈或者微博的内容;
评论、赞:用户可能会有的两种常规互动操作;
搜索:用户的搜索,内容的搜索。
而部署方式按照最简单的三层部署架构,负载均衡负责请求的分发,应用服务器负责业务逻辑的处理,数据库负责数据的存储落地。这时,所有模块的业务代码都混合在一起了,数据也都存储在一个库里。
1. 存储层的扩展性
无论是存储的数据量,还是并发访问量,不同的业务模块之间的量级相差很大,比如说成熟社区中,关系的数据量是远远大于用户数据量的,但是用户数据的访问量却远比关系数据要大。所以假如存储目前的瓶颈点是容量,那么我们只需要针对关系模块的数据做拆分就好了,而不需要拆分用户模块的数据。所以存储拆分首先考虑的维度是业务维度。
拆分之后,这个简单的社区系统就有了用户库、内容库、评论库、点赞库和关系库。这么做还能隔离故障,某一个库“挂了”不会影响到其它的数据库。
按照业务拆分,在一定程度上提升了系统的扩展性,但系统运行时间长了之后,单一的业务数据库在容量和并发请求量上仍然会超过单机的限制。这时,我们就需要针对数据库做第二次拆分。
这次拆分是按照数据特征做水平的拆分,比如说我们可以给用户库增加两个节点,然后按照某些算法将用户的数据拆分到这三个库里面,具体的算法我会在后面讲述数据库分库分表时和你细说。
水平拆分之后,我们就可以让数据库突破单机的限制了。但这里要注意,我们不能随意地增加节点,因为一旦增加节点就需要手动地迁移数据,成本还是很高的。所以基于长远的考虑,我们最好一次性增加足够的节点以避免频繁的扩容。
当数据库按照业务和数据维度拆分之后,我们尽量不要使用事务。因为当一个事务中同时更新不同的数据库时,需要使用二阶段提交,来协调所有数据库要么全部更新成功,要么全部更新失败。这个协调的成本会随着资源的扩展不断升高,最终达到无法承受的程度。
说完了存储层的扩展性,我们来看看业务层是如何做到易于扩展的。
2. 业务层的扩展性
我们一般会从三个维度考虑业务层的拆分方案,它们分别是:业务维度,重要性维度和请求来源维度。
首先,我们需要把相同业务的服务拆分成单独的业务池,比方说上面的社区系统中,我们可以按照业务的维度拆分成用户池、内容池、关系池、评论池、点赞池和搜索池。
每个业务依赖独自的数据库资源,不会依赖其它业务的数据库资源。这样当某一个业务的接口成为瓶颈时,我们只需要扩展业务的池子,以及确认上下游的依赖方就可以了,这样就大大减少了扩容的复杂度。
除此之外,我们还可以根据业务接口的重要程度,把业务分为核心池和非核心池。打个比方,就关系池而言,关注、取消关注接口相对重要一些,可以放在核心池里面;拉黑和取消拉黑的操作就相对不那么重要,可以放在非核心池里面。这样,我们可以优先保证核心池的性能,当整体流量上升时优先扩容核心池,降级部分非核心池的接口,从而保证整体系统的稳定性。
最后,你还可以根据接入客户端类型的不同做业务池的拆分。比如说,服务于客户端接口的业务可以定义为外网池,服务于小程序或者 HTML5 页面的业务可以定义为 H5 池,服务于内部其它部门的业务可以定义为内网池,等等。
课程小结
本节课我带你了解了提升系统扩展性的复杂度以及系统拆分的思路。拆分看起来比较简单,可是什么时候做拆分,如何做拆分还是有很多细节考虑的。
未做拆分的系统虽然可扩展性不强,但是却足够简单,无论是系统开发还是运行维护都不需要投入很大的精力。拆分之后,需求开发需要横跨多个系统多个小团队,排查问题也需要涉及多个系统,运行维护上,可能每个子系统都需要有专人来负责,对于团队是一个比较大的考验。这个考验是我们必须要经历的一个大坎,需要我们做好准备。
一课一思
在今天的课程中,我们谈到了传统关系型数据库的可扩展性是很差的,那么在你看来,常见的 NoSQL 数据库是如何解决扩展性的问题呢?欢迎在留言区和我一起讨论。
最后,感谢你的阅读,如果这篇文章让你有所收获,也欢迎你将它分享给更多的朋友。
分享给需要的人,Ta购买本课程,你将得18元
生成海报并分享
赞 29
提建议
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
上一篇
04 | 系统设计目标(二):系统怎样做到高可用?
下一篇
06 | 面试现场第一期:当问到组件实现原理时,面试官是在刁难你吗?
精选留言(61)
- mgxian2019-09-27memcached 官方没有提供集群特性,一般第三方会使用一致性哈希算法来进行负载均衡实现集群扩展。 redis 官方的集群选择把数据根据key来哈希分配到固定数量的 slot 上,然后把 slot 再分配到具体的redis实例上来实现集群,当需要扩展时,只需要把 slot 分配一些到新加入的 redis 实例即可。这种多加一层来实现集群扩展的方式,类似于我们数据库分表时使用的索引表。展开
作者回复: 👍👍👍👍
共 5 条评论50 - coffee2019-09-27关系型数据库是强schema约束,并且支持事务,所以扩展性相对较差。 NOSQL一般都不是强schema约束的,所以可扩展性比关系型数据库要好。
作者回复: 不只是schema free,还在于支持auto sharding
29 - 岁寒2019-10-16很很有感触,最近在做微服务拆分,面试过许多人,他们做拆分,是按照接口拆分,并不会按业务拆分,在我自己做的时候,就很容易发现,按业务确实不好拆。但是拆分的纬度不仅仅是业务,还有并发,比如块业务,是读多,还是写多,也可以按照这个纬度来分,在存储层,如果上层用redis做缓存,下层是mysql,也可以按redis拆,还是共mysql库 架构就是折中。共 1 条评论26
- YY2019-12-17数据库拆分后,但是各个库还是有相互依赖的,比如关系池,内容池都会依赖用户池,这种相互依赖的关系怎么解决?
作者回复: 梳理依赖关系,如果有相互依赖的话,看看能不能拆出更基础的服务,让其他服务都依赖它
共 3 条评论15 - xu晓晨2019-09-30redis扩展主要两方面。主备方案以及集群方案。 主备的话可以用redis的sentinel(哨兵)方案主要是解决redis主节点故障后的自动切换。它负责持续监控主从节点的健康,当主节点挂掉时,自动选择一个最优的从节点切换为主节点。 集群方案的话主流的是两种 一种是codis另一种是redis官方提供的cluster。
作者回复: 👍
共 6 条评论11 - A.Windy2019-09-29这不就是微服务嘛😄
作者回复: 是的哦
11 - hailong2019-10-09为了一个服务就要一个库? 本次课程举的例子一个数据库分不同的表就行了,没懂分库啥意思
作者回复: 分表的话还是不是在一个机器上,突破不了单机的限制
共 4 条评论8 - 吃饭饭2019-10-08两个问题: 1)尽量不适用事务,这里是不是需要使用分布式事务?类似于使用消息中间件等? 2)图示中的`池`指的是单个的微服务吗?我第一次听说这个概念,希望老师帮忙回答:)
作者回复: 1. 其实我还没有遇到什么场景是必须要使用分布式事务的,以前做过的社区产品对一致性的要求不是很高 2. 池子指的是一组服务器组成的集群,比如用户池就是这个集群只提供用户相关的功能
共 2 条评论4 - helloworld2019-10-07系统的易扩展之从存储和业务拆分来考虑。业务拆分是容易理解的,专人专事;从存储角度来考虑我想是因为所有应用的功能的最终的结果都是把一些结果存储起来,于是存储就是重点,是重点就必然引起量大,量大了必然成瓶颈。打卡04。
作者回复: 是的
4 - StarKing2019-10-02预测未来的数据库一定会综合ES+MONGODB的能力,直接选择库的模式就可以像NOSQL一样使用。
作者回复: 这样看你团地的运维能力了
4 - 飞翔2019-09-27老师。不太理解什么是池? 用户池 里是一台机器还是多台机器呀?
作者回复: 池子就是一组机器组成的集群
共 5 条评论4 - Christopher2020-01-17看的是真过瘾,很有逻辑和条理性,不像一些课程要么都是曲高和寡的技术细节,要么都是泛泛白话
作者回复: 感谢您的支持和肯定,预祝新年快乐,事事如意
3 - XD2019-11-01redis相关的书籍或者博客有推荐吗?前段时间刚看了一个分布式锁的文章,收获很大。
作者回复: 可以看看黄建宏的redis设计与实现
共 5 条评论3 - Hwan2019-10-29看评论说auto shard ,最近在看redis,redis通过哨兵做主从备份和主从迁移,通过集群做增加主节点和减少主节点的事情,基本可以做好自动扩容和缩容了,请问下关系型数据库里面有类似的吗,能够实现自动的扩容之类的,还是说必须需要人工来呢
作者回复: 需要制定迁移方案,加餐中有介绍哦
2 - 醇梨子2019-09-28我们在实际生产环境中,线程池隔离及线程池设置大小的依据是什么?
作者回复: 主要要监控一下线程池中任务的堆积情况,然后动态调整大小
2 - 王三好2020-04-17一直都是这么做的,没有系统整理过,业务池子拆分还可以根据,业务是否经常变化,经常变化的抽取出来
作者回复: 👍
1 - null2020-03-26老师,您好! 原文“把相同业务的服务拆分成单独的业务池” 这里为啥取名为“池”,池子里的最小单位是服务实例,还是接口吖? 产生这样的疑问,是因为学习到按业务维度划分时,池子里都是相同业务的服务,如内容池都是一组内容服务。而按核心非核心划分时,是对接口划分。 谢谢老师!!展开
作者回复: 池子里是接口
1 - 芳菲菲兮满堂2020-03-18如果说it存在万金油 那么可能就是拆分和分层
作者回复: 嘿嘿😁
1 - JOHN2020-02-07查阅了一些资料,我发现作者对于可扩展和网上大部分的解读不一样,网上的解读是架构上要封装变化,以此实现对扩展开放(开闭原则),我更加认同网上的解读,我觉得作者的这个解读更加适合 可扩容 这个词。概不过话说回来,可扩容也给我们读者提供了一个新的视角来看系统,也很棒的。
作者回复: 是解释层面不一样吧 从写代码的角度来说,可扩展可以认为是封装变化 从架构层面来说,我认为可扩展性是可灵活扩容
共 4 条评论1 - 书中迷梦2019-10-15存储层扩展的时候,是不是加个限定词比较好,对于数据不敏感的业务可以考虑不使用事物!而不是直接不使用事物!像支付和订单这些系统,数据肯定需要一致性
作者回复: 支付需要考虑一致性,订单应该还好
1