高并发系统设计

通用方法

  • Scale-Out(横向扩展 ):采用分布式部署方式将流量分开,让每个服务器承担一部分并发和流量。(Scale-Up,提高单机性能,系统初期使用Scale-Up,并发超过单机极限使用Scale-out)
  • 缓存:使用缓存来提高性能。磁盘IO(10ms)速度远低于从内存(纳秒)和网卡(微秒)上读取。
  • 异步:在某些场景下,未处理完成之前可以让请求先返回,在数据准备好之后再另行通知。

架构分层

  • 简化系统设计,让不同层次专注做不同的事。
  • 分层后可以做到复用
  • 分层可以更容易横向扩展,比如只针对数据层做扩展。
  • 分层架构相邻层相互依赖,数据只在相邻层交互
  • 分层缺点:实现复杂,有些需求需要修改每一层的代码;如果层次单独部署,层次间通过网络来交互,性能会有损耗。

高并发三大目标

高性能

优化原则

  • 性能优化是问题导向的。
  • 性能优化遵循二八原则,20%精力解决80%性能问题,优先优化主要性能瓶颈点。
  • 性能优化需要数据支撑,优化过程中要时刻了解优化让响应时间减少了多少,提高了多少吞吐量
  • 性能优化过程是持续的。

度量指标

  • 某个时间段内接口响应时间的平均值:敏感度差些,如有少量超慢请求,并不容易发现
  • 某个时间段内接口响应时间的最大值:过于敏感,可能只有极少超慢请求
  • 分位值:比如90分位、75分位等。把某个时间段的请求响应时间从小到大排序,假如有100个请求,排在90位的响应时间就是90分位值。分位值排除了偶发极慢请求的影响,能够较好反应这段时间的性能情况,分位值越大,对于慢请求的影响越敏感
    高并发系统设计_第1张图片
  • 脱离并发谈性能是没有意义的,通常使用吞吐量和响应时间来度量并发和流量,吞吐量使用多些,两者呈倒数关系。响应时间1秒,吞吐量是每秒1次,响应时间10ms,吞吐量每秒100次。
  • 度量性能时,会兼顾吞吐量和响应时间,比如设立优化目标如下:每秒1万次请求,响应时间99分位值在10ms以下。
  • 通常响应在200ms下用户无感知,1s以内用户能感觉到延迟但可以接受,超过1s就会有明显等待感。健康系统的99分位值的响应时间通常需要控制在200ms内,不超过1s的请求占比超过99.99%。

性能优化

  • 假设系统中有一个处理核心,执行任务响应时间10ms,吞吐量每秒100次。优化思路有两种:
1、提高系统的处理核心数
  • 即增加系统并行处理能力,此时,吞吐量 = 并发进程数 / 响应时间,即两个核心,响应时间不变,吞吐量提高一倍
  • 阿姆达尔定律,固定负载下,并行计算的加速比,即并行化之后效率的提升情况:(Ws+Wp)/(Ws+Wp/s)。即效率提升百分比 =(串行计算量 + 并行计算量)/(串行计算量 + 并行计算量 / 并行进程数),可以推导出另外一个公式1/(1-p+p/s),即效率提升百分比 = 1/(1 - 并行任务占比 + 并行任务占比/并行进程数),p为0,即完全串行,加速比为1,完全无加速;s -> 无穷大,加速比为1/(1-p),即和p成正比,当p - > 1时,加速比无穷大。
  • 所以是不是无限增加处理核心就行呢?很遗憾,随着并非进程数增加,并行任务对于系统资源的争抢也会越来越严重,在某个节点继续增加并非进程数,反而会造成性能下降,这就是拐点。
    高并发系统设计_第2张图片
  • 并发数处于轻压力区,响应时间平稳,吞吐量和资源利用率稳步上升;重压力区,资源利用率达到极限,吞吐量随着并发数增长,有下降趋势,响应时间也有上升;此时再加大压力,则进入拐点区,处于超负荷,吞吐量下降,响应时间大幅上升。
  • 所以压力测试的目的就是找到拐点,从而知道系统的承载能力,便于找到瓶颈,持续优化系统性能。
2、较少单次任务响应时间
  • 系统是CPU密集型,需要处理大量的CPU计算,那么选用高效算法或者减少运算次数就是这类系统的重要优化手段,发现这类问题的主要方式,是通过一些Profile工具找到消耗CPU时间最多的方法或者模块(比如golang pprof)。
  • 系统是IO密集型,系统的大部分操作是在等待磁盘IO或者网络IO,比如数据库系统,缓存系统、Web系统等。这类系统的性能瓶颈可能在系统内部,也可能是依赖的其他系统,发现这类性能瓶颈的手段通常有两类:采集工具(如tcpdump)和监控(elk)。
  • 如果是数据库访问慢,那就要看是不是又锁表、全表扫描、索引是否合适等;如果是网络,就要看网络参数是否可以优化、是否有大量超时重传、网卡是否有大量丢包等。

高可用

  • 高可用性(High Availability,HA)指的是系统具备较高的无故障运行的能力。
  • 高并发大流量的系统,出现故障比系统性能低更损伤用户的使用体验。因为单位时间会影响更多用户。

度量指标

  • MTBF(Mean Time Between Failure)是平均故障间隔的意思,代表两次故障的间隔时间,也就是系统正常运转的平均时间。这个时间越长,系统稳定性越高。
  • MTTR(Mean Time To Repair)表示故障的平均恢复时间,也可以理解为平均故障时间。这个值越小,故障对于用户的影响越小。
  • Availability = MTBF/(MTBF + MTTR),计算出的比例代表着系统的可用性。
    高并发系统设计_第3张图片

设计思路

  • 成熟系统的可用性需要从系统设计和系统运维两方面做保障,缺一不可。

1、系统设计

  • 要把发生故障作为一个重要考虑点,预先考虑如何自动化地发现故障,发生后如何解决。具体方法比如failover(故障转移)、超时控制以及降级和限流
故障转移
  • 一般来说,发生故障转移的节点可能存在两种情况:在完全对等的节点之间转移故障在不对等的节点之间,即系统中存在主节点也存在备用节点
  • 对等节点之间做故障转移容易些,这类系统所有节点都承担读写流量,并且节点不保存状态,每个节点都可以作为另一个节点的镜像。在这种情况下,如果访问某一个节点失败,那么随机访问另一个节点即可。 比如,Nginx可以配置当某一个Tomcat出现大于500的请求时,重试请求另一个Tomcat节点
    高并发系统设计_第4张图片
  • 针对不等节点的failover机制复杂很多,比如一主多备,备用节点可以是热备(同样在线提供服务),也可以是冷备(只作为备份使用)。那么我们需要在代码中控制如何检测主备机器是否故障,以及如何做主备切换。使用最广泛的故障检测机制是“心跳”,在客户端或备节点定期向主节点发心跳包。一段时间无回应,则可认为主节点故障,选主的结果需要在多个备份达成一致。
超时控制
  • 复杂的高并发系统通常由很多模块组成,同时依赖很多组件和服务。他们之间调用最怕的是延迟而非失败,因为失败通常是瞬间的、可以通过重试的方式解决。而一旦调用某一个模块发生较大延迟,调用方辉阻塞在这次调用上,占用的资源得不到释放,当存在大量这种阻塞请求,调用方会因为用尽资源而挂掉。
  • 如何确定超时时间。短了,会造成大量超时错误,对用户体验产生影响;长了,起不到作用。建议通过系统之间的调用日志,统计比如99%的响应时间是怎样的,然后依据这个时间来指定超时时间。如果没有调用日志,就只能按照经验来指定了。不过无论哪种方式,超时时间不是一成不变的,需要在后面的系统维护过程中不断地修改。
  • 超时控制实际上就是让请求在一段时间后失败,释放资源给其它请求使用,对于用户有损,但是却是必要的。牺牲了少量请求,保证整体系统的可用性。
降级和限流
  • 降级是为了保证核心服务稳定而牺牲非核心服务的做法。比如发微博会经过反垃圾服务检测->检测是否广告->写数据库的逻辑,其中反垃圾服务检测是相对比较重的操作,涉及到非常多的策略匹配。所以在大流量时可以暂时关闭它,保证主体流程稳定。
  • 限流完全是另外一种思路,通过对并发的请求进行限速来保护系统。比如对于web应用,限制单机每秒处理1000次,超出部分直接返回错误。虽然这种做法损害了用户使用体验,但是它是在极端并发下的无奈之举,是短暂行为,因此可以接受。

2、系统运维

  • 可以从灰度发布、故障演练两个方面考虑。
  • 系统平稳运行时,故障很少发生,90%故障发生在上线变更阶段。比如上线新功能,设计方案问题导致数据库慢请求翻一倍,导致系统请求拖慢产生故障。除了提供必要回滚方案,还有一个手段就是灰度发布。
灰度发布
  • 灰度发布指的是系统的变更不是一次性推到线上,而是按照一定比例逐步推进的。一般情况下是以机器维度进行的,比如现在10%的机器上进行变更,同时观察DashBoard上的系统性能指标和错误日志,如果运行一段时间后系统指标比较平稳且没有大量错误,再推动全量变更。
  • 灰度发布让开发和运维同学能在线上流量观察变更带来的影响,是保证系统高可用的的重要关卡。它是系统正常运行条件下,保证系统高可用的运维手段。
故障演练
  • 故障演练指的是对系统进行一些破坏性的手段,观察在出现局部故障时,整体系统表现如何,从而发现潜在的可用性问题
  • 复杂的高并发系统依赖了太多组件,比如磁盘数据库网卡等,这些组件出现故障带来的影响会不会让整体服务不可用呢?这个是不确定的,所以需要故障演练。
  • 如果系统还不够稳定,建议在线下搭建和线上结构相同的系统,进行演练。

可扩展

  • 高扩展性是设计指标,表示可以通过增加机器的方式,线性提高系统处理能力。

复杂度

  • 单机增加处理核心方式,在并行任务较多时,系统会因为争抢资源达到性能上的拐点,处理能力不升反降。
  • 集群来说也是如此,不同系统分层上存在着瓶颈点,瓶颈点制约了横向扩展能力。
  • 比如系统每秒1000次请求,数据库也是每秒1000次请求,流量增加十倍,系统可以正常扩容,数据库却成了瓶颈。
  • 又比如单机网络带宽50Mbps,扩容到30台机器,负载均衡带宽就超过了千兆,也会成为瓶颈。
  • 数据库、缓存、依赖第三方、负载均衡、交换机带宽等都是需要考虑因素

设计思路:拆分

  • 把庞杂的系统拆分成独立的、有单一职责的模块,将复杂问题简单化。
  • 对于不同模块,拆分遵循原则不同。
    高并发系统设计_第5张图片

存储层

  • 首先考虑维度是业务维度,比如用户数据,关系数据,内容数据可以拆分到多个数据库。还可以隔离故障。
    高并发系统设计_第6张图片
  • 当该业务的数据过多,单机满足不了时,可以按照数据特征做水平拆分,将相同业务数据根据算法(比如一致性哈希)拆分到不同库。需要注意的是,最好一次性增加足够节点,避免频繁扩容,因为数据迁移成本较高。
  • 拆分后尽量不要使用事务、因为一个事务更新不同库时,需要使用二阶段提交,来协调所有数据库要么成功要么失败,协调的成本随着资源扩展不断升高,最终无法控制。

业务层

  • 首先可以根据业务拆分,比如用户池、关系池、内容池(即不同业务接口部署在不同机器上)。每个业务依赖不同的数据库资源,当某一个业务接口成为平静,只需要扩展该业务的池子(即增加负责该业务的机器)。
    高并发系统设计_第7张图片
  • 其次还可以根据业务接口的重要程度拆分,将一个业务拆分为核心池与非核心池(即一个业务的不同重要性的接口分开部署),优先保证核心池子的性能。整体流量上升时优先扩容核心池。
    高并发系统设计_第8张图片
  • 最后还可以根据接入客户端类型不同做业务池拆分。比如APP接口、H5接口、内网接口拆分到不同的机器。

你可能感兴趣的:(系统设计)