最近西安一码通的故障引起了业界广泛的讨论,究其根本原因还是系统未充分考虑到扩展性,在面临超过日常访问数倍甚至十倍以上的突发流量时某个环节达到了瓶颈点,并且系统不能做到自动扩缩容,最终导致了故障。
而之前各个网站频繁崩溃登上微博热搜,也是在应对突发流量方面做的不是很好,一方面是因为系统的冗余度评估不足,流量超出了系统的最大承载能力;另一方面是因为系统不能做到自动扩缩容,在流量超过系统最大承载时需要人工介入,响应速度和故障恢复速度都远远比不上自动扩缩容。
1
通用百万级 DAU 用户系统架构设计
在阐述千万级 DAU 系统的架构设计之前,我们首先来看一个通用的百万级 DAU 互联网应用架构的设计。如下图所示,一次用户访问请求通常要经过以下几层:
1.1
DNS
DNS 最主要的作用是根据用户的 IP 地址,决定把请求解析到哪个地域的 IDC,一般大型互联网公司往往不止一个 IDC,为了访问速度考虑,北方用户多访问联通机房,南方用户多访问电信机房。正常情况下每个用户请求的 DNS 解析是固定的,并且在客户端留有缓存,如果以前访问联通机房就会一直访问联通机房,以前访问电信机房也会一直访问电信机房。
1.2
四层负载均衡
流量经过 DNS 解析后第一个要访问的就是四层负载均衡了。四层负载均衡主要起到流量转发作用,比如根据请求的域名决定流量转发到后端哪个七层网关集群。四层负载均衡设备有软负载也有硬负载,最典型的软负载是 LVS,单台承载能力通常是十万级 QPS 以上。而硬负载如 F5,单台承载能力达到百万级 QPS,不过成本也较高,单台机器成本几十万以上,所以一般互联网公司四层负载均衡设备主要采用软负载 LVS。
1.3
七层负载均衡
七层负载均衡一般又叫做网关,一般互联网公司通过 Nginx 搭建自己的网关集群,当流量经过四层负载均衡转发后,就到了具体某个七层网关集群了。为什么有了四层负载均衡后还要有七层负载均衡呢?主要有以下两方面的考虑:
应用层的转发
对于大型互联网公司应用来说,通常业务包括几十个甚至上百个域名,不仅要针对域名来做四层流量的负载均衡,还要针对统一域名下不同的接口来做七层流量的负载均衡,比如上下行接口的拆分,核心和非核心接口的拆分等。
集成鉴权、日志、监控等通用功能
除此之外,一些通常的接口功能需求,如接口调用鉴权、日志统计、耗时监控等等,适合放在网关层统一进行处理,而不需要每个接口都实现这些功能。
1.4
服务端
流量经过网关转发后,就可以访问某台具体 IP 的服务器了,实际的应用程序就部署在服务器上。服务端的程序部署一般有两种架构:
单体应用
单体应用就是所有的业务的代码开发、合并、打包、部署都集成在一起,通常适合于比较简单的业务或者团队规模比较小的团队。
微服务拆分
当应用的接口上百个或者是团队规模超过十人时,代码开发、合并、打包、部署在一起就会引起开发效率的降低,这个时候就适合进行微服务拆分了,把相同领域的接口拆分到一个应用,独立进行代码开发、合并、打包和部署,更加灵活。
1.5
缓存
业务流量到达服务端后,服务端通常需要请求数据库,再组装处理后返回。一般情况下数据库的延迟在十毫秒以上,为了提高访问速度,可以把经常访问的数据放到缓存中,当前用的最多的如 memcached、redis 等,单机的承载能力都是十万级别,并且延迟只有 1-2 毫秒。
1.6
数据库
一般情况下用户请求的数据大部分都被缓存住,但缓存的命中率不可能达到 100%,穿透过来的请求还是要访问数据库。为了高可用数据库层面一般要做到以下几个层面:
主从分离
一般互联网应用来说,都是读多写少,为此可以将读写分离,写请求访问主库,读请求访问从库,根据读请求的量来决定从库的数量。
分库分表
一般单台服务器的磁盘容量通常在 T 级别,而大型互联网应用的数据总量一般在百 T 甚至千 T 级别,显然单机无法承载,因此要对数据库进行分库。另一方面单表查询的性能会随着容量增加而逐渐衰减,一般情况下单表容量要控制在千万行级别,因此也需要对数据库进行分表。分库分表一般有两个维度,一个是时间维度,不同时间的数据存储在不同的表上,这样单张表的容量就有限了;一个是访问维度,比如以用户 ID 为维度,按照用户 ID 进行 hash,把不同用户 ID 的访问分散到不同的数据库端口上。
基本按照以上架构支撑百万级 DAU 的用户访问通常是没问题的,但对于千万级甚至亿级以上 DAU 的系统来说, 只有在各层都支持自动扩缩容并配合快速降级等手段,才能在面对突发峰值流量时不至于崩溃。笔者结合过去十年在一线互联网公司的实际经历,总结了千万级 DAU 以上系统架构需要做出的改进。
2
混合云架构
当用户的请求经过 DNS 解析后,就决定了要访问哪个机房。首先要求机房出口总带宽要足够大,这是用户流量的入口,如果带宽打满用户请求就失败了。一般情况下,机房出口带宽是固定的,不能实时扩容,所以要预留足够的冗余。但实际情况下,各公司机房出口带宽不可能无限扩容。为此可以利用公有云来解决机房出口带宽有限的问题,可以将业务部署在公有云机房,日常情况下流量主要走私有机房,当流量上涨超出私有机房出口带宽的承载值时,可以把一部分流量切换到公有云上,这就要求系统能支持混合云架构。
混合云架构要求业务不仅要能部署在私有云机房,还要能部署在公有云。首先要求私有云和公有云机房之间的网络互通,对于千万级 DAU 以上的系统来说,通常需要专线才能保证带宽和稳定性需求。其次要考虑哪些层部署在公有云上,一个可以参考的架构如下图所示。
以上混合云架构要求业务在私有云和公有云上都能够部署服务,这对业务来说是一个比较大的挑战,需要有相应的混合云平台支撑,不仅要向业务屏蔽公有云和私有云的资源差异,还要能支持业务同时部署在公有云和私有云上。一个比较好的解决方案是企业在现有的运维基础设施上集成开源算力引擎 BridgX(https://github.com/galaxy-future/bridgx/),它支持从不同的公有云上获取资源,并管理私有云和公有云的机器,通过 Kubernetes 切割并以固定 IP 形式输出等功能,只需要很小的开发成本即可支持混合云部署。
3
全链路弹性扩容
当用户流量访问超过现有机房的承载能力时,可以把一部分流量切换到公有云上,这时候就要求公有云上部署的四七层、服务端、缓存和数据库都要能支撑流量。
3.1
四七层
云上四层单个 SLB 能承载的并发连接数可达百万级,因此在四层预留多几台 SLB 即可支撑几百万连,因为 SLB 是按照流量收费的, 日常没有流量成本也很低。而七层可以根据流量来实时弹性扩容,以 Nginx 为例,单台 Nginx 的承载能力通常在十万级,可以根据实际的 QPS 来决定需要 Nginx 的数量,弹性扩容后实时挂载到 SLB 上。
3.2
服务端
通常服务端是无状态的,可以根据流量实时弹性来应对。但这一层的弹性扩容不能简单的根据 QPS 来判定,还需要考虑耗时、慢速比等因素。一个通用的解决方案是根据接口的耗时分布对 QSP 加权,拟合服务端实际的压力值,再通过压测获取服务端最大承载能力,那么最大承载能力除以实际压力值就是服务端的实时冗余度,最后划定最高冗余度和最低冗余度两条线,即可自动扩缩容。目前有一个比较好的开源解决方案 CudgX(https://github.com/galaxy-future/cudgx),它可以通过各类服务的多维度、大规模的日志数据采集以及机器学习训练分析,对服务进行数字化、指标化度量,从而使业务能够做更加精准的服务自动扩缩容 ( https://github.com/galaxy-future/cudgx),它可以通过各类服务的多维度、大规模的日志数据采集以及机器学习训练分析,对服务进行数字化、指标化度量,从而使业务能够做更加精准的服务自动扩缩容 )。
3.3
缓存和数据库
为了应对千万级 DAU 以上的系统访问,缓存也要支持扩容。但由于缓存加载热数据需要时间,往往以天为单位,如果是业务流量上涨比较快,这时候靠弹性扩容往往难以应对,最好预留足够的冗余度。与缓存扩容类似,数据库的弹性扩容也需要较长时间,因为涉及到数据同步,以 MySQL 的扩容为例,若一个端口的数据量在 T 级别的话,数据同步往往在小时级以上。如果是流量上涨比较快的业务,则数据库层面也要保持充足的冗余度。
4
三级降级机制
为了保障千万 DAU 级的业务,业务除了要支持全链路弹性扩容以外,还要能够支持降级。降级一般是主动牺牲某些系统功能和用户体验,为了能够快速释放系统冗余度的自保措施。为了最大限度的降低降级带来的影响,通常降级要分为三级:
- 一级降级是用户感知不到的降级,降级能释放的冗余度往往有限,通常在 30%以下;
- 二级降级是用户可以感知到的降级,降级也能释放一定的冗余度,通常在 50%以下;
- 三级降级是比较严重的降级,对用户体验的影响比较严重,释放的冗余度可达到 50%-100%,不到最后时刻一般不会采用。
实际在保障千万级 DAU 的系统时,除了要做到混合云架构、全链路弹性扩容、三级降级机制以外,还需要有各种各样的配套机制,比如决策支持系统、值班报警机制等。