一、高并发的说明和背景
高并发解决的核心问题是在同一时间上有大量的请求过来,然后我们的系统要怎么抗住这些请求带来的压力。比如在线直播服务,同时有上百万甚至上千万人观看。比如秒杀品,同时有大量用户涌入。
高并发是从业务角度去描述系统的能力,实现高并发的手段可以采用分布式,也可以采用缓存等,当然也包括多线程、协程,但远远不仅如此;高并发的基本表现为单位时间内系统能够同时处理的请求数,高并发的核心是对资源的有效压榨,有限的资源应对大量的请求。
现代互联网服务,基本上都要考虑高并发问题,因为一般的产品,用户的请求量都很大。
二、高并发架构设计经验
高并发架构设计,需要从三大层来建设和分析
- • 基础设施层:这个是最基础的依赖,主要是一些服务的部署。针对大公司而言,一般都有成熟的系统和基础设施来承载,可能针对业务开发的同学来看,平时关注的的细节并不深入,但是不妨碍我们从全局角度来剖析高并发的设计。
- • 服务端架构层:这个是我们重点要关注的架构设计,架构设计不合理,就很难抗住高并发,主要包括各种架构和模块的设计。
- • 服务应用层:这个主要是针对我们写的代码来进行优化改进。从代码架构、代码性能等方便去抗并发。
2-1、基础设施层
部署:多 IDC + 异地多活
基础设施层一般包含了服务器、IDC、部署方式等等。目前而言,我们一般都用容器部署的方式,而容器的底层能力基本也都是建立在 k8s 容器层之上的,服务本身的部署管理已经有成熟的设施帮我们实现了。具体到部署方式,包括但不限于:
- • 多 IDC 部署。比如服务同时在广州、上海两地部署。这个依赖我们的服务是无状态的
- • 其他的参考下 异地多活架构等相关部署。
监控:可观测性
系统的可观测性主要包含三个部分: logging、tracing、metrics。这个一般都要引入可观测系统,这样才能帮助我们在异常的时候能够快速定位问题。属于必备设施,一般而言,公司应该都有专门的团队去做这个事情。
2-2、服务端架构层
服务端架构层是我们重点要关注的,这个也是考量个人架构能力的最关键部分。
系统分层设计:分层、分割、分布式
- • 架构分层
- • 应用层:网站首页,用户中心,商品中心,购物车,红包业务,活动中心等,负责具体业务和视图展示
- • 服务层:订单服务,用户管理服务,红包服务,商品服务等,为应用层提供服务支持
- • 数据层:关系数据库,nosql数据库 等,提供数据存储查询服务
- • 将系统在横向维度上切分成几个部分,每一层的功能职责要足够单一,然后通过上层对下层的依赖和调度组成一个完整的系统
- • 比如把电商系统分成:应用层,服务层,数据层。(具体分多少个层次根据自己的业务场景)
- • 业务分割
- • 在纵向方面对业务进行切分,将一块相对复杂的业务分割成不同的模块单元,对应的是模块的划分,通过合理的模块划分,使得每个模块都能可以满足 高内聚低耦合 的设计要求,这样不同的模块可以分布式部署,也能提高并发处理能力和功能扩展
- • 比如用户中心可以分割成:账户信息模块,订单列表模块,充值模块,优惠券模块等
- • 分布式
- • 分布式应用和服务,将分层或者分割后的业务分布式部署,独立的应用服务器,数据库,缓存服务器,当业务达到一定用户量的时候,再进行服务器均衡负载,数据库,缓存主从集群
集群架构设计:应用集群、数据集群
应对高并发系统,不管是应用层面还是数据层面,单机都不可能搞定,因此都需要搭建集群架构,然后通过负载均衡来对外提供服务。同时集群架构还能保证系统的可用性,当某台服务或者机器异常,负载均衡会自动剔除,不会影响对外服务。
- • 应用服务器集群
- • nginx 反向代理
- • slb
- • LVS …
- • 数据集群(关系/nosql数据库)
- • 主从分离,一主多从
- • 数据读写分离
数据库设计:读写分离+分库分表+冷热分离
应对高并发,数据的存储,首先就要做好预估,先进行分库分表 和 读写分离,最后可以根据情况来看是否冷热分离:
- • 读写分离。互联网系统大多数都是读多写少,因此读写分离可以帮助主库抗量。一般我们都是一主多从的架构,可以抗量,也可以保证数据不丢。分库分表只能解决 QPS 高,但是无法解决 TPS 高,比如写入的量足够大的话(TPS 高),就得读写分离。
- • 分库分表。数据存储量大的时候,就需要通过分库分表来存储。先分,避免后期要拆,后期拆的话,就面临洗数据的问题,就需要采用双写模式来搞定。
- • 分库分表模式虽然能显著提升数据库的容量,但会增加系统复杂性,而且由于只能支持少数的几个维度读写,从某种意义上来说对业务系统也是一种限制,因此在设计分库分表方案的时候需要结合具体业务场景,更全面的考虑。
- • 冷热分离。针对业务场景而言,如果数据有冷热之分的话,可以将历史冷数据与当前热数据分开存储,这样可以减轻当前热数据的存储量,可以提高性能。
不过,既然是高并发系统,不能应用层直接读写 DB 的,一定有一个缓存在上面,如果直接读写 DB 能够搞定,其实不能叫高并发了,只能说是并发有点高。在非互联网系统里面还是可以的。
缓存设计:多级缓存架构和本地缓存
缓存的最大作用是可以提升系统性能,保护后端存储不被大流量打垮,增加系统的伸缩性。缓存的设计,需要分多个思路并行
- • 首先要考虑的,就是必须在数据库之上,增加一层分布式缓存,比如 Redis 或者 Memcached。
- • 这里需要考虑一下缓存和数据库一致性的问题。
- • 其次需要考虑的是多级缓存架构。分几级缓存设计,同时设计热点缓存架构。
- • 在分布式缓存之上,还可以加一个本地缓存,来缓存最热的数据
- • 采用多个分布式缓存来搭建多级缓存。
消息队列设计:MQ 抗量和削峰
针对流量突峰,仅仅有缓存来抗量可能还不够,还需要使用消息队列来削峰。使用消息队列后,可以将同步处理的请求改为 通过消费 MQ 消息来异步消费,这样可以大大减少系统处理的压力,增加系统的并发量。常用的消息队列比如 kafka。
服务治理设计:超时、熔断、降级、限流等
超时、熔断、降级、限流等都是常规策略,可以在另外服务治理章节去细看。
资源隔离设计:SET 部署
资源隔离有各种类型,物理层面的服务器资源、中间件资源,代码层面的线程池、连接池,这些都可以做隔离。
一般我们最常见的就是应用部署层面的,比如 SET 化部署。一个服务对外的使用方可能有 A 业务、B 业务,那么如何保证 AB 业务不会相互影响,那么就是 SET 化部署。SET 化部署也可以防止非关键业务来影响关键核心业务。一个隔离的维度可以是按业务场景区分,分为关键集群、次关键集群和非关键集群三类,这样能避免关键和非关键业务互相影响。
SET 化部署就是把业务系统分为多个可扩展的逻辑分区,每个 SET 化的逻辑分区都可以独立部署并提供服务,SET 也可以理解为 ”逻辑机房“ ,主要目的就是为了进行独立部署并且做到业务上的逻辑隔离。
关于 SET 的具体例子:微信红包用户发一个红包时,微信红包系统生成一个ID作为这个红包的唯一标识。接下来这个红包的所有发红包、抢红包、拆红包、查询红包详情等操作,都根据这个ID关联。红包系统根据这个红包ID,按一定的规则(如按ID尾号取模等),垂直上下切分。切分后,一个垂直链条上的逻辑Server服务器、DB统称为一个SET。各个SET之间相互独立,互相解耦。并且同一个红包ID的所有请求,包括发红包、抢红包、拆红包、查详情详情等,垂直stick到同一个SET内处理,高度内聚。通过这样的方式,系统将所有红包请求这个巨大的洪流分散为多股小流,互不影响,分而治之,
2-3、服务应用层
多线程、线程同步、协程
并发问题一直是服务端编程中的重点和难点问题,为了优化系统的并发量,单机解决高并发问题从最初的 Fork 进程开始,到进程池/线程池,再到 Epoll 事件驱动(Nginx),再到协程(如 Goroutine)。
对于 Go 语言,尽可能的多使用协程去提高并发能力。
异步化
消息队列也是一种异步化操作,但是除了依赖外部的中间件如消息队列,在应用内我们也可以通过线程池、协程的方式做异步化,能异步的尽量异步处理,这样可以提高并发。
预处理:预加载、预热
系统的预热一般有JVM预热、缓存预热、DB预热等,通过预热的方式让系统先“热”起来,为高并发流量的到来做好准备。预热实际应用的场景有很多,比如在电商的大促到来前,我们可以把一些热点的商品提前加载到缓存中,防止大流量冲击DB。
还有一种预热的思路是利用业务的特性做一些预加载,比如 feeds 流刷新的时候,提前加载 1-2 页数据,这样用户往下刷新的时候,就感觉不到卡顿。