02-企业应用系统架构设计的原则二(高伸缩设计)
xiangliheart
xiangliheart
发布于 2022-01-05 / 22 阅读 / 0 评论 / 0 点赞

02-企业应用系统架构设计的原则二(高伸缩设计)

大型互联网架构高伸缩设计

要学习伸缩性架构,就必须先掌握垂直扩展和水平扩展两个概念。

垂直扩展是指通过增加服务器的配置来满足业务要求,可以增加CPU核数、内存大小、磁盘容量,或者更换读写更快的内存、网卡、固态硬盘。

水平扩展是指通过增加服务器的数量来满足业务要求,服务器的配置可以不高,1台不够用2台,2台不够用4台。

垂直扩展和水平扩展就好像人搬桌子一样,垂直扩展是一个人搬不动则找一个更强壮的人来搬,甚至换成机器来搬;水平扩展是一个人搬不动则找两个人来搬,如果还搬不动则找十个人来搬。垂直扩展和水平扩展的对比如图2-58所示。

图2-58 垂直扩展与水平扩展的对比

高伸缩性主要是指服务的水平扩展能力,能够快速地通过增加服务器的数量提高系统性能和存储能力,从而能够支撑高并发请求,或者海量数据的存储。具有高伸缩性能够达到按需扩容和弹性伸缩的目的,对服务器资源的利用更加合理。

例如,电商系统在“双11”“618”等大型促销活动之前,对活动期间的用户访问量、订单量进行预测,对登录服务、订单服务、产品服务进行重点扩容,水平扩展几百个甚至几千个节点来应对即将到来的亿级高并发访问。等活动结束后,交易高峰逐渐消退,就可以释放这些临时扩展的节点,从而达到按需伸缩的目的,极大地节约成本。

水平扩展需要借助负载均衡技术(参见2.1.3小节),然而应用想要支持水平扩展,最好提供无状态服务,有状态服务是难以水平扩展的。

无状态服务与有状态服务是相互对应的,主要看用户的多次请求是否存在上下文关系和依赖关系。

在图2-59所示的架构下,用户服务水平部署2个节点,通过Nginx进行负载。

(1)当用户上传头像时,请求被分发到用户服务A节点,并且将头像文件存储在A节点的本地磁盘中;

(2)当用户查看头像时,请求被负载到用户服务B节点,B节点从本地磁盘的相同目录中无法查找到用户头像文件,因此头像无法加载;

(3)用户反复刷新加载头像,就会出现一次有头像,一次没有头像的现象。这就是一种有状态服务在负载均衡架构下所引发的问题。

图2-59 有状态服务示例

对于一些借助于本地内存、本地磁盘的服务都属于有状态服务,它们无法进行水平扩展。典型的场景有借助于Cookie和Session的登录机制(在4.8节中有详细讲解),借助于Session的购物车、图片验证码功能,文件上传、导出、导入功能,批处理等功能。

有状态服务修改为无状态服务主要有两种方案:数据同步和数据共享。

1.数据同步

对于本地磁盘可以采用数据同步的方式。例如,在头像上传和加载的例子中,只需要将用户服务A节点中上传的头像文件同步给用户服务B节点即可,这样无论用户访问到哪台服务器上都可以获取到头像文件,如图2-60所示。

图2-60 数据同步

对于Session可以采用Session同步的方式,保证多个服务节点的存储一致,如图2-61所示。这种方式只适合节点较少(一般不大于5个)的情况,否则会出现同步延迟,以及同步风暴问题。

图2-61 Session同步

在图2-62所示的场景下,将某个服务扩展为6个节点,其中任何一个节点发生数据变化,就要通知其他5个节点,并将数据也同步到其他5个节点。如果在高并发场景下,每个节点并发数为1000,则同一时间就要发生30000次的数据同步,性能会急剧下降,数据同步延迟加剧。

图2-62 通知风暴、同步风暴问题

注意

某个服务一共有N个节点,每个节点数据发生变化都要同步到其他N - 1个节点,节点数量越大,通知风暴效应越大,因此对于任何同步策略都需要考虑同步节点数量和数据传输量问题。

2.数据共享

多个服务节点将有状态数据存储到共享介质中,不会产生任何的延迟和不一致问题,是当前使用最多的一种架构方式。例如,可以将Session、图片验证码、购物车等数据存储到Redis中,将Token、JWT等认证信息存储到Redis或MySQL中,如图2-63和图2-64所示。

图2-63 共享磁盘存储

图2-64 共享数据库存储

数据共享的缺点是应用需要依赖第三方服务,额外维护与第三方之间的连接,并通过网络交互完成数据的读写。因此,性能不如读写本地内存和本地磁盘,并且需要面对第三方服务不可用的情况,如网络抖动、无法连接、服务器宕机等问题。

现在的大多数中间件都具有高可用模式,如主从模式、副本集模式、集群模式等,并且它们都可以在内网直接访问,因此服务的稳定性、可靠性已经不是太大的问题。

2.2.1 文件存储伸缩设计

伸缩性还体现在文件存储方面,对于互联网业务(如云相册、云存储、短视频、影音等),需要海量的文件存储空间,因为有大量的文件上传、下载操作。一方面要保证有足够的存储空间,能够随时扩容;另一方面还需要具有极高的访问效率和安全性,避免用户的文件丢失。

主要使用网络文件系统和分布式文件系统架构来解决这个问题,

如NFS、GFS、FastDFS、HDFS、Ceph、GridFS、MogileFS、TFS、对象存储云服务等。下面选择比较有代表性的NFS、GFS和FastDFS进行讲解。

1. NFS

NFS(Network File System,网络文件系统)能让服务器之间通过 TCP/IP协议共享存储资源,可以将本地目录挂载到远程目录上,客户端可以透明地操作远程NFS服务器上的文件(读写远程文件与读写本地文件是一样的,应用程序无须任何修改)。

NFS挂载和传输的原理如图2-65所示,客户端可以在挂载点(/data 目录)上进行创建、复制、重命名、移动、删除等各种文件操作,就好像在操作本地文件一样,所有操作均会同步到NFS服务器上执行。为了保证NFS服务的数据不丢失,还可以配置备份节点进行数据备份。

图2-65 NFS挂载和传输的原理

NFS的优点是对开发人员、运维人员透明,搭建和上手难度较低,易于维护。

NFS的缺点是:(1)存在单节点故障,NFS服务器宕机,就会导致所有客户端无法读取历史文件,并且会将新文件写入本地磁盘下,重新挂载需要手动同步;(2)NFS在高并发场景下性能存在瓶颈;

(3)没有安全认证机制,数据都是通过明文传输,对数据完整性不做校验。因此,建议NFS在内网环境下中小规模系统使用。

2. GFS

GFS(Google File System,谷歌文件系统)采用一主多从的结构,由一个GFS master节点(主服务)和多个GFS chunkserver节点(块服务)构成,其架构如图2-66所示。

图2-66 GFS架构(来源于Google File System论文)

GFS采用文件分块存储的方式,如图2-67所示。1个256MB的视频文件,可以被分为4个64MB的块文件,存储到GFS chunkserver节点

中,为了保证文件的高可用,各个GFS chunkserver之间会进行相互复制(Replica),将自己节点上的块分散到其他节点上。文件被拆分后势必要记录它们的存储位置,在哪个GFS chunkserver节点的哪个块上存储,而这些位置信息及对应关系就是文件的元数据,被存储在GFS master节点中。

图2-67 GFS 文件分块存储

GFS master节点用于存储集群中的所有元数据信息,整个集群只有一个GFS master节点,并且数据全部存储在内存中,因此读写效率和一致性极高。GFS master节点的内存容量也代表着GFS集群的最大存储容量。

GFS master节点以树形结构存储文件的命名空间(namespace),可以将这棵树理解为常见的目录结构或索引,目的就是记录文件的位置,以便快速地查找并读写文件。元数据包括文件与chunk的对应关系,以及chunk副本的存储位置。

GFS master和GFS chunkserver之间会保持心跳检查,从而判定存储节点的存活状态,以及数据存储现状,GFS master还负责GFS chunkserver的存储空间回收任务。

客户端要读取某个文件时的过程如图2-68所示。

(1)     客户端去询问GFS master,文件存在哪里,从哪能找到它们?

(2)     GFS master根据文件名在元数据中进行检索,返回要读取内容的所有副本位置。

(3)     客户端直接与存储了该数据副本的GFS chunkserver交互,请求读取文件。

(4)     GFS chunkserver将数据返回给客户端。

图2-68 GFS读取文件

通过文件的读取流程可以看出,客户端读取数据时,实际上直接与GFS chunkserver交互传输,并不会经过GFS master节点,以免GFS master成为性能瓶颈。

GFS的设计目标主要是针对大文件存储、读多写少的场景,支持弹性伸缩(增加GFS master的内存、增加GFS chunkserver节点的数量或存储容量即可)、高并发访问、数据冗余备份,能够运行在廉价低端的服务器上。

3. FastDFS

FastDFS(Fast Distribute File System,Fast分布式文件系统)是一个轻量级分布式文件系统,使用C语言编写,主要面向互联网业务。目前包括阿里巴巴、迅雷、58同城等大型互联网厂商均有使用。FastDFS 具有冗余备份、负载均衡、伸缩扩容、副本容错、可用性高等特点,能够提供高效的文件上传和下载服务。

与GFS不同,FastDFS更适合存储占用空间小但数量巨大的“碎文件”,它不对文件进行分块存储,避免了文件拆分与合并的开销。

FastDFS主要分为两个集群,即Tracker集群和Storage集群(与GFS 架构十分相似,Tracker等价于GFS master,Storage等价于GFS chunkserver),其架构如图2-69所示。

图2-69 FastDFS架构

Tracker集群负责负载均衡和元数据存储,Storage集群负责文件存储和副本复制。Storage定时向Tracker上报自己的节点状态、剩余存储空间、文件复制等情况。

FastDFS上传文件的流程如图2-70所示。

(1)  客户端请求Tracker,告知要上传文件。

(2)Tracker查找可用的Storage,哪个组还有足够的存储空间,选用哪个Storage节点存储本次请求的数据。

(3)Tracker 将Storage节点的IP、端口等信息返回给客户端。

(4)客户端使用IP、端口直接请求Storage节点,上传文件数据。

(5) Storage节点生成文件ID,并将文件写入磁盘保存。

(6)Storage节点将文件ID返回给客户端。

(7)客户端将文件ID与业务数据关联存储。

图2-70 FastDFS上传文件的流程

以上流程中的重点是文件ID,等同于文件的唯一路径,可以用于快速检索并下载文件。文件ID=/组/虚拟磁盘路径/一级目录/二级目录/ 文件名.扩展名。

示例:文件ID=/group1/M00/23/45/1Wkf23Fd223FdsFsKwsd.jpeg。

文件名与上传时的原始文件名不同,它具有特定的含义,包括文件时间戳、大小、随机数、源存储Storage的IP地址等信息,组合并编码加密后形成最终文件名。

FastDFS下载文件的流程如图2-71所示。

(1)     客户端请求Tracker下载文件,提交文件ID。

(2)     Tracker查找文件存储在哪些Storage节点上。

(3)     Tracker节点返回Storage节点的IP、端口等信息。

(4)     客户端直接请求对应的Storage节点,并携带文件ID。

(5)     Storage根据文件ID查找文件。

(6)     Storage将文件内容返回给客户端。

注意

无论是FastDFS还是GFS,客户端都是直接与Storage、GFS chunkserver存储节点进行文件交互,提高了系统的吞吐量和处理效率。

图2-71 FastDFS下载文件的流程

2.2.2 数据库伸缩设计

应用和磁盘存储需要水平伸缩来提高可靠性、服务能力、数据容量,数据库也同样如此。数据库的扩容主要有3个目的,一是容量的增加,二是读写性能的改善,三是连接数的增加。

一些企业内部系统,用户数量和业务规模有限,因此并不会产生巨大的数据量,这时数据库的存储和读写性能均不会成为瓶颈,没有扩容的需要,因此无须考虑伸缩性。

对于一些互联网系统,前后端应用可以通过CDN、缓存、负载、水平扩展等技术解决瓶颈问题,但是数据库成为最终的读写集中点,每天都在产生海量的数据和读写请求,因此成为系统瓶颈。

Redis、MongoDB等NoSQL数据库虽然支持数据分片,但是并不能取代关系型数据库,对于逻辑关系紧密、复杂的系统,必须借助关系型数据库良好的事务特性来支持。绝大多数公司依然主要采用关系型数据库,而将其他数据库作为辅助。

对于MySQL数据库,虽然理论上单表可以支撑上亿条数据,但是性能较差,增加索引、优化SQL语句已经于事无补。阿里巴巴规约提出单表行数超过500万行或单表容量超过2GB,才推荐进行分库分表。这个建议的目的是避免过度设计。

数据库的扩容主要采用分库分表的策略,按照垂直拆分和水平拆分两个维度,可分为4种策略,分别是垂直分表、垂直分库、水平分表和水平分库,如图2-72所示。

图2-72 分库分表的两个维度

1.垂直分表

垂直分表的特点是在一个数据库内,将一个表垂直拆分为多个关联表,表结构彼此不相同。

如图2-73所示,商品表包含商品的基本信息、广告信息、促销信息等数据,造成数据表的字段较多,有些字段(热点字段)是频繁访问的,而有些字段很少访问,这样会导致数据表读写性能较低。因此,可以将商品表垂直拆分为商品基本信息、商品广告信息和商品促销信息3个表,3个表之间通过相同的商品ID做关联,每个表只包含商品信息的部分字段。

这样就减少了每个表的字段数量,同时尽量将经常访问的字段放在同一个表中,不经常访问的字段放在其他表中,提高数据的检索速度。垂直分表对程序设计有影响,需要同步调整程序。

2.垂直分库

垂直分库的特点是将一个数据库拆分为多个数据库。企业早期将所有业务表都放在一个数据库中,包含用户、订单、产品等所有数据。各个服务都连接到同一个库,从而造成数据库的存储、连接数、读写性能都成为系统瓶颈,如图2-74所示。

图2-73 垂直分表

图2-74 集中式数据库

垂直分库就是将一系列相关的表单独拆分为一个数据库,达到专库专用的目的,可以提升数据库的读写性能、连接数。如图2-75所示,将以前的一个集中式数据库拆分为用户库、订单库和产品库,再由各个系统使用。

图2-75 垂直分库

垂直分库会对整个系统架构产生影响,并且需要考虑分布式事务问题,需要对业务进行更加细致的设计,开发成本较高。

虽然垂直分表、垂直分库有诸多好处,但是依然没有解决单表数据量过大的问题,每个表中还是存储全量数据。例如,一个订单表有一千万条数据,垂直拆分为多个表或多个库后,单表依然是一千万条数据。

3.水平分表

水平分表的特点是在同一个数据库内,将一个表水平拆分为多个表,表结构均相同。

当单表记录数过大时可以进行水平拆分,如图2-76所示,将商品表拆分为多张表,每张表中都只存储部分产品数据。对于每一条商品数据如何存储到表中,存储到哪个表,就涉及分片字段和分片算法的选取,一般使用主键作为分片字段,使用基于哈希取模和范围的分片算法。

图2-76 水平分表

(1)基于哈希取模的分片算法:采用商品ID哈希取模的方式进行存储,如图2-77所示。例如,要存储ID为100的数据,经过整数哈希算

法计算之后得到8719912,与分片表的数量3取模。计算公式为

MOD(INTHASH(100),3)=8719912%3=1,代表此条数据要存入商品表

1(从0计数)。

图2-77 基于哈希取模的分片存储

基于哈希取模的分片算法的优点是可以让数据分布得更加均匀,但是数据存储比较分散,不适合范围类查询。例如,当需要新增或减少分片表时,会引起较大的数据流动(数据从一个位置转移到另外一个位置)。例如,再增加一个分片表,变为4张分片表,当存储ID为

100的数据时,分片位置的计算公式为

MOD(INTHASH(100),4)=8719912%4=0,则原来存储在商品表1中的数据就需要转移到商品表0中。

(2)基于范围的分片算法:也可以基于分片字段的范围进行存储。例如,根据商品ID的范围进行存储,如图2-78所示,分片规则为商品ID为1~1000万的存储在商品表0中,1001万~2000万的存储在商品表1中,2001万~3000万的存储在商品表2中,这样每张表中的数据都不会超过1000万条。

图2-78 基于范围的分片存储

例如,当插入商品ID为1100万的数据时,对比此ID属于1001万

~2000万这个范围,因此将数据存储到商品表1中。

常用的还有基于时间范围的分片算法。例如,从2001年至2020 年,每个年份创建一张表,这样将相同业务日期的数据存入相同的表中,既便于查询又方便转储和清理。

除了基于哈希取模和范围的分片算法,应用程序也可以自定义算法和路由规则。例如,可以定义一个路由规则表,每次进行增删改查时按照规则定义计算。

4.水平分库水平分库的特点是将同一个表水平拆分到多个数据库中,每个表的结构相同。

水平分库与水平分表的原理基本相同,即将一个表拆分为多个相同结构的表,分散在不同的数据库中,如图2-79所示。水平分表是在同一个数据库中拆分为多个相同的表,会造成一个数据库中的表数量过多,依然会面临单机数据库的性能瓶颈问题,因此水平分库的性能更好。水平分库与水平分表的分片规则相同,这里不再赘述。

图2-79 水平分库

水平分库也面临着诸多问题:如何进行跨库关联查询?如何进行跨库的分页和排序?如何保证数据事务的一致性?如何进行数据迁移?分库分表的基本原则有以下3点。

(1)     不进行过度设计,不需要将所有库、所有表都进行分库分表。分库分表虽然有诸多好处,很好地解决了数据库的存储容量和性能瓶颈,但是程序复杂度会急剧升高,出现问题的概率呈指数级增长。

(2)     不在项目初期就进行分库分表,而是动态调整。当单表数据量增长到一定程度时再进行分库分表即可。

(3)     先垂直拆分,再水平拆分。如果垂直拆分能够解决问题,则不需要再进行水平拆分。

5.分库分表框架推荐

这里推荐使用Apache ShardingSphere框架,其起源于当当网开源的 Sharding-JDBC框架,已于2020年4月16日成为 Apache 软件基金会的顶级项目。

以下内容来源于Apache ShardingSphere项目官网介绍。

Apache ShardingSphere 是一套开源的分布式数据库解决方案组成的生态圈,它由 JDBC、Proxy和 Sidecar(规划中)这3款既能够独立部署,又支持混合部署配合使用的产品组成。 它们均提供标准化的数据水平扩展、分布式事务和分布式治理等功能,可适用于如 Java 同构、异构语言、云原生等各种多样化的应用场景。

Apache ShardingSphere旨在充分合理地在分布式的场景下利用关系型数据库的计算和存储能力,而并非实现一个全新的关系型数据库。关系型数据库当今依然占有巨大市场份额,是企业核心系统的基石,未来也难以撼动,我们更加注重在原有基础上提供增量,而非颠覆。

Apache ShardingSphere 5.x 版本开始致力于可插拔架构,项目的功能组件能够灵活地以可插拔的方式进行扩展。 目前,数据分片、读写分离、数据加密、影子库压测等功能,以及 MySQL、PostgreSQL、

SQL Server、Oracle 等 SQL 与协议的支持,均通过插件的方式植入项目。开发者能够像使用积木一样定制属于自己的独特系统。Apache

ShardingSphere 目前已提供数十个 SPI 作为系统的扩展点,仍在不断增加中。


评论