写点什么

架构实战营 1 期模块 5 作业——高性能计算架构

用户头像
tt
关注
发布于: 35 分钟前

作业部分

设计微博系统中”微博评论“的高性能高可用计算架构。


基于模块 5 第 6 课的微博实战案例,分析“微博评论”这个核心场景的业务特性,然后设计其高性能高可用计算架构,包括但不限于如下内容:

  • 计算性能预估(不需要考虑存储性能);

  • 非热点事件时的高性能计算架构,需要考虑是否要拆分独立的服务;

  • 热点事件时的高可用计算架构。

高性能计算架构

用户行为建模和性能估算

参考发微博和看微博的的用户行为建模和性能估算。


  • 发微博。

  • 大部分的人发微博集中在早上 8:00~9:00 点,中午 12:00~13:00,晚上 20:00~22:00,假设这几个时间段发微博总量占比为 60%,则这 4 个小时的平均发微博的 TPS 计算如下:

  • 看微博。

  • 假设平均一条微博观看人数有 100 次,则观看微博的次数为:

  • 大部分人看微博的时间段和发微博的时间段基本重合,因此看微博的平均 QPS 计算如下:


微博评论是在看微博的基础上的,假设 10%的人看完微博后会进行评论,所以评论微博的次数为


看微博次数 * 10% = 250亿 * 10% = 25亿
复制代码


微博评论的时间段和看微博时间段完全重合,因此微博评论的平均 TPS 计算如下:


微博评论TPS = 看微博QPS * 10% = 1000K/S * 10% = 100K/S
复制代码

高性能计算架构设计

主要包括缓存架构和负载均衡架构两个方面。

业务特性分析

  • 写场景。

  • TPS 巨大,应该使用多级负载均衡架构。

架构分析与设计

  • 缓存架构

  • 写场景,不能用缓存。

  • 负载均衡架构

  • 用多少级的负载均衡?类似于发微博,请求量巨大,可以采用 4 级负载均衡架构,覆盖 DNS -> F5 -> Nginx -> 网关。

  • 负载算法。评论微博依赖于已经发布的微博,即此时不能仅仅基于请求,而是基于业务内容。

  • 存在一次路由(IDC 内的路由)、二次路由(IDC 之间的路由)。

  • 业务负载均衡方法。可以使用自定义 HTTP Header 或者 HTTP query string,内容为被评论微博的 ID,根据这个 ID 将评论发送到相应的业务服务器进行处理。

业务服务器数量估算

类似于发微博,评论微博也会涉及几个关键的处理:内容审核(依赖审核系统)、数据写入存储(依赖存储系统)、数据写入缓存(依赖缓存系统),因此按照一个服务每秒处理 500 来估算,完成 100K/S 的 TPS,需要 200 台服务器,考虑 25%的预留量,250 台服务器可满足需求。


高可用计算架构

用户行为建模和性能估算

热点事件发生后,用户的评论量也会有增加,粉丝还会分成不同的阵营,互相评论。假设评论量会上升 30%。

高可用计算架构设计

业务特性分析

根据微博评论的高性能计算架构分析,评论是基于已经发布的微博的,即这些激增的评论会由发布微博的服务器进行处理。

架构分析与设计

热点事件会有多少新增评论不好估计(和事件热度有关系),所以无法做总量控制,但是可以控制评论生成的速度,所以限流采用令牌桶算法。这样就有少量的请求被丢弃,此时用户再重新评论即可。

笔记部分

本模块讨论高性能高可用计算架构。


  • 高性能计算架构 = 缓存(多级缓存+分布式缓存)架构 + (多级)负载均衡架构

  • 高可用计算架构 = 接口高可用 = 防止雪崩(请求太多导致) + 防止链式效应(接口故障导致)

多级缓存架构

缓存原理和设计框架

原理

  • 缓存,Cache,位于速度差异较大的两种组件之间,用于协调其速度差异的结构。

  • 缓冲, Buffer,临时存储区域, 保存需要在两个组件之间传输的数据的结构。


有的结构即使缓存,又是缓冲,比如 Innodb buffer pool。

有的 Buffer 被名字是“缓存”,比如磁盘控制器写缓存(Writecache)。

所以,名字不重要,本质才重要,关键是看其作用是协调速度(或者性能)差异还是临时存储数据


缓存的本质是空间换时间。华仔认为缓存架构是高性能计算架构而不是高性能存储架构,因为:


  • 缓存不会持久化数据

  • 缓存是通过空间换时间来提高访问速度和性能,自然是高性能计算架构。

设计框架

3W1H:


  • what, 存什么?

  • when,存多久?

  • where, 存在哪?比如 App 缓存、HTTP 缓存、CDN、Nginx 缓存、Memcached、Redis 等。

  • how,如何存?就是指数据的更新机制

  • 过期更新

  • 定期更新

  • 主动更新,指数据修改后,主动更新缓存中的数据

多级缓存架构模式

设计缓存时要在性能需求和架构复杂度之间取得平衡。下面的集中架构模式是按照复杂度从高到低,性能需求也是从高到低排序的。


  • 5 级缓存架构


五级缓存架构
  • 4 级缓存架构,在 5 级的基础上去掉 CDN。这样虽然降低了复杂度,但是可以满足性能需求没有那么高的场景,还能减少成本。

  • 3 级缓存架构。在 4 级的基础上,去掉应用内缓存。因为应用内缓存的复杂度比较高(比如数据一致性问题以及编码复杂度问题);但是没有去掉 Web 容器缓存和 App/浏览器缓存,因为他们的复杂度很低,却能带来不错的收益。

缓存技术概要介绍

  • 本地缓存:App 缓存、HTTP 缓存

  • CDN 缓存

  • Web 服务器缓存

  • 应用缓存:应用在本地缓存数据,不只内存可以,SSD 也可以

  • 分布式缓存

  • Redis 和 Memcached

    • 较多大对象缓存用 Memcached:因为 Redis 是单线程的

    • 如果业务上的需求比较复杂,不是简单的数据库+缓存的方案可以满足,就用 Redis,因为它支持复杂的数据结构

    • 如果缓存生成代价高,一旦丢失后果严重,就用 Redis,因为它支持持久化

    • 如果不确定用哪个,就用 Redis,它已经是事实上的标准

思考题

视频网站的带宽成本是主要成本,用 CDN 并没有减少总体访问带宽,那么为何用 CDN 可以降低带宽成本?


因为 CDN 依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,可以使用户就近获取所需内容,可以减少夸运营商的流量,所以可以降低带宽成本?

分布式缓存架构设计

分布式缓存架构模式

在系统演化的过程中,为了提高存储系统的性能,应该优先使用读写分离而不是缓存,这是因为读写分离可以比较容易的从单主的架构演化而来,存储系统往往支持这样的扩展,这样带来的复杂度就会降低,对业务的侵入性少;而上缓存要解决诸如数据一致性的问题,复杂度相对于读写分离来说会高不少。


  • 数据缓存

  • 适用于实时性要求高且读多写少的业务场景。需要处理好缓存中数据与数据库中数据的一致性。

  • Redis 持久化带来的风险就和数据一致性有关,进一步说就是 Redis 持久化的数据和存储系统如 MySQL、HBASE 持久化的数据会由于 Redis 持久化数据无法及时更新而更长时间的导致数据不一致。

  • 结果缓存

  • 适用于计算量大但实时性要求不高的场景,例如推荐、排行榜、分页等。需要平衡缓存有效期与新鲜度。

数据缓存架构一致性设计

造成存储与缓存数据不一致的原因有两个:


  • 更新存储与缓存的写操作不是一个事务,二者的更新没有原子性:

  • 正常情况下,两者的更新操作中间有一个客户端可见的窗口,期间数据是不一致的;而且这个窗口是不可避免的;

  • 当存储系统或缓存系统有任何一方失败的时候,只能靠重试来解决,时序图参见“写操作”小节的时序图。重试的方法可以参见“一致性解决方案”小节,主要是利用消息队列和关系数据库本地事务表

  • 缓存的读操作非常特殊:当第一次读取缓存没有命中的时候,需要去存储系统中读取,然后把这个最新的值写入缓存中,即读操作中包含写入的动作!数据库系统设计中,要处理读写冲突和写写冲突,但存储+缓存其实没有读写冲突和写写冲突(存储内部和缓存内部已经解决了这些冲突),有的是旧值覆盖新值的问题

  • 旧值能覆盖新值,说明发生了**先发(先读取的旧值)后至(晚于缓存中的新值更新)**的现象;

  • 既然存在新旧之分,那么就应该可以通过给数据增加版本号的办法来区分新旧数据、并且不允许旧数据覆盖新数据的办法来解决。

  • 详细分析可以参看“缓存的更新”小节的时序图。

读操作

数据缓存就是为了解决读数据慢的问题,所以一定是先去读缓存,否则就没有意义了。


  • 先读缓存,如果有则直接返回;

  • 如果缓存里没有,则去读存储系统,然后把值回种缓存;如果数据库里也没有,就应该回种一个空值。


这是缓存最典型的应用场景——读多写少。因为写操作比较少,所以应该直接写入到存储系统中。体现在 Spring 编程的过程中,写操作的方法上是没有缓存相关注解的。

读取的过程就会涉及到几个问题:

  • 数据在缓存里没有,在存储系统中有:发生这种情况的可能原因之一就是存在并发访问,线程 A 已经写成功了存储系统,还没有来得及回种缓存时,线程 B 开始读取缓存。

  • 数据在缓存里没有,在存储系统中也没有:这种情况也可以理解为一种“异常情况”,既然是读多写少的场景,怎么可能发生去读还没有生成的数据这种操作呢?所以此时只有两种可能:

  • 缓存和存储中的数据被误删除了

  • 恶意的访问请求,此时可以采用布隆过滤器来解决。

  • 在缓存里有,在存储系统里没有。在读多写少的场景中,有两种可能原因:

  • 可以视为一种错误了:删除了存储系统中的数据,缓存中对应的数据却没有删除,如果这不是程序的 bug,就是在级联删除缓存数据的时候出现了异常,即缓存系统和存储系统的操作没有包在一个事务里,这个时候就应该重试删除,比如使用关系数据库记录操作在本地事务表中;或者使用消息队列去删除。

  • 存在并发访问:线程 A 删除了数据库,还没有删除缓存的时候,线程 B 读取了缓存。这也是一种不合理的情况,删除数据时应该先使缓存中的数据失效,再去删除数据库。

写操作

  • 一般情况下,缓存多是为了应对系统读取性能不足的问题,此时写缓存只是写成功存储系统后将值同步到缓存中的一种操作。

  • 如果把缓存当做数据库,也可能发生频繁的写操作(此处特指更新而不是删除)。但既然缓存的读写性能高出存储系统一个数量级,就不应该让两者的写操作成为同步操作,否则相对较慢的存储系统早就被缓存压死了,部署缓存也就没有意义了。此时(即把缓存当做数据库)应该让缓存和存储系统之间的数据复制操作通过异步的方式进行;既然已经是异步复制了,那隐含的意思就是容忍数据的不一致,以缓存的数据为主、以存储的数据为辅。异步复制有两种方式:

  • 使用消息队列作为同步组件

  • 在缓存失效的时候,将数据写回数据库,这样需要缓存内部支持,应该比较难以实现。

​ 如果把缓存当做数据库,则还有一个问题不好解决。用 3W1H 缓存设计框架进行分析的结果如下(WHAT 和 WHERE 比较简单,就不罗列了):

  • WHEN,存多久的问题不好解决,显然需要存储直到存到数据写入存储系统为止,相当于不能设置过期时间,必须手动使其过期

  • HOW, 如何更新。此时缓存由业务代码更新,所以 HOW 特指存储如何更新

  • 主动更新,即由单独的线程或者服务来更新存储(业务系统不会直接和它交互,缓存系统如 Redis 也不具备这个功能);使用消息队列进行更新就是这种情况

  • 定时更新,由单独的线程或者服务定时更新存储。这样也不好做,因为是有状态的操作,必须记录上次更新到哪里了,太过于复杂。

  • 过期更新,不适用,因为此时缓存无法设置更新时间。

  • 综上,缓存的应用基本前提还是读多写少, 主要目的是提升系统的读取性能;即使有写操作,性能需求也不可以超过存储系统的上限;换一种说法,就是缓存发生写操作,一定是存储系统先发生了写操作,为了数据的一致性,才很有必要地写了一次缓存。通常情况下,带有缓存的架构中,还是应该以存储系统为主,缓存为辅


根据上面的分析,下面以只读缓存的分析为重点


  • 读写缓存

  • 先写缓存后写存储:写缓存成功写存储失败,缓存失效后,数据丢失,导致关联业务出问题。

  • 按照上面的分析,此时最好采用异步的策略来更新缓存。此时会面临上述分析第二大条中的困难。

  • 先写存储后写缓存,写存储成功,写缓存失败;缓存失效后重新加载才能使得数据再次一致。这个更新缓存的操作有个专门的名字——Read/Write Through

  • 只读缓存

  • 先删除缓存,再更新存储

  • 先删除缓存再写存储系统(适合用户相关数据):正常情况下数据是一致的;但如果删除缓存失败,还是要写入存储,这样缓存中是旧值,存储中是新值,则数据不一致;

  • 双删(适合全局数据,例如运营活动图片):先删除缓存,再写存储,再删除缓存。

  • 先更新存储系统,然后删除缓存(使缓存失效)。这个有个专门的名字叫做 Cashe Aside

  • 本条在华仔的课上没有提到

  • 上面的两个操作的序列图如下:


只读缓存数据一致性
  • 可以看出,上图中两种办法基本差不多,但是先删除/失效缓存,再更新存储的话:

  • 由于操作的并发性,缓存中会有较长的时间保存旧值(图中绿色文字所示);

  • 同时由于删除了缓存,数据库的压力会急剧升高。

  • 所以对于只读操作来说,还是采用先更新存储再删除/失效缓存的方法较好(与华仔的结论不一致)。

缓存的更新

这里再把缓存的更新方法拿出来单独的进行说明。更新的方法分别是:


  • 读写缓存中的 Write Through,即先写存储后写缓存。

  • 只读缓存中的 Cashe Aside,即先更新数据库,然后使缓存失效


如果减少概念(比如读写缓存/只读缓存)的数量,只讨论缓存的读和写的逻辑,反而更方便一些。


两者的时序图(Rule)见下图(画吐血了,总算觉得搞明白了),由图可以看出,在正常情况下(即缓存操作成功的场景)还是 Cashe Aside 更新策略更优,可以保证旧值不会覆盖新值。


缓存的更新


一致性解决方案

由于上述的推导得出结论,先更新存储,再删除缓存为更优的方案,所以下面的论述都是这个顺序,与华仔课件的论述顺序相反


数据缓存架构的一致性复杂度的本质是需要跨越缓存系统和存储系统实现分布式事务!解决办法有如下几种:


  • 容忍不一致性:根据容忍度设定缓存的有效期,例如新闻资讯、微博、商品信息等。

  • 使用关系数据库本地事务表(实现较复杂;数据不一致的时间等于重试间隔):

  • 正常的时候,先写入数据库后删除缓存;

  • 缓存系统异常的时候,通过事务记录一条消息到本地消息表,然后后台定时读取消息表记录,重试删除操作;

  • 消息队列异步删除(实现较复杂;数据不一致的时间等于重试间隔):

  • 正常的时候,先写入数据库后删除缓存;

  • 缓存系统异常的时候,发送一条删除操作给消息队列,然后后台读取消息队列记录,重试删除操作。

缓存架构通用三类问题及设计

三类问题

对这几个概念,华仔给出了自己的定义。这些概念的定义确实存在不统一的现象。


  • 缓存穿透:缓存里没有数据

  • 缓存雪崩:缓存失效引起雪崩效应

  • 缓存热点:部分缓存访问量超高

缓存穿透

定义:业务系统虽然去缓存查询数据,但缓存中没有数据,业务系统需要再次去存储系统查询数据。这样的场景如下:


  • 存储系统中确实不存在被访问的数据,比如被黑客攻击,导致大量无效业务请求;

  • 解决办法:采用回种空值到缓存中。

  • 存储中存在,缓存中不存在:例如老数据、冷门数据;

  • 解决办法:可以采用缓存当前数据(存储系统中的数据分为当前数据和历史数据)的办法来解决。

  • 系统刚启动,缓存还没有生效(比如营销活动、秒杀、大促等场景)。解决办法:可以采用缓存预热的办法来解决,具体为:

  • 模拟请求触发系统生成缓存,实现比较复杂;

  • 后台按照规则批量生成缓存,实现工作量比较大;

  • 灰度发布/预发布触发系统生成缓存(推荐)。

  • 大量缓存集中过期。为了解决这个问题,在设置缓存有效期的时候,不能设置的一样,要加上一个随机的浮动量。

缓存雪崩

定义:当缓存失效(过期)后引起系统性能急剧下降的情况。


缓存穿透的场景中,也有一个是由于缓存过期失效导致的。但缓存雪崩更强调失效后,大量相同的请求并发地访问后面的存储系统或者计算系统,而且存储系统或者计算系统的操作都非常耗费时间,从而导致系统性能急剧下降。


技术本质


缓存雪崩的过程


原因:


  • 生成缓存较慢,即缓存后端系统的操作比较耗时,比如复杂的数据查询、大量的计算等;

  • 缓存失效后并发请求量较大,例如 50 个以上。


应对方法


解决办法主要针对的是导致缓存雪崩的两个原因:大量并发访问后端系统、缓存失效。


  • 更新锁(需要引入分布式锁,保证只有一个线程可以更新)

  • 对缓存更新操作进行加锁保护,保证只有一个线程能够进行缓存更新;

  • 未能获取更新锁的线程要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。

  • 后台更新(需要保证后台线程高可用)

  • 缓存有效期设置为永久;

  • 由后台线程来更新缓存,而不是由业务线程来更新缓存;

  • 后台线程更新缓存,更新策略分为“定时更新”、“事件触发更新”;

  • 业务线程只读取缓存,缓存不存在就返回空值。

缓存热点

定义特别热点的数据,如果大部分甚至所有的业务请求都命中同一份缓存数据,则这份数据所在的缓存服务器的压力也很大,有可能撑不住。常常发生在热点事件、突发事件(比如微博上发生的热点事件)的场景下。


应对方法——多副本缓存,即让压力分担到多一点的节点上,不至于一个节点直接被压趴。


  • 写入的时候,缓存的 key 加上编号,写入到多个缓存服务器

  • 读取的时候随机生成编号组装 key,然后读取。

  • 挑战:不太好预料哪些 key 是热点。

思考题

如果让你来设计微博的缓存热点动态决策,你觉得可以怎么做?


是不是可以根据微博的访问量形成的时间序列,来预测热点的形成,如果预测成功,则增加其副本数(鸡蛋篮子法则)。

负载均衡架构

多级负载均衡架构

全负载——4 级负载均衡

4级负载均衡架构


上图的各个层级有如下的关系:


  • 越往上的层级,业务含义越低、性能越高、配置变动频率越低

  • 越往下的层级,业务含义越高、性能越低、配置变动频率越高

多级负载均衡架构设计关键点

两个关键点:


  • 并不是一成不变的,而是根据性能需求来合理的增删层级

  • 是否可以接受复杂度成本;


几个问题:


  • 多级级联加长了处理路径,性能应该会受到影响,为何还要这么设计?

  • 不会。因为负载均衡的设备的性能都非常高,性能的瓶颈点在于业务服务、存储系统,和它们相比,负载的性能损失可以忽略。即抓主要矛盾。

  • 多级级联架构很复杂,看起来违背了架构的简单原则,直接用 F5 或者 LVS 负载均衡到服务网关不就可以了么?

  • 主要考虑维护复杂度,尽量不要经常改动上层的配置,否则因为上级负载会接很多服务,这样影响面太大,容易引入风险。

简化的负载均衡架构——合适原则

  • 3 级负载均衡架构——去掉 F5/LVS,因为它们的成本非常高,性能需求没有这么高的场景就可以去掉

  • 2 级负载均衡架构——将 nginx 也去掉,性能需求进一步降低的时候,就可以这么做。

负载均衡技术剖析

  • DNS

  • 地理位置和机房级别的负载均衡。

  • 标准协议,可以直接花钱购买

  • 有 DNS 劫持的问题,此外,DNS 还有缓存,规则生效不会那么快。

  • HTTP-DNS

  • 应用场景:App、客户端

  • 非标协议,不通用,不太适合 Web 业务,因为 Web 业务的 DNS 解析是由浏览器进行的,一般情况下无法控制

  • 正常的时候走 DNS,异常的时候才走 HTTP-DNS;

  • SDK 也会缓存 HTTP-DNS 解析结果

  • GSLB

  • GSLB(Global Server Load Balancing):全局负载均衡,主要用于在多个区域拥有自己服务器的站点,为了使全球用户只以一个 IP 地址或域名就能访问到离自己最近的服务器,从而获得最快的访问速度。

  • LVS:内核级别的负载均衡,基本能跑满千兆网卡带宽,性能量级 10~100 万请求。

思考题

对照 F5 的性能,计算一下一台 100 万的 F5 可以支撑什么量级的 ToC 业务?


这样的 F5 性能为“每秒 L4 HTTP 请求数:7M,最大 L4 并发连接数:24M”,所以可以支撑千万量级的 ToC 业务。

负载均衡技巧

通用负载均衡算法

做负载均衡算法选择时,最重要的就是考虑任务是否是有状态的,比如用户的状态,服务器的状态等。


  • 轮询 & 随机

  • 通用,无状态的负载均衡。

  • 不会判断服务器状态,除非服务器连接丢失。

  • 不会考虑服务器之间性能的不同。

  • 负载均衡算法 - 加权轮询

  • 适用于服务器的处理能力有差异的场景,例如新老服务器搭配使用。

  • 不会判断服务器状态,除非服务器连接丢失;

  • 权重配置不合理可能导致过载,比如 2020 年采购的机器 CPU 核数是 2019 年采购的机器 1 倍,运维直接配置了 2 倍权重,结果导致新机器全部过载。

  • 加权轮询算法

  • “权重 = 请求数量”:给服务器 1 发送 40 个请求,然后再给服务器 2 发送 40 个请求,然后再给服务器 3 发送 20 个请求。

  • 权重概率:例如:服务器[0~39],服务器 2[40~79],服务器 3[80~99],生成 0~99 的随机数,落入哪个区间就用那个服务器。

  • 权重动态调整:Nginx 的实现,兼顾服务器故障后的慢启动。

  • 负载优先:负载均衡系统将任务分配给当前负载最低的服务器,这里的负载根据不同的任务类型和业务场景,可以用不同的指标(如 4 层可以用“连接数”,7 层可以用“HTTP 请求数”作为指标)来衡量。

  • 实现复杂,需要管理或者获取服务器状态。

  • 性能优先:负载均衡系统将任务分配给当前性能最好的服务器,主要是以响应时间作为性能衡量标准。比如 Nginx 可以进行扩展以支撑此类算法。

  • 如果服务器响应不经过负载均衡器,则不能应用这种算法,即取决于系统的网络拓扑结构。

  • Hash:基于某个参数计算 Hash 值,将其映射到具体的服务器。

  • 适用于有状态的任务,例如购物车;

  • 任务是分片的,例如某个用户的请求只能在某台服务器处理。

  • 类似于会话保持算法。如果可以把上下文集中放在一个缓存中,业务服务器去请求上下文,则可以不固定在某台服务器。但这样增加了复杂度。

业务级别负载均衡技巧

通用负载均衡算法是基于请求的,业务级别的负载均衡是基于业务内容的,更灵活。

业务负载均衡技巧

  • Cookie:一般用于 session 保持、购物车、下单等场景。

  • 自定义 HTTP Header

  • 可以通过 X- 来自定义 HTTP Header,可以服务器下发,也可以直接带上本地信息。这种用法被 IETF 在 2012 年 6 月发布的 RFC5548 中明确不推荐,虽然 RFC 已经不推荐使用带 X 前缀的 http 头部,但是不推荐不代表禁止,目前应用广泛。


自定义HTTP Header实现负载均衡
  • 应用场景:一般用于精细化的地理位置、机房级别、版本、平台、渠道等负载均衡,例如:

  • 如果客户端位于加利福尼亚州山景城,则负载平衡器会添加 Header:X-Client-Geo-Location:US,Mountain View.

  • X-Client-Version: 3.0.0, X-Client-Platform:iOS 11, X-Client-Channel:Huawei.

  • HTTP query string

  • 此时 query string 包含负载均衡信息,例如 http://host:port/?userId=14167734;

  • 对业务侵入较大,不如用 Cookie。

服务器性能估算

  1. 估算接口应该达到的性能

  2. 线上业务服务器接口处理时间分布为 20~100ms;

  3. 平均大约为 50ms;

  4. 访问存储或者其它系统接口是主要的性能消耗点。

  5. 评估服务器性能:线上单个服务器(32 核)性能大约为 300~1000 TPS/QPS。

  6. 最后计算服务器数量:服务器数量 = (总 TPS+QPS) / 单个服务器性能。


CPU 核数增加,并不能够线性提升处理能力。这是因为处理能力主要是有存储系统、编码质量来决定的。

思考题

为什么看起来没那么强大的算法或者技术的应用反而会更广泛?类似的还有 TCP/IP(曾经的对手是 ATM),缓存算法中的 LRU(LFU、LRU-K……).


  1. 因为合适原则。简单的算法可以覆盖很多常用场景,类似于“二八”定律,80%的场景用 %20 的技术就可以解决;

  2. 因为边际收益。随着复杂度的增加,带来的收益反而会降低。

接口高可用

架构和代码共同决定系统质量!架构决定系统质量上限,代码决定系统质量下限。

高可用计算与高性能计算的关系

高可用计算架构设计和高性能计算架构设计是类似的:


  • 相同点是都是根据用户行为进行性能估算;

  • 不同点是高可用是针对热点事件这种会对系统造成冲击的异常(outlier)流量进行的。因为高可用就是要解决这种偶发高流量场景下系统的可用性。

  • 因为它是偶发的,所以不可能按照这个来估算服务器的数量;

  • 因为它是超过通常流量的流量,所以会对系统造成冲击,为了保证高可用,就需要丢弃这些超过系统设计容量的请求,所以接口高可用架构本质上是“丢车保帅”策略,业务或者用户体验会部分有损!也可以说,其本质就是研究请求可不可以限速处理,甚至是可不可以丢弃^_^。

  • 与高性能架构设计不同,高可用架构设计是在前者的基础上经过分析后,综合使用限流、排队、降级、熔断的方法保证高可用。

  • 不用估算业务服务器的数量了,限流、排队、降级、熔断甚至都不是业务

接口高可用整体框架

做到高可用需要做到:


  1. 在应对大量请求时确保压力不要超过某个节点的处理能力从而导致系统崩溃,即防止雪崩效应

  2. 此时的主要矛盾是请求太多,可以使用排队+ 限流的办法。

  3. 如果发生故障,要及时隔离故障,防止在系统模块之间链式扩散,即防止链式效应

  4. 此时的主要现象是某些接口发生了故障,可以使用熔断+降级的办法。

限流

用户请求全流程各个环节都可以限流:


  • 请求端限流:发起请求的时候就进行限流,被限流的请求实际上并没有发给后端服务器。

  • 比如在前端限制请求次数或者在前端嵌入简单业务逻辑,随机丢弃某些请求;

  • 流量在本地进行控制,防君子不防小人(脚本)

  • 接入端限流:接到业务请求的时候进行限流,避免业务请求进入实际的业务处理流程。

  • 比如限制同一用户请求频率;或者随机抛弃无状态请求(比如浏览请求);

  • 可以实现防刷单;但是限流的阈值不好确定,需要人工进行判断;

  • 接入端没有复杂的处理逻辑,从这里限流能对整个系统进行保护;

  • 微服务限流:单个服务的自我保护措施,处理能力不够的时候丢弃新的请求。

  • 处理能力难以精准配置。


常见算法:


  • 固定 & 滑动时间窗

  • 漏桶算法

  • 主要应用在瞬时高并发流量场景(例如 0 点签到,整点秒杀)。

  • 一瞬间请求将桶填满,其余请求被丢弃,后台系统可以按照自己的实际处理能力不间断处理请求(即达到了最大速度),又不被压垮。此时系统不间断的处理请求,但到底是多大的速度,无法精确控制的,有点像系统控制中的反馈调节回路,可以是系统处在满负荷运行状态,自动得出一个最大处理对。正因为速度不好确定,那么桶多大才能保证前端系统不超时也不好确定

  • 虽然控制不了系统的处理速度,但是桶的大小一旦确定,某一瞬间,系统最多会保留桶大小的请求数量,所以漏桶的技术本质是总量控制

  • 桶大小动态调整比较困难,例如 JavaBlockingQueue;

  • 综上,桶大小是设计关键

  • 漏桶算法变种 - 写缓冲(Buffer):如果漏桶的容量无限(例如用 Kafka 消息队列),则漏桶可以用来做写缓冲。技术本质是同步改异步,缓冲所有请求,慢慢处理。可以应用在高并发写入请求。

  • 令牌桶

  • 与漏桶算法不同,令牌桶(桶可以是消息队列等)中令牌生成的速度是指定的,即精确的控制了系统的处理速率;而漏桶是无法精确控制处理速率的;

  • 正因为处理速率是指定的,当配置不当时,突发流量的时候可能丢弃很多请求;

  • 令牌生成的速度是可以动态调整的。

  • 既然是速率控制,就可以用于控制访问第三方服务的速度,防止把下游压垮,相当于做了本系统和三方系统之间的处理速度匹配;也可以控制自己的处理速度,防止过载。相当于对下游系统做了保护(不像漏桶算法那样,是使得下游系统处在满负荷运行状态中)。

  • 综上,令牌产生的速度是设计关键

排队

基本原理:收到请求后并不同步处理,而是将请求放入队列,系统根据能力异步处理。


技术本质:请求缓存 + 同步改异步 + 请求端轮询(或者可以等待异步通知?)。这三个要素是互相关联的:


  • 我觉得用请求缓冲更贴切一些:队列中的请求不是被临时保存等待查询,而是要被转送到业务处理逻辑中去;

  • 既然排队了,何时处理完是不确定的,所以同步等待就不太适用了;

  • 既然请求端无法同步获取到结果,那只好轮询或者等待异步通知了。

  • 所以,要重点保证用户体验(前端、客户端交互)。


漏桶中的请求如果按照进入的先后进行处理,也相当于在排队等待。但排队和限流的区别在于排队最终是异步处理

排队和限流都避免了请求直接冲击业务处理逻辑,排队比限流更进一步,处理结果的获取都是异步的,不存在同步处理中得到结果却超时被请求端抛弃的情况,这样就不会浪费系统的处理能力了,应该可以更多的提高系统的处理效率


应用场景:秒杀、抢购。

排队的具体实现方案示例

排队的具体实现方案


注意上图使用 token 做了简单的权限验证:排队号用于声称“你是你”,token 用于证明“你是你”

降级

基本原理:直接停用某个接口或者 URL,收到请求后直接返回错误(例如 HTTP 503)。


应用场景:故障应急,通常将非核心业务降级,保住核心业务,例如降级日志服务、升级服务等。


设计要点:


  • 执行降级操作的,可以是独立的降级系统,也可以是嵌入到其它系统的降级功能。

  • 人工判断,人工执行(指下发指令,指令还是由降级系统或者降级模块执行的)。

熔断

熔断的本质是资源隔离:下游接口故障的时候,不再浪费系统处理能力去重试或者去等待,及时的释放出来做确定有收益的事情。


熔断策略可以通过配置中心,也可以通过配置文件来配置。


基本原理:下游接口故障的时候,一定时期内不再调用。应用场景:服务自我保护,防止故障链式效应。

思考题

架构设计和代码共同决定系统质量,那么谁的影响更大一些?

计算架构实战

这里以微博为例进行分析。

用户行为建模和性能估算

详细步骤在模块四第 4 课,大概的过程为:用户量预估 -> 用户行为建模 -> 性能需求计算。


  • 用户量:月活 5.11 亿,日活 2.24 亿。

  • 行为建模:发微博、看微博、评论微博。就是具体的需求点或者关键场景,估算都是针对具体的场景或者行为进行的。

  • 性能需求计算:整体上是将行为分为读、写两大类,然后估计每个场景下相应的 QPS 和 TPS。在估计时需要考虑用户行为的特点,比如发微博集中的时间段,这样才可以估算出峰值而不是平均值,因为用户行为在时间上的分布不是正态的或者均匀的。

  • 比如,发微博是写操作,要评估 TPS;看微博是读操作,要评估 QPS。

  • 发微博。TPS 为 10K/S。

  • 看微博。QPS 约 1000K/S。


所以,性能估算的大概步骤就是场景分解 -> 业务特性分析(包括读写场景分析和用户量/请流量分析)-> 架构分析与设计。

高性能计算架构设计

所谓计算架构设计就是本模块的两大内容:缓存架构设计、负载均衡架构设计。


  • 缓存架构设计,重点在于场景类型(读/写)。主要考虑读写比,读操作很大的场景才可以用缓存。读写分类相当于是任务分解

  • 负载均衡架构设计,重点在于请求数量,最直接的决定因素就是用户量。

发微博

业务特性分析
  • 写场景,所以不用缓存

  • 用户量很大(即请求很大),可以用负载均衡

架构分析与设计
  • 缓存架构

  • 写场景,不适合用缓存

  • 负载均衡架构

  • 用多少级的负载均衡?用户量巨大,可以使用 4 级负载均衡

  • 负载算法?发微博时依赖用户登录状态,登录状态保存在分布式缓存中,所以对于业务服务来说,相当于是无状态的,所以可以使用轮询或者随机算法(合适原则,越简单越好)。

业务服务器数量估算

按照一台服务器每秒处理 500 来估算,完成 10K 的 TPS 需要 20 台服务器,考虑预留量,25 台服务器。


注:这里是业务服务器数量估算,不是缓存服务器!

看微博

业务特性分析
  • 读场景,适合用缓存

  • 用户量很大,可以用负载均衡架构

架构分析与设计
  • 缓存架构

  • 请求量达到 250 亿,使用多级缓存架构,其中 CDN 缓存是缓存设计的核心。

  • 负载均衡架构

  • 用多少级的负载均衡?用户量过亿,使用多级负载均衡架构。

  • 负载算法?不登录也可以看微博,即请求没有状态,可以选择随机/轮询算法(合适原则,越简单越好)。

业务服务器数量估算。

读写请求量巨大,此时 CDN 的成本就是值得的。假设 CDN 承担 90%的流量(90%这个数字是对缓存系统命中率的一般要求,如果达不到,说明缓存设计的有问题),那么 QPS 为 1000K/S * 0.1 = 100K/S。


读微博的逻辑比较简单,主要是读缓存(注意:这里是业务服务器数量估算,不是缓存服务器!),因此假设单台业务服务器的处理能力是 1000QPS,那么需要的机器数量为 100 台,按照 20%的预留量,最终数为 120 台。

高可用计算架构设计

正如“接口高可用”小节一开始所说的那样,高可用计算架构和高性能计算架构是类似的:


  • 都是做性能估算(高可用场景是偶发超高流量下的性能估算;高性能场景是做正常流量下的性能估算),所以也需要对用户行为进行分析、建模。

  • 但是不再分析缓存与负载架构,而是使用限流、排队、降级、熔断等手段来构建高可用架构。

  • 不用估算业务服务器的数量了,限流、排队、降级、熔断甚至都不是业务。


所以,下面的分析都是针对微博热点事件这一场景。

用户行为建模和性能估算

场景分解:热点事件微博会引起大量转发和围观(即业务场景):


  • 转发:这里用了一个假设的模型:10%的围观用户会在 60 分钟内转发。

  • 围观:即看微博,模型取决于事件的影响力和影响范围


当无法预估热点事件时怎么办?只能做好预防工作。

转发微博

业务特性分析

转发微博的业务逻辑基本等同于发微博,所以与发微博分析类似:写场景、用户量巨大。

架构设计与分析(该不该/如何丢弃、限速分析)
  • 限流还是丢弃?

  • 转发的微博重要性和影响力不如原微博,可以考虑对“转发微博”限流;

  • 由于转发能带来更好的传播,因此尽量少丢弃请求,考虑用“漏桶算法”。因为漏桶算法的本质是总量控制

看微博

业务特性分析

读场景。热点事件发生后,绝大部分请求都落在了导致热点事件发生的那一条微博上面。

架构设计与分析(该不该/如何丢弃、限速分析如何丢弃、限速分析)

很明显,热点事件微博存在缓存热点问题,可以考虑“多副本缓存”,由于原有的缓存架构已经采用了“应用内的缓存,总体上来看,缓存热点问题其 实不一定很突出。即结论为不用丢弃

思考题

如果微博业务方不希望在热点事件发生的时候做防护,而是希望能够尽量支撑热点事件,应该如何做?


热点事件有两个特点:突发、请求量大于系统的设计容量,会对系统造成冲击。从成本上考虑,不可以按照热点事件的流量水平来设计系统容量,这样没有热点事件的时候,就造成了资源的浪费。


所以防护是必须有的,在这个基础上,尽量缩短防护时间,利用防护期的时间窗口,对系统做扩容,从而支撑热点事件。

用户头像

tt

关注

还未添加个人签名 2019.04.09 加入

还未添加个人简介

评论

发布
暂无评论
架构实战营1期模块5作业——高性能计算架构