互联网网站服务端并发分布式架构演进之路

背景

本文以某个电商为原型,介绍从一百个到千万级并发情况下服务端的架构的演进过程,同时列举出每个演进阶段会遇到的相关技术,让大家对架构的演进有一个整体的认知,文章最后汇总了一些架构设计的原则。

基本概念

在介绍架构演进之前,为了避免部分读者对当互联网架构设计中的一些概念不了解,下面对几个最基础的概念进行介绍:

分布式:系统中多个模块在不同的服务器上部署,即称知为分布式系统。如Tomcat 服务器和数据库分别部署在不同的服务器上。

高可用:当一个分布式系统中,部分节点失效的时候,其他节点能够接替他继续提供服务,则可认为系统具有高可用性。

集群:指的是一个特定的领域的软件部署在多台服务器上并作为一个整体对外提供服务,这个整体称之为集群。如在分布式协调组 件Zookeeper中的Master和Slaver分别部署在多台服务器上,共同组成一个整体,共同对外提供服务。在常见的集群中,客户端往能够连接任意一个节点获得服务,并当集群中一个节点出现掉线时,其他节点能够自动的接替他,这时候说明集群具有高可用性。

负载均衡:当请求发送到系统的时候,通过某些方式将请求均匀的分发到不同的节点上,使得集群中各个不同的节点能够均匀处理请求负载,则可以认为系统是负载均衡的。

正向代理和反向代理:系统内部要访问外部网络时,统一通过一个代理服务器把请求转发出去,在外部网络看来就是代理服务器发起的访问,此时代理服务器实现的是正向代理;当外部请求进入系统时,代理服务器把该请求转发到系统中的某台服务器上,对外部请求来说,与之交互的只有代理服务器,此时代理服务器实现的是反向代理。简单来说,正向代理是代理服务器代替系统内部来访问外部网络的过程,反向代理是外部请求访问系统时通过代理服务器转发到内部服务器的过程。

架构演进

互联网架构不是一蹴而就的,而是随着互联网业务的不断增长而进化的长期进化的结果。

单机架构

以myShop电商网站作为例子。在网站最初时,应用数量与用户数都较少,可以把Tomcat和数据库部署在同一台服务器上。浏览器往www.myShop.com发起请求时,首先经过DNS服务器(域名系统)把域名转换为实际IP地址10.23.123.34,浏览器转而访问该IP对应的Tomcat。

随着用户数的增长,Tomcat和数据库之间竞争资源,单机性能不足以支撑业务。于是,需要对架构进行升级。

单体架构

第一次演进

Tomcat与数据库分离,如下图所示:

第一次演进架构

Tomcat与数据库分别部署在不同的服务器上,可以显著提升两者各自性能。

随着用户的不断增长,并发读写数据库逐步成为瓶颈。

第二次演进

引入本地缓存及分布式缓存。

第二次架构演进

在Tomcat同服务器上或同JVM中增加本地缓存,并在外部增加分布式缓存,缓存热门商品信息或热门商品的html页面等。通过缓存能把绝大多数请求在读写数据库前拦截掉,大大降低数据库压力。其中涉及的技术包括:使用memcached作为本地缓存,使用Redis作为分布式缓存,还会涉及缓存一致性、缓存穿透/击穿、缓存雪崩、热点数据集中失效等问题。

缓存抗住了大部分的访问请求,随着用户数的增长,并发压力主要落在单机的Tomcat上,响应逐渐变慢。

第三次演进

引入反向代理负载均衡。

第三次架构演进

在多台服务器上分别部署Tomcat,使用反向代理软件(Nginx)把请求均匀分发到每个Tomcat中。此处假设Tomcat最多支持100个并发,Nginx最多支持50000个并发,那么理论上Nginx把请求分发到500个Tomcat上,就能抗住50000个并发。其中涉及的技术包括:Nginx、HAProxy,两者都是工作在网络第七层的反向代理软件,主要支持http协议,还会涉及session共享、文件上传下载的问题。

反向代理使应用服务器可支持的并发量大大增加,但并发量的增长也意味着更多请求穿透到数据库,单机的数据库最终成为瓶颈。

第四次演进

随着客户请求量不断增大,数据库逐渐成功整个系统瓶颈,这样的话,就需要对数据库进行读写分离了。

第四次架构演进

把数据库划分为读库和写库,读库可以有多个,通过同步机制把写库的数据同步到读库,对于需要查询最新写入数据场景,可通过在缓存中多写一份,通过缓存获得最新数据。其中涉及的技术包括:MyCAT,它是数据库中间件,可通过它来组织数据库的分离读写和分库分表,客户端通过它来访问下层数据库,还会涉及数据同步,数据一致性的问题。

业务逐渐变多,不同业务之间的访问量差距较大,不同业务直接竞争数据库,相互影响性能。

第五次演进

数据按照业务进行分库。

第五次架构演进

把不同业务的数据保存到不同的数据库中,使业务之间的资源竞争降低,对于访问量大的业务,可以部署更多的服务器来支撑。这样同时导致跨业务的表无法直接做关联分析,需要通过其他途径来解决,但这不是本文讨论的重点,有兴趣的可以自行搜索解决方案。随着用户数的增长,单机的写库会逐渐会达到性能瓶颈。

第六次演进

将大表拆分成小表,比如针对评论数据,可按照商品ID进行hash,路由到对应的表中存储;针对支付记录,可按照小时创建表,每个小时表继续拆分为小表,使用用户ID或记录编号来路由数据。只要实时操作的表数据量足够小,请求能够足够均匀的分发到多台服务器上的小表,那数据库就能通过水平扩展的方式来提高性能。其中前面提到的Mycat也支持在大表拆分为小表情况下的访问控制。这种做法显著的增加了数据库运维的难度,对DBA的要求较高。数据库设计到这种结构时,已经可以称为分布式数据库,但是这只是一个逻辑的数据库整体,数据库里不同的组成部分是由不同的组件单独来实现的,如分库分表的管理和请求分发,由MyCAT实现,SQL的解析由单机的数据库实现,读写分离可能由网关和消息队列来实现,查询结果的汇总可能由数据库接口层来实现等等,这种架构其实是MPP(大规模并行处理)架构的一类实现。

第七次演进

数据库和Tomcat都能实现动态的水平扩展,可以支撑的并发大幅提升,随着用户的增长,最终单击Nginx会逐步成为瓶颈。所有需要使用LVS或者F5来使用多个Nginx负载均衡。

第七次架构演进

由于瓶颈在Nginx,因此无法通过两层的Nginx来实现多个Nginx的负载均衡。图中的LVS和F5是工作在网络第四层的负载均衡解决方案,其中LVS是软件,运行在操作系统内核态,可对TCP请求或更高层级的网络协议进行转发,因此支持的协议更丰富,并且性能也远高于Nginx,可假设单机的LVS可支持几十万个并发的请求转发;F5是一种负载均衡硬件,与LVS提供的能力类似,性能比LVS更高,但价格昂贵。此处需要注意的是,上图中从Nginx层到Tomcat层这样画并不代表全部Nginx都转发请求到全部的Tomcat,在实际使用时,可能会是几个Nginx下面接一部分的Tomcat,这些Nginx之间通过keepalived实现高可用,其他的Nginx接另外的Tomcat,这样可接入的Tomcat数量就能成倍的增加。

第八次演进

引入NoSQL数据库和搜索引擎等技术。

当数据库中的数据多到一定规模时,数据库就不适用于复杂的查询了,往往只能满足普通查询的场景。对于统计报表场景,在数据量大时不一定能跑出结果,而且在跑复杂查询时会导致其他查询变慢,对于全文检索、可变数据结构等场景,数据库天生不适用。因此需要针对特定的场景,引入合适的解决方案。如对于海量文件存储,可通过分布式文件系统HDFS解决,对于key value类型的数据,可通过HBase和Redis等方案解决,对于全文检索场景,可通过搜索引擎如ElasticSearch解决,对于多维分析场景,可通过Kylin或Druid等方案解决

当然,引入更多组件同时会提高系统的复杂度,不同的组件保存的数据需要同步,需要考虑一致性的问题,需要有更多的运维手段来管理这些组件等。引入更多组件解决了丰富的需求,业务维度能够极大扩充,随之而来的是一个应用中包含了太多的业务代码,业务的升级迭代变得困难。

第九次演进

虽然通过前面几次架构演进,我们对Nginx、数据库、缓存、进行了扩展集群化部署,但是此时我们的应用还是单体架构,所有的业务逻辑越来越庞大复杂,这样影响到整个应用的迭代,开发部署以及正常的维护。

按照业务板块来划分应用代码,使单个应用的职责更清晰,相互之间可以做到独立升级迭代。这时候应用之间可能会涉及到一些公共配置,可以通过分布式配置中心ZooKeeper来解决

将原来单个应用拆分成多个应用

但是这样也会存在一个问题:不同应用之间存在共用的模块,由应用单独管理会导致相同代码存在多份,导致公共功能升级时全部应用代码都要跟着升级。所以需要将公共的代码或者模块抽成单独的服务,这样的话,我们就需要对应用进行服务号改造。

第十次演进

复用的功能抽离成微服务。

分布式微服务架构

如用户管理、订单、支付、鉴权等功能在多个应用中都存在,那么可以把这些功能的代码单独抽取出来形成一个单独的服务来管理,这样的服务就是所谓的微服务,应用和服务之间通过HTTP、TCP或RPC请求等多种方式来访问公共服务,每个单独的服务都可以由单独的团队来管理。此外,可以通过Dubbo、Spring Cloud等框架实现服务治理、限流、熔断、降级等功能,提高服务的稳定性和可用性。

腾讯云TSF针对 微服务 开发、部署、治理、运维 4 个阶段提供全方位解决方案,可以快速构建微服务。

第十一次演进

引入容器化技术实现运行环境隔离与动态服务管理。

目前最流行的容器化技术是Docker,最流行的容器管理服务是Kubernetes(K8S),应用/服务可以打包为Docker镜像,通过Kubernetes来动态分发和部署镜像。Docker镜像可理解为一个能运行你的应用/服务的最小的操作系统,里面放着应用/服务的运行代码,运行环境根据实际的需要设置好。把整个“操作系统”打包为一个镜像后,就可以分发到需要部署相关服务的机器上,直接启动Docker镜像就可以把服务起起来,使服务的部署和运维变得简单。

第十二次演进

系统可部署到公有云上,利用公有云的海量机器资源,解决动态硬件资源的问题,在大促的时间段里,在云平台中临时申请更多的资源,结合Docker和K8S来快速部署服务,在大促结束后释放资源,真正做到按需付费,资源利用率大大提高,同时大大降低了运维成本。

而这次使用TSF可以比较完美的解决,TSF支持云虚拟机、容器、Serverless三种方式部署应用。是一个围绕着应用和微服务的 PaaS 平台,提供应用全生命周期管理、数据化运营、立体化监控和服务治理等功能。

至此,以上所提到的从高并发访问问题,到服务的架构和系统实施的层面都有了各自的解决方案,但同时也应该意识到,在上面的介绍中,其实是有意忽略了诸如跨机房数据同步、分布式事务实现等等的实际问题,这些问题以后有机会再拿出来单独讨论。