服务端高并发分布式结构演进之路
个人主页:C++忠实粉丝
欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 C++忠实粉丝 原创服务端高并发分布式结构演进之路
收录于专栏[redis]
本专栏旨在分享学习Redis的一点学习笔记,欢迎大家在评论区交流讨论💌
目录
概述
常见概念
基本概念
评价指标(Metric)
架构演进
单机架构
应用数据分离框架
应用服务集群架构
读写分离/主从分离架构
引入缓存 —— 冷热分离架构
垂直分库
业务拆分 —— 微服务
分布式系统总结
概述
在进行技术学习过程种,由于大部分读者没有经历过一些中大型系统的实际经验,导致无法从全局理解一些概念,所以本文以一个 “电子商务” 应用为例,介绍从一个到千万级并发情况下服务端的架构的演进过程,同时列举出每个商务阶段会遇到的相关技术,让大家对架构的演进有一个整体的认知,方便大家对后续知识做深入学习时有一定的整体视野。
常见概念
在正式引入架构演进之前,为避免读者对架构中的概念完全不了解导致低效沟通,优先对其中一些比较重要的概念做前置介绍:
基本概念
应用(Application)/ 系统(System)
为了完成一套服务的一个程序或者一组相互配合的程序群。生活例子类比:为了完成一项任务,而搭建的由一个人或者一群相互配合的人组成的团队。
模块(Module)/ 组件(Component)
当应用较复杂时,为了分离职责,将其中具有清晰职责的、内聚性强的部分,抽象出概念,便于理解。生活例子:军队中为了进行某据点的攻克,将人员分为突击小组、爆破小组、掩护小组、通信小组等。
分布式(Distributed)
系统中的多个模块部署于不同的服务器之上,即可以将该系统称为分布式系统。如 Web 服务器于数据库分别工作在不同的服务器上,或者多台 Web 服务器被分别部署在不同服务器上。生活例子类比:为了更好的满足现实需要,一个在同一个办公场地的工作小组被分散到多个城市的不同工作场地中进行远程配合工作完成目标。跨主机之间的模块之间的通信基本要借助网络支撑完成。
集群(Cluster)
被部署于多台主机服务器上、为了实现特定目标的一个/组特定的组件,整个整体被称为集群。比如多个 MySQL 工作在不同服务器上,共同提供数据库服务目标,可以被称为一组数据库集群。生活例子类比:为了解决军队攻克防守坚固的大城市的作战目标,指挥部将大批炮兵部队集中起来形成一个炮兵打击集群。
分布式 VS 集群。通常不用太严格区分两者的细微概念,细究的话,分布式强调的是物理形态,即工作在不同服务器上并且通过网络通信配合完成任务;而集群更在意逻辑形态,即是否为了完成特定服务目标。
主(Master)/ 从 (Slave)
集群中,通常有一个程序需要承担更多的职责,被称为主;其他承担附属职责的被称为从。比如 MySQL 集群中,只有其中一台服务器上数据库允许进行数据的写入(增/删/改),其他数据库的数据修改全部要从这台数据库同步而来,则把那台数据库称为主库,其他数据库称为从库。
中间件
一类提供不同应用程序用于相互通信的软件,即处于不同技术、工具和数据库之间的桥梁。生活例子类比:一家饭店开始时,会每天去市场挑选买菜,但随着饭店业务量变大,成立一个采购部,由采购部专职于买业务,称为厨房和菜市场之间的桥梁。
与业务无关的服务(功能更通用的服务)
1. 数据库
2. 缓存
3. 消息队列
......
评价指标(Metric)
可用性(Availability)
考察单位时间段内,系统可以正常提供的服务的概率/期望。例如:年化系统可用性 = 系统正常提供服务时长/一年总时长。这里暗含着一个指标,即如何评价系统提供无法是否正常,我们就不深入了。平时我们常说的4个9即系统可以提供 99.99% 的可用性,5个9是99.999%的可用性,依次类推。我们平时只是用高可用(High Availability)这个非量化目标简要表达我们系统的追求。
响应时长(Response Time RT)
指用户完成输入到系统给出用户反应的时长。例如点外卖业务的响应时长 = 拿到外卖的时刻 - 完成点单的时刻。通常我们需要衡量的是最长响应时长、平均响应时长和中位数响应时长。这个指标原则上是越小越好,但很多情况下由于实现的规则,需要根据情况具体判断
吞吐(Throughput)VS 并发(Concurrent)
吞吐考察单位时间段内,系统可以成功处理的请求和数量。并发指系同一时刻支持的请求最高量。例如一条车道高速公路,一分钟可以通过20辆车,则并发是2,一分钟的吞吐量是20。实践中,并发量往往无法直接获取,很多时候都是用极短的时间段(比如1秒)的吞吐量做代替。我们平时用高并发(Hight Concurrnet)这个非量化目标简要表达系统的追求。
架构演进
单机架构
初期,我们需要利用我们精干的技术团队,快速将业务系统投入市场进行检验,并且可以迅速响应变化要求。但好在前期用户访问量很少,没有对我们的性能、安全等提出很高的要求,而且系统架构简单,无需专业团队,所以选择单机架构是合适的。
千万不要小瞧这个东西,绝大部分的公司的产品,都是这种单机架构!!!
现在计算机硬件,发展速度非常之快,哪怕只有一台主机,这一台主机的新能也是很高的。可以支持非常高的并发 & 非常大的数据存储。
如果业务进一步增长,用户量和数据量都水涨船高,一台主机难以应付的时候,就需要引入更多的主机。引入更多的硬件资源~~
一台主机的硬件资源是有上限的!!
包括不限于以下几种:
1. CPU 2. 内存 3. 硬盘 4. 网络.....
服务器每次收到一个请求,都是需要消耗上述的一些资源的,如果同时时刻,处理的请求多了,此时就可能会导致某个硬件资源不够用了!!无论是哪个方面不够用了,都可能会导致服务器处理请求的时间变长,甚至于出错~
如果我们遇到了这样的服务器不够用的场景,怎么处理?
1. 开源 简单粗暴,增加更多的硬件资源(但一个主机上面增加的硬件资源也是有限的,取决于主板的扩展能力)所以一台主机扩展到了极限了,但还是不够,就只能引入更多台主机了!不是说买新的机器买来就直接可以解决问题了,也需要软件上做出对应的调整和适配~
一旦引入多台主机,咱们系统就可以直接称为是 “分布式系统”
注意:引入分布式,这是万不得已,无奈之举,系统的复杂程度会大大提高~
2. 节流 软件上优化(各凭本事,需要通过新能测试,找到是哪个环节出现了瓶颈,再去对症下药)难!!!对于程序猿的水平要求比较高!
相关软件
Web 服务器软件:Tomocat、Netty、Nginx、Apache等
数据库相关软件:MySQL、Oracle、PostgreSQL、SQL Server等
应用数据分离框架
随着系统的上线,我们不出意外地获得了成功。市场上出现了一批忠实于我们的用户,使得系统的访问量逐步上升,逐渐逼近了硬件资源的极限,同时团队也在此期间积累了对业务流程的一批经验。面对当前的性能压力,我们需要未雨绸缪去进行系统重构、架构挑战,以提升系统的承载能力。但由于预算仍然紧张,我们选择了将应用和数据分离的做法,可以最小代价的提升系统的承载能力。
和之前架构的主要区别在于将数据库服务独立部署在同一个数据中心的其他服务器上,应用服务通过网络访问数据。
应用服务集群架构
我们的系统受到了用户的欢迎,并且出现了爆款,单台应用服务器已经无法满足需求了。我们的单机应用服务器首先遇到了瓶颈,摆在我们技术团队面前有两种方案,大家针对方案的优劣展开了热烈的讨论:
垂直扩展/纵向扩展 Scale UP。通过购买性能更优、价格更高的应用服务器来应对更多的流量。这种方案的优势在于完全不需要对系统软件做任何的调整;但劣势也很明显:硬件性能和价格的正常关系是非线性的,意味着选择性能2倍的硬件可能需要花费超过4倍的价格,其次硬件提升是有明显上限的。
水平扩展/横向扩展 Scale Out。通过调整软件架构,增加应用层硬件,将用户流量分担到不同的应用层服务器上,来提升系统的承载能力。这种方案的优势在于成本相对较低,并且提升的上限空间也很大。但劣势是带给系统更多的复杂性,需要技术团队有更多的经验。
经过团队的学习、调研和讨论,最终选择了水平扩展方案,来解决改问题,但这需要引入一个新的组件 —— 负载均衡:为了解决用户流量向哪台应用服务器分发的问题,需要一个专门的系统组件做流量分发。实际中负载均衡不仅仅指的是工作在应用层的,甚至可能是其他的网络层之中。同时流量调度算法也有很多,这里简单介绍几种较为常见的:
1. Round-Robin 轮询算法。即非常公平地将请求依次分给不同的应用服务器。
2. Weight-Round-Robin 轮询算法。为不同的服务器(比如性能不同)赋予不同的权重(Weight),能者多劳。
3. 一致哈希散列算法。通过计算用户的特征值(比如 IP 地址)得到哈希值,根据哈希结果做分发,优点是确保来自相同用户的请求总是被分发给指定服务器。也就是我们平时遇到的专项客户经理服务。
相关软件:负载均衡软件:Nginx、HAProxy、LVS、F5等
应用服务器可能会比较吃 CPU 和内存
如果把 CPU 或者内存吃没了,此时应用服务器就顶不住了,引入更多的应用服务器,就可以有效解决上述问题~
注意:这上面的图上起来是两个,实际上可能是多个,用户的请求会先到达负载均衡/网关服务器,假设有1W个用户请求,有2个应用服务器,承担5K访问量
读写分离/主从分离架构
上面提到,我们把用户请求通过负载均衡分发到不同的应用服务器之后,可以并行处理了,并且随着业务增长,可以动态扩展服务器的数量,到一定程度之后,数据的压力称为系统承载能力的瓶颈点。我们可以像扩展应用服务器一样扩展数据库服务器吗?答案是否定的,因为数据库服务有其特殊性:如果将数据分散到各个服务器之后,数据的一致性将无法得到保障。所谓数据的一致性,此是指:针对一个系统,无论何时何地,我们都应该看到一个始终维持统一的数据。想象一下,银行管理的账户金额,如果收到一笔转账之后,一份数据库的数据修改了,但另外的数据库没有修改,则用户得到的金额将是错误的。
我们采用的解决办法是这样的,保留一个主要的数据库作为写入数据库,其他的数据库作为从属数据库。从属数据库的所有数据全部来自主库的数据,经过同步后,从库可以维护着与主库一致的数据。然后为了分担数据库的压力,我们可以将写数据全部交给主库处理,但读请求分散到各个从库中。由于大部分的系统中,读写请求都是不成比例的,例如 100 次读 1 次写,所以只要将读请求由各个从库分担后,数据库的压力就没有那么大了。当然这个过程不是没有代价的,主库到从库的数据同步其实是有时间成本的,但这个问题我们暂时不做进一步探讨。
应用中需要对读写请求做分离处理,所以可以利用一些数据库中间件,将请求分离的职责托管出去。
相关软件:MyCat、TDDL、Amoeba、Cobar等类似数据库中间件等。
引入缓存 —— 冷热分离架构
随着访问量继续增加,发现业务中一些数据的读取频率远大于其他数据的读取频率。我们把这部分数据称为热点数据,与之相对应的是冷数据。针对热数据,为了提升其读取的响应时间,可以增加本地缓存,并在外部增加分布式缓存,缓存热门商品的 html 页面等。通过缓存能把绝大多数请求在读写数据库前拦截掉,大大降低数据库压力。其中涉及的技术包括:使用 memcached 作为本地缓存,使用 Redis 作为分布式缓存,还会涉及一致性、缓存穿透/击穿、缓存雪崩、热点数据集中失效问题。
相关软件:Memcached、Redis 等缓存软件。
垂直分库
随着业务的数据量增大,大量的数据存储在同一个库中已经显得有些力不从心了,所以可以按照业务,将数据分别存储。比如针对评论数据,可按照商品 ID 进行 hash,路由到对应的表中存储;针对支付记录,可按照小时创建表,每个小时表继续拆分为小表,使用用户 ID 或记录编号来路由数据。只要实时操作的表数据量足够小,请求能够均匀的分发到多台服务器上的小表,那数据库就能通过水平扩展的方式来提高性能。其中前面提到的 MyCat 也支持在大表拆分为小表情况下的访问控制。这种做法显著增加了数据库运维的难度,对 DBA 要求较高。数据库涉及到这种结构时,已经可以称为分布式数据库,但是这只是一个逻辑的数据库整体,数据库里面的不同组成部分是由不同的组件单独来实现的,如分库分表的管理和请求分发,由 MyCat 实现,SQL 的解析由单机的数据库实现,读写分离可能由网关和消息队列来实现,查询结果的汇总可能由数据库接口层来实现等等,这种架构其实就是 MPP (大规模并行处理)架构的一类实现。
相关软件:Greenplum、TiDB、Postgresql XC、HAWQ等,商用的如南大通用的 GBase、睿帆科技的雪球DB、华为的LibrA等。
业务拆分 —— 微服务
随着人员增加,业务发展,我们将业务分给不同的开发团队去维护,每个团队独立实现自己的微服务,然后互相之间对数据的直接访问进行隔离,可以利用 Gateway、消息队列等技术,实现相互之间的调用关系。甚至可以把一些类似用户管理、安全管理、数据采集等业务提成公共服务。
至此,一个还算合理的高可用、高并发系统的基本雏形已显。注意,以上所说的架构演变顺序只是针对某个侧面进行单独的改进,在实际场景中,可能同一时间会有几个问题需要解决,或者可能到达瓶颈的是另外的方面,这时候就应该按照实际问题实际解决。如在政府类的并发量可能不大,但业务可能很丰富的场景,高并发就不是重点解决的问题,此时优先需要的可能会是丰富需求的解决方案。
对于单次实施并且性能指标明确的系统,架构设计能够支持系统的性能指标要求就足够了,但要留有扩展架构的接口以便不备之需。对于不断发展的系统,如电商平台,应设计到能满足下一阶段用户量和性能指标要求的程度,并根据业务的增长不断地迭代升级架构,以支持更高地并发和丰富的业务。
所谓的 “大数据” 其实就是海量数据采集清洗转换、数据存储、数据分析、数据服务等场景解决方案的一个统称,在每一个场景后包含了多种可选的技术,如数据采集有 Flume、Sqoop、Kettle等,数据存储有分布式文件系统HDFS、FastDFS、NoSQL 数据库HBase、MongoDB等,数据分析有 Spark 技术栈,机器学习算法等。总的来说大数据架构就是根据业务的需求,整合各种大数据组件组合而成的架构,一般会提供分布式存储、分布式计算、多维分析、数据仓库、机器学习算法等能力。而服务端架构更多指的是应用组织层面的架构,底层能力往往是由大数据架构来提供的。
分布式系统总结
1. 单机架构(应用程序 + 数据库服务器)
2. 数据库和应用分离 应用程序和数据服务器分别放到不同主机上部署了
3. 引入负载均衡:应用服务器 => 集群 通过负载均衡器,把请求比较均匀的分发给集群中的每个应用服务器。
4. 引入读写分离,数据库主从结构
一个数据库节点作为主节点,其他 N 个数据库节点作为从节点。
主节点负责 写 数据,从节点负责 读 数据(主节点需要把修改过的数据同步给从节点)
5. 引入缓存,冷热数据分离
进一步的提升了服务器针对请求的处理能力。
二八原则,Redis 在一个分布式系统中,通常就扮演着缓存这样的角色~(引入的问题:数据库和缓存的数据一致性问题)~
6. 引入分库分表,数据库能进一步扩展空间
7. 引入微服务,从业务上进一步拆分应用服务器
从业务功能的角度,把应用服务器,拆分成更多的功能更单一,更简单,更小的服务器。
上述这样的一个演化的步骤,只是一个粗略的过程。
实际上一个商业项目,真实的演化过程,都是和其他的业务发展密切相关的,业务是更重要的,技术只是给业务提供支持的。
最后,所谓的分布式系统就是想办法引入更多的硬件资源!