如何在云中构建大规模分布式系统
“烦恼”时,如何利用云计算平台的弹性,结合业务自身特点,设计和构建一个高可用、高伸缩性的后端系统架构。同时会以 QingCloud 平台上的真实案例为背景,讲述从简单后端系统到大规模分布式系统的演进之路。
青云QingCloud 系统研发工程师,负责 QingStor 对象存储服务的设计与研发,对 Linux 操作系统、计算机网络、分布式系统、云计算等领域有较深入的研究。原街旁团队创始成员,基础架构负责人。九零前,文青程序员,代码诗人,北京土著。
以下是本次分享的内容整理。
大家好,我是 QingCloud 系统工程师王煜(William),今天分享的主题是如何在云计算平台上构建稳定可靠的分布式系统架构。
◆ ◆ ◆
幸福的烦恼
很多企业和开发者在开发一款产品时,首要考虑的是产品功能的实现,其后端架构通常都是非常简单直接的。产品在上线初期,由于用户访问压力很小,数据量积累也不大,在短时间内都会运行良好。
然而如今移动互联网的普及和成熟,让一款产品很可能在短时间内聚集大量用户,企业就会面临流量激增、数据量翻番、访问量指数级攀升等诸多“烦恼”。这本来是一件好事,可是如果后端系统不能及时扩展,势必会造成响应缓慢,频繁出错甚至拒绝服务的情况。
即便没有上述系统压力突然增大的“烦恼”,产品在不断开发升级的过程中,各种功能模块会变的越来越复杂,如果不能很好的梳理和组织后端架构,系统出错崩溃、不可使用的风险也会越来越大。
在没有云计算的时代,物理硬件从采购、上架、插线,到安装、调试、部署,再到真正投入使用,是一个漫长而耗费人力的过程,往往跟不上系统紧急扩容的节奏。而云服务的出现不仅仅让我们节约了使用成本,更重要的是可以利用云计算极度弹性的特点,让企业和开发者根据需求,对系统进行在线快速的扩容。
但仅仅在云服务上快速扩容是不够的,企业也需要在业务层面,关注各个系统组件的可用性和伸缩性。接下来我来给大家介绍如果利用云计算的优势,结合企业的业务特点构建稳定可靠的分布式系统。
◆ ◆ ◆
接入层如何扩展
首先我们从一个最简单的后端架构开始:
接入层:Nginx
业务层:Java Application
数据层:MySQL
接下来我们回到上面那个简单的后端架构。随着访问压力越来越大,单台 Nginx + Java Application 可能不足以应付,你会看到这台主机的 CPU 越来越忙 ,内存使用越来越多。而且这台主机一旦故障,整个服务都不可用了。
所以我们首先调整这里的结构,增加多台 Nginx + Java Application 同时提供服务,在接入层引入负载均衡器(下文用 LB 这个词代替),使外网请求首先发到 LB 上。LB 的选择有很多,比如提供七层负载能力的 Nginx 和 HAProxy ,也有提供四层负载能力的 LVS,安装和配置的方法各有不同。
LB 的引入可以分摊请求压力到后端的多台业务服务器,并且可通过心跳检查,自动隔离后端出现故障的服务器,实现业务层的高可用。但这时 LB 本身也会成为一个单点,当出现故障也会导致全局不可用。所以可以使用 服务为 LB 提供一个副本,在一台出问题的时候可以马上顶上,部署方法网上有很多资料。
有人会说可以通过 DNS 轮询到不同的 IP ,实现 LB 的高可用,但事实上这样不行,因为一旦一台 LB 挂掉,DNS 还会解析到这个 LB,此时即便马上修改 DNS,在 DNS 缓存更新之前(通常要很久),服务也是不可用的。
虽然 LB 的原理并不复杂,但是部署配置有很多工作量,而且为了实现 LB 的高可用还要额外做一些事情。QingCloud 从北京 3 区开始提供了高性能、高可用的 LB 集群服务,可以直接拿来使用。
改造后的架构如下图所示:
◆ ◆ ◆
业务层如何扩展
接下来我们思考业务层的扩展问题。首先要解决如何快速扩充业务服务器。如果业务服务器的运行环境和程序不会频繁更新,可以基于已有的业务服务器制作主机映像,当需要扩容时,直接基于映像创建新的主机,挂接到 LB 后端就可以马上对外服务了。
此时你还可以使用 Auto Scaling 功能自动化这一过程,即当到达某种触发条件,如 LB 并发数、响应延迟达到多少后,自动触发主机的扩容。当触发条件不满足时,可以回收资源。
当然如果你的业务服务器环境或程序需要频繁更新,不适合做成固定模版。此时可以自己搭建自动化部署(如 Puppet / Ansible)实现业务自动扩容,这一切操作可以使用 QingCloud 开放的 API 接口,结合你的自动化部署程序完成。
此外你还需要保证业务服务器是无状态的,因为每次 LB 请求的后端可能不同,不能假设上一次请求和这一次请求落在同一台业务服务器上。如果服务器需要保存用户访问的 Session 信息,可将其下放到缓存或数据库中存储。
随着产品功能越来越丰富,你会发现原有单一的业务项目越来越庞大,各种功能逻辑交织在一起,当一个功能出现故障,可以引发全局不可用。此时你需要考虑将单一的业务项目分拆成多个独立子服务。子服务之间可以基于消息通信,亦或基于 RPC 通信。
子服务的调用可分为需同步处理和可异步处理两类。你应该尽量异步化所有不需要马上返回结果的请求。对于可异步处理的请求,我们通过引入消息队列,为请求产生的数据做缓冲,请求的接收者(队列消费者)可根据队列中任务的数量做水平扩容。
消息队列的选择有很多,例如 RabbitMQ、ActiveMQ、,QingCloud 平台上目前已经提供分布式、可分区、多副本的消息队列服务,具有高吞吐量、低延迟等特点,用户可以方便的集成到自己的系统中。
如今数据分析对于企业越来越至关重要,业务服务器在处理请求的过程中,可以将原始数据通过队列,源源不断地导入大数据处理系统,QingCloud 提供完善的大数据分布式处理平台 Spark 和 Hadoop,用户可以根据需求方便的创建,使用和扩容。
通过拆分子服务,使得我们有能力在某项子服务发生故障时,尽可能降低对于全局的影响,提高系统整体的可用性。另外,对于处理压力比较大的子服务,我们还可以进行独立的水平扩容,方式和前面讲到的业务服务器扩容相似,QingCloud 内网 LB 服务也可以在这里发挥作用。
改造后的架构如下图所示:
◆ ◆ ◆
数据层如何扩展
随着业务的增长,数据层面临的压力会越来越大,单机数据库已经不足以支撑,接下来我们谈一下数据层的分布式和扩展技术。
对于大多数的业务场景来说,数据的操作都是读多写少,而且读都集中在少部分的热点数据上,首先应该引入缓存层来缓解数据库的读压力,如果缓存容量需求比较大,可以构建缓存集群,在上层按照 Consistent Hashing 算法将数据分散到多个节点,后续需要增加新缓存节点时,只有少部分的数据会失效。
接着引入新的数据库种类,Redis 已经成为诸多企业的首选标配,因为其支持丰富的数据类型和数据查询接口,且内存型的数据库天然具有更高的性能。
你可以将业务中关系性要求不高的数据,从 MySQL 转移到 Redis 中,尤其是列表类的数据以及计数统计类的数据。给 MySQL 减负的同时提高数据的查询性能。
单台 Redis 节点也许不能满足你对容量的需求,QingCloud 平台提供了支持多主多从 Redis 3.0 集群服务,一方面可对数据自动分区提高存储容量,另一方面保证了服务的高可用性。
对于 MySQL 的扩展可以分为几个步骤来做。首先,增加 MySQL Slave 节点,在上层将部分读请求分发到 Slave 节点上去,由于 Slave 同步可能有延时,业务应该能容忍短暂的数据不一致现象,举例,比如你的一个用户修改了年龄属性,其他用户要等一会儿才能看到他的新年龄。
QingCloud MySQL 数据库支持一主多从的架构,并且已经在多个从节点之上做好了负载均衡,你可以轻易在界面上操作增加新的从节点来分担业务的读压力。
即便有 Slave 作为数据副本,你也应该定期对数据库进行冷备份,方便当业务出现误操作时,能够回滚或恢复到曾经的某个时间点。在 QingCloud 平台上,备份的过程可以手动执行或者配置为自动任务,备份过程不影响数据库的正常使用。
随着数据的增长,单个数据库不能承载完整的数据集合,并且写操作对于单库的压力越来越明显,你应该考虑分库分表技术。将比较庞大的数据表拆分出来单独存放,可以给主数据库腾出来一部分空间,分担读写压力。拆分的时候,还可以按照功能逻辑,把相关联的数据表存在一个库里。
当数据库单表非常庞大,对读写都造成瓶颈时,你需要开始考虑水平分表 Sharding ,这种扩展方式可以同时解决单表容量过大,读压力和写压力过大的问题,但带来的研发和运维难度也会增大,推荐把上述的优化做完以后,最后在有必要的情况下再做。
这里简略说一下水平分表的要点。首先要从数据表的字段中,选择一个合理的分区键(Shard Key),这个键应该是所有该表查询条件里最经常用到的字段,这样才会使大部分的查询,能够提前判断应该向哪些特定的分区(Shard)发送请求,如果查询条件中不带 Shard Key ,需要遍历所有的分区,并将结果进行 Merge。
有了 Shard Key还要设计一种分区算法,比如常见的有按照区间,如 User_ID IN [0, 100] 在 Shard 1,User_ID IN [101, 200] 在 Shard 2,还比如按照 Hash 取模等等。设计分区算法的时候要充分考虑业务特点,多从读写操作的角度思考,这么设计能否将压力和数据均匀分摊到每个 Shard 上去。
还需要考虑数据层的扩展如何对上层透明,比如引入分布式数据库中间件,或者结合业务逻辑把数据库操作做成一个独立的子服务,供其它服务调用。如果不做成子服务,至少在业务代码里有独立的一层来封装对数据库的操作。
至此,数据层的扩展示意图如下所示:
除了上述的结构化数据的存取以外,企业还有存储海量小文件数据(非结构化数据)的需求,单机硬盘、LVM 和 NAS 可以作为临时方案使用,但都无法同时满足无限容量、高性能、高安全性、高可用性的多重需要。而自行搭建分布式存储系统,如 Ceph、GlusterFS、HDFS 适用场景非常有限,且运维和二次开发的成本也非常高。
在 QingCloud 平台上用户可以使用 QingStor 对象存储服务来存储海量的数据文件,服务本身提供了无限容量、高扩展性、高可用性和高安全性的特性。
◆ ◆ ◆
多机房部署和异地容灾
讲完数据层的扩展技术,最后来谈一下多机房部署和异地容灾的话题。QingCloud 从北京3区机房开始,通过自营的骨干网光纤和多路环网技术,使得当机房出现网络故障时保障用户无感知,在基础设施上保障了高可用性。但是用户的业务如果能够多机房部署,可以在分摊访问负载的同时加速区域访问,比如加速中国南北方的用户或者海外用户的访问。
如上图所示,若是有三个机房,中间是 QingCloud 北京3区机房,负责主营业务。左边是 QingCloud 亚太1区机房,主要服务亚太和海外的客户。这两个机房都使用了 QingCloud 私有网络(VPC)部署,通过GRE或IPsec加密隧道在网络上的互联互通。右边是你办公室的物理机房,IT 人员可以在这个环境下进行开发和办公。
在业务上实现异地多活时,通常从易到难有三个阶段:
第二,两个机房同时部署业务服务器和缓存,由于大部分数据请求可以从缓存中读取,不用进行跨机房访问。但当缓存失效时,依然要从主机房的数据库去查询。
第三,两机房同时部署全套系统,包括接入层、业务层和数据层。数据层依靠数据库双主或主从技术进行跨机房同步。
◆ ◆ ◆
总结
最后总结一下今天的分享。没有一个所谓经典或完美的架构,只有最适合企业业务的架构,今天分享的是在最通用的业务场景下,系统在接入层、业务层和数据层的常用扩展方法。企业后端架构的演进过程是一个漫长而艰巨的过程,不可能从零开始一蹴而就,就能设计出一个万般周全的系统,但如果设计之初能更多着眼于未来,就可以为进一步优化留出了余地。
【预告】青云QingCloud 实践课堂广州站报名开始了。还是那句话——我们不作秀,只想同认真好学的你分享最踏实的技术知识。
报名请扫描下方二维码,或者点击 阅读原文 。
- FIN -