透明多级分流系统

如何解释这个名字? 原有的架构或者单体系统无法支撑需求的负载 于是在他的基础上进行进一步改造 通过引入不同部分的中间层 将程序的运行链路分割解耦 对每一部分依据需求进行优化或拓展

据此所生成的多个层次 此为“多级”

多个分布式中间件平衡的承担压力 此为“分流”

对进行的优化应当是用户无感知的 不会对原有的操作逻辑和用户体验带来任何的影响 此为“透明”

三个名词就组成了设计时的最基本要求 而具体如何体现的 原文通过客户端缓存、域名服务器、传输链路、内容分发网络、负载均衡器、服务端缓存接下来进行介绍

但是还有一个更基本的要求就是 所谓的因架构设计所引入的各个中间件 也需要尤其所存在的意义。 原文称之为

  • 不是每一个系统都要追求高并发、高可用的,根据系统的用户量、峰值流量和团队本身的技术与运维能力来考虑如何部署这些设施才是合理的做法,在能满足需求的前提下,最简单的系统就是最好的系统。

一个经典的例子就是StackOverFlow 全世界主流的程序员交流平台 其设计所谓很多的分布式组件 只有简简单单的10台mysql机器就足以 具体可查阅。

客户端缓存

这里所说的客户端缓存主要是指http协议在数据传输中 所做的缓存优化 即在请求头上 服务端和客户端所做的约定

强制缓存

表示强语义的 在某个时间点之前 资源的内容与状态一定不会被改变 因此客户端若处于合法时间内 可一直持有使用该资源的本地缓存副本

Expires

Http/1.0中已有Header 其内容为一个时间 表示在这个时间之前 客户端资源都不会变动 浏览器可以直接缓存这些数据

1
2
3
HTTP/1.1 200 OK
Expires: Wed, 8 Apr 2020 07:28:00 GMT
Expire

但是这个设计非常的简单粗暴 他的颗粒度是相对于整个请求而言的 并且选择只有可或否两种

假如说某个请求中的局部数据 是不希望缓存的

又假如说某个请求中包含了用户的认证鉴权数据 这部分内容是可以换存在用户的本地缓存中

Cache-Control

对此 HTTP/1.1中推出了Cache—Control

他所做的修改优化包括

  • max-age & s-maxage: 表达相对应请求的时间n秒内缓存上有效的 避免Expire中绝对时间有可能受客户端时钟影响的效果 s-maxage中的s指share 代表允许被CDN、代理等持有的缓存有效时间

  • public & private: 代表是否包含用户的私有资源(认证或者鉴权一类)

  • no-cache & no-store: 大致指对应的资源不应该被缓存 令强制缓存完全失效 但是此时的协商缓存机制仍然生效

  • no-transform: 禁止资源被任何形式(编码 解压缩)修改

  • min-fresh: 建议服务器返回一个不少于改时间的缓存资源

  • only-if-cached: 表示客户端要求不必发送具体内容 直接通过客户端缓存进行响应 若缓存miss则直接返回503

Last-Modified & If-Modified-Since

前者为Resp中的Header 表示这个资源的最后修改时间 对于带有该头的 会通过后者 把这一时间在下一次请求相同资源的时候发送回去

假如服务端判断出该资源在之后 并没有修改过 则直接返回如下 表明当前节点没有被修改过

1
2
3
HTTP/1.1 304 Not Modified
Cache-Control: public, max-age=600
Last-Modified: Wed, 8 Apr 2020 15:31:30 GMT

如果有变动的话 则返回如下完整响应

content指具体的消息响应体

1
2
3
4
5
HTTP/1.1 200 OK
Cache-Control: public, max-age=600
Last-Modified: Wed, 8 Apr 2020 15:31:30 GMT

Content

Etag & If-None-Match

同理Etag是服务器的RespHeader 表示的是服务器该资源的唯一标识 对于带有这个Header的资源 当客户端需要再次请求时 会通过If-None-Match把之前收到的资源的唯一标识发送回客户端

假如该唯一标识在两次请求间返回一致 则表示资源没有被修改过 只需返回304即可

1
2
3
HTTP/1.1 304 Not Modified
Cache-Control: public, max-age=600
ETag: "28c3f612-ceb0-4ddc-ae35-791ca840c5fa"

如果该唯一标识有变动 同理就会返回携带最新消息体的完整响应

1
2
3
4
5
HTTP/1.1 200 OK
Cache-Control: public, max-age=600
ETag: "28c3f612-ceb0-4ddc-ae35-791ca840c5fa"

Content

通过这种设定唯一标识的方法 保证了他的一致性是涉及到的方法中所最强的 但是生成唯一标识这个过程 注定了它同时也是上方所提到的方法中 性能最差的

有点CAP味了~

域名解析

DNS的作用 即将域名解析为IP的这一过程

这一过程涉及根、顶级、权威等多级DNS服务器

对于这一过程 每一级DNS服务器之间都是存在缓存的 用于减少无谓的查询消耗

但是这一缓存并不会主动更新:缓存本身会具有一个TTL 过期后会将其对齐 依靠此时的重新获取来保证一致性

这一机制就会带来一个中间人攻击的威胁 在解析域名的时候 “夹带私货” 注入广告之类的

于是出现了另外一种HTTPDNS的工作模式 将原有的服务开放为机遇HTTPS的查询服务(原来的是UDP)

传输链路

整体依然是以发展的视角来看

http一般情况是可认为是基于TCP所构建的,在带来了安全可靠传输等特点的同时,也导致了他的请求头非常重。构建连接的耗时很长,带来很大消耗。

这就导致了当打开一个含有诸多元素的页面时,大量短而小的tcp连接导致了网络性能的瓶颈。

想要减轻这一部分的消耗的话 其中一个方法就连接数优化

  • 减少发生出的请求数量

  • 增加客户端到服务端端可连接数量

这个思路通过将某些资源合并一同发送,减少了TCP连接到建立,某种程度上也带来性能优化。但是会带来其他问题:

但是,通过开发人员的 Tricks 来节省 TCP 连接,这样的优化措施并非只有好处,它们同时也带来了诸多不良的副作用:

如果你用 CSS Sprites 将多张图片合并,意味着任何场景下哪怕只用到其中一张小图,也必须完整加载整个大图片;任何场景下哪怕一张小图要进行修改,都会导致整个缓存失效,类似地,样式、脚本等其他文件的合并也会造成同样的问题。
如果你使用了媒体内嵌,除了要承受 Base64 编码导致传输容量膨胀 1/3 的代价外(Base64 以 8 bit 表示 6 bit 数据),也将无法有效利用缓存。
如果你合并了异步请求,这就会导致所有请求返回时间都受最慢的那个请求的拖累,整体响应速度下降.
如果你把图片放到不同子域下面,将会导致更大的 DNS 解析负担,而且浏览器对两个不同子域下的同一图片必须持有两份缓存,也使得缓存效率的下降。
……
由此可见,一旦在技术根基上出现问题,依赖使用者通过各种 Tricks 去解决,无论如何都难以摆脱“两害相权取其轻”的权衡困境,否则这就不是 Tricks 而是会成为一种标准的设计模式了。

另外一外一个思路就是长连接

即在SSE等场景所能经常见到的
Keep-Alive机制

他最初的实现方法很简约 通过维护一个FIFO队列 在存取数据的时候 入队出队就完事了

但是这导致了一个问题 因为此时的FIFO虽然实现了复用 但是消息之间没有唯一标识符 (不支持并行)

假如将此时的所有资源混杂到一起交叉传输,顺序会乱,客户端就很难区分哪个数据包归属哪个资源了

这就是队首阻塞问题

可以从上方得出 解决方法是为其设定一个标识符

这就是在HTTP2中的改变 他将传输过程中最小粒度的单位由http请求修改为帧

在这里引入了两个新的对象: 流 & 帧

  • 流是指一连串消息帧的集合 在这个场景下本质上可以看作是一次响应

  • 帧是将原有消息体进行再分割的时候所使用到的最小单位 是流的组合形式

注意:此处的流并不等于SSE和WS等中所谓的流,他本质上是一个消息的整体。只是因为由帧组成,所以才称之为“流”

这就带来了很大改进:

修改前:

建立连接 -> header + body -> … -> 销毁连接或等待前者处理完毕

(如果body没发完还要继续发)

修改后:

建立连接 -> header -> body -> body -> … -> 处理完毕等待下一次复用

这就是HTTP/2.0中出名的多路复用

这玩意比八股好记多了

而由于这个原因 原来在一部分小请求下header可能会成为性能瓶颈 然后做的类似上方连接数优化的这些改进措施

似乎全部都没啥意义了 只带来了负面效果

历史遗留问题。。。

传输压缩

首先我们知道

压缩率和区分度(数据的多样性和变化程度)成反比。重复内容多、变化小的数据压缩率高;数据杂乱、随机性强的压缩效果差。

而HTTP中传输的主要内容html,cs,js三件套中基本都是文本数据,所以压缩的收益是非常高的,传输数据量降至原有20%左右

http针对压缩所采用的是分块传输编码,当在响应Header中加入“Transfer-Encoding: chunked”之后。代表该报文将分块编码。Body使用一系列“分块”传输。最后通过一个长度为0的分块表示结束。(类似于一个特殊结束符吧)

快速UDP网络连接

从执之前也有提到过 TCP虽然到来了很多可靠性 鲁棒性上的优势 但是它本身的体量导致了性能瓶颈

而我们所做的优化除了上方所说的诸多长连接多路复用压缩这种 还有最根本的 —— 替换到HTTP底层的TCP

这就是HTTP/3的设计重点

在UDP的基础上 进行修改 最终定义为 quick UDP Internet Connections, 即我们常说的QUIC协议

他抓住了UDP的轻快 结合TCP中某些必要的保险措施 最终促成了 HTTP over QUIC

quic 具体内容TBD

内容分发网络 CDN

CDN 本质上可以说是前方所说的优化措施的一个综合运用案例

不严谨的对齐下定义 可以认为是单网页在多个地理位置下的缓存

按照原文的思路 单个互联网的系统的影响因素有:

  1. 服务器的出口带宽
  2. 客户端的入口带宽
  3. 数据包传输期间经过的运营商节点间的带宽切换
  4. 数据包从服务器到客户端之间的传输时延

以上涉及的4个问题中 1个取决于服务器本身 1个取决于客户端本身 1个取决于途径的运营商 1个取决于传输的速率和地理位置

我们知道在没有CDN协助下 DNS解析域名的过程是需要递归在多个DNS服务器下查询 直至查到本机IP的真实地址

而覆盖了CDN之后 查询该域名的时候会返回另外一个CNAME记录 然后根据这个域名继续往下递归查 会获得多个不同地区的A记录

< CNAME记录是DNS记录的一种 本质上是值将一个域名映射到另外一个域名

而这一过程本质上就是 查资源的时候 去CDN服务器查 然后直接返回CDN缓存节点中的页面数据 当然 这多个不同地理位置的信息不会直接返回到本地DNS中 而是返回到第一次所返回的CNAME的权威DNS服务器(CDN服务器)当中

并且这个查询到过程一般只会出现在第一位用户来访站点的时候 这个时候会执行上方涉及到的DNS查询过程

而之后的查询会直接来到CNAME的权威DNS服务器(CDN服务器)当中 然后根据当前的地理位置等因素 确定具体合适的CDN 然后返回对应的url

然后就直接把这个IP当作源站服务器来进行访问

这一过程可以认为是对客户端是完全透明的

附原文的图 更清晰表述有无CDN下的查询流程

无CDN

有CDN

内容分发更新

上方介绍了CDN的主要作用 可以猜到他的优化方式就是起一个分流 将流量分配到多个网站的副本当中

然后会产生的问题就是老生常谈的缓存一致性: 如何保证主站和CDN缓存分站间的数据一致?

目前主要有两种方式:

用的最广泛的是被动回源————拉取的模式

有点像缓存更新策略中的旁路模式

它是由用户访问触发,全自动,双向透明的资源缓存过程。当某个资源首次被请求的时候,如果CDN缓存中没有,他就会从源站中获取。此时该次请求的响应时间则为源站到CDN到缓存时间 + CDN发送到用户客户端 两个环节的时间之和

所以这个访问相比于普通访问的话 是多了一段请求获取&请求缓存的过程

但是 不代表这一次访问就比用户之间访问源站(不加CDN的情况)慢 因为CDN服务器处的网络条件是高于普通用户的 访问速度普遍比直接访问快 所以要实际权衡两段请求耗费的时间 才能进行比较

这一策略可以看到 是完全不需要服务器和客户端主动对CDN作兼容的 换句话说 被动回源这一模式对客户端和服务器都是完全透明的 便捷性使现在除了秒杀,大流量等特殊场景下 主流使用的都是这种

另外一种方式就是主动分发————相对应的 是主动push的形式

上方的模式是CDN作为客户端向源站服务器拉取 这一模式是源站主动向CDN缓存节点推送最新内容

这也就意味着 推送的时机,内容数量,淘汰策略这些都是需要源站主动进行定义,执行的。所以它对源站不是透明的,而只是对用户一侧透明。这一般用于网站要预载大量资源的场景下。也就是秒杀,双十一等大流量场景了。

CDN应用

这一部分的内容是指CDN除了快速分发资源 还能带来的好处 这里就直接把原文搬过来了 稍微理解一下都挺有意思的

加速静态资源:这是 CDN 本职工作。
安全防御:CDN 在广义上可以视作网站的堡垒机,源站只对 CDN 提供服务,由 CDN 来对外界其他用户服务,这样恶意攻击者就不容易直接威胁源站。CDN 对某些攻击手段的防御,如对DDoS 攻击的防御尤其有效。但需注意,将安全都寄托在 CDN 上本身是不安全的,一旦源站真实 IP 被泄漏,就会面临很高的风险。

协议升级:不少 CDN 提供商都同时对接(代售 CA 的)SSL 证书服务,可以实现源站是 HTTP 协议的,而对外开放的网站是基于 HTTPS 的。同理,可以实现源站到 CDN 是 HTTP/1.x 协议,CDN 提供的外部服务是 HTTP/2 或 HTTP/3 协议、实现源站是基于 IPv4 网络的,CDN 提供的外部服务支持 IPv6 网络,等等。

状态缓存:第一节介绍客户端缓存时简要提到了状态缓存,CDN 不仅可以缓存源站的资源,还可以缓存源站的状态,譬如源站的 301/302 转向就可以缓存起来让客户端直接跳转、还可以通过 CDN 开启HSTS、可以通过 CDN 进行OCSP 装订加速 SSL 证书访问,等等。有一些情况下甚至可以配置 CDN 对任意状态码(譬如 404)进行一定时间的缓存,以减轻源站压力,但这个操作应当慎重,在网站状态发生改变时去及时刷新缓存。

修改资源:CDN 可以在返回资源给用户的时候修改它的任何内容,以实现不同的目的。譬如,可以对源站未压缩的资源自动压缩并修改 Content-Encoding,以节省用户的网络带宽消耗、可以对源站未启用客户端缓存的内容加上缓存 Header,自动启用客户端缓存,可以修改CORS的相关 Header,将源站不支持跨域的资源提供跨域能力,等等。

访问控制:CDN 可以实现 IP 黑/白名单功能,根据不同的来访 IP 提供不同的响应结果,根据 IP 的访问流量来实现 QoS 控制、根据 HTTP 的 Referer 来实现防盗链,等等。

注入功能:CDN 可以在不修改源站代码的前提下,为源站注入各种功能,图 4-7 是国际 CDN 巨头 CloudFlare 提供的 Google Analytics、PACE、Hardenize 等第三方应用,在 CDN 下均能做到无须修改源站任何代码即可使用。
绕过某些“不存在的”网络措施,这也是在国内申请 CDN 也必须实名备案的原因,就不细说了。

这里的绕过不存在… 大概意思就是利用CDN在国内建立某些站点的分站 而作为CDN可以向这些本访问不了的分站获取数据 就可以达到直接访问国内CDN浏览的效果

负载均衡

调度后方的多台机器,以统一的接口对外提供服务,承担此指责的技术组件被称为“负载均衡”

上方所讨论过的CDN中就有负载均衡策略的体现 即在从CNAME查询CDN域名的这一步 从多个域名中 根据当前的影响因素通过某种算法进行一个综合评估 整个过程即可被称为“负载均衡”

类似的过程同样出现在当前各个层次的中间件 甚至是业务测和接入层当中 其往往是和集群这一概念一同出现的

而在形式上可以根据OSI七层模型的分布,将负载均衡分为两种类型:

  • 第四层(传输层) —— 性能高
  • 第七层(应用层) —— 功能强

这里所描述的第四层是多种均衡器工作模式的统称 人话就是 是针对于传输层往下掉多种 通过调度器,转发流量的模式统称 而由于这些操作对于TCP(第四层)来说是透明的 从上往下看还是维持着同一个TCP连接 所以将这些优化方式都统称为四层负载均衡

所以这里先介绍一下数据流量层的措施

数据链路层

这一块原文说的比较详细 笔者重在理解流程 可能省略的会不太严谨

像上方所说的 请求这一过程本身是对用户是透明的 在发送请求的时候 不会直接发送到服务器本机上 而是发送到均衡器上 这个均衡器 起到的就是一个调度分流的作用 就请求打在他旗下的服务器当中 然后对应的 服务器之间返回响应至用户侧

这里修改的值则是mac地址 用户向均衡器的mac地址发起请求 均衡器修改目标mac地址 发起请求

这种模式称为“三角传输模式”,“单臂模式”,“直接路由”

但是可以看出 他这里的均衡器可以分配的服务器仅仅是可位于当前子网内的 无法跨VLAN 这个是均衡器的可达范围限制的

网络层

同样的 在网络层也有一个类似的通过均衡器进行分流的三角模式

但是数据链路层的颗粒度是同一个子网下的不同mac地址

而网络上是直接修改IP 这一模式在网络层被称为IP隧道

本质上是 数据包会被发送到负载均衡器中 然后均衡器会维持当前的原有数据包不变 将原来的Headers + Payload 整体看作一个Payload 然后在这个基础上为他赋值一个新的Header 这一过程本质上称为”隧道化“ 就是在原来的基础上添加一层消息头 以此达到改变消息传送地的原因

这一方式不会对原来的消息包进行任何的修改 也就是说 在客户端视角 也是完全透明的 IP数据包中的响应地址因为不会收到影响 也是直接返回用户 不会被均衡器所变动

所以整体就又形成了一个三角关系

但是这个方法会带来一定的效率损失 即 重新组装消息头部这一过程 同样引用原文的图此处

对应的 还有一种方法就是修改原数据包

当应用请求的时候 现将数据包发给均衡器 均衡器此时修改数据包中的目标ip位置 将原请求赚到真实服务器当中

但是此时 真实服务器必须先将数据返回到均衡器当中 再由均衡器返回至用户

因为由均衡器->真实服务器这个过程中 是变化的 也就是锁对于客户端来说 目标变化了 发送响应的源变化了

在通俗易懂的说 客户端给A发了信息 A转发给了B B响应给客户端 此时本应是理想情况 但是客户端不认识B 不认识此时返回响应的源

所以需要服务器响应回均衡器 此时客户端是认识A这个源的 相对应的 就能接受它所返回的响应

这一负载均衡的模式被称为NAT模式 和计网中出现的NAT是一个东西(网络地址转换 —— Network Address Translation) 本质就是充当家里,公司,学校的路由器作用 —— 在这些环境下 直接访问路由器 便可以访问外部的网站 就是因为路由器本身起到的路由转发的功能

这种模式下的缺陷也很显而易见 很依靠均衡器 本身的性能 因为在这个模式下 它单机处理代表单个服务集群(所有服务进行应答) 而它本身的一个出口和入口带宽都是固定的 —— 多个设备抢网络 这本质其实就是日常中网络卡顿的一个影响因素

应用层

上方所涉及到的四层负载均衡工作模式都属于 “转发” 将原来的数据包进行少量修改或不修改 直接路由到对应的真实服务器当中 而这里所提到的就是七层——应用层上的工作模式 它的工作模式是代理 通过发起两个独立的请求来达成目的

引用一下原文的图

而代理又可以根据哪一方可以看见(对哪一方不透明)而分成三类 分别是正向代理、反向代理、透明代理

  • 正向代理:服务端透明 客户端可知 科学上网所使用到的软件 如clash小火箭 本质为本机发送流量至代理服务器当中 代理服务器向后端发起请求 后端向代理服务器返回结果 然后再将结果返回本机当中

  • 反向代理:服务端可知 客户端透明 典型例子就是Nginx了 客户端发送服务到Nginx服务器当中 在其中进行负载均衡 反向代理等操作之后 再返回到客户端当中

  • 透明代理:服务端透明 客户端透明 这个可以理解为各公司中的内网翻墙 或者是openwrt等方式达成的路由器翻墙 通过一个中间件(一般来说可以说是均衡器) 对流量进行重定向 就可以达成在两者都透明的前提下代理

代理作为应用层负载均衡的主要体现形式 如果只论性能 他是包比不过的 因为无论是正向还是反向代理 他们的本质上都再次创建了对应的TCP连接 而握手的时间 还有销毁啥的 这个成本都是很大的

当然这个代理方可以做一些优化 例如说Nginx多次转发后端间 这一过程的TCP连接就是可以复用的 但是从性质上决定了 其普遍性能就是没有四层负载均衡快

else

这里简易补充下Clash代理的运行机制 使用的时候可以看到有个代理端口的选项 一般是7890 这个端口可以粗略认为是clash作为代理服务器的监听端口

在规则模式下 当有流量来的时候 clash首先判断该url是否已配置 即判断它是否需要代理 如果不需要代理 则直接范围对应对目标 如果需要代理 则由代理服务器截取 然后重新构造一个TCP连接 请求对应对后端服务器 返回的时候 返回到clash代理服务器 然后同样通过监听端口将响应数据返回回来

TODO TUN等模式 埋个坑 感觉有点扯远了这里先不展开

策略&实现

这里就直接把原文的摘过来了

说白了 就是

轮询、权重、随机、效率

一切围绕效率出发 根据前三者为方向发展出 不同的负载均衡算法

  • 轮循均衡(Round Robin):每一次来自网络的请求轮流分配给内部中的服务器,从 1 至 N 然后重新开始。此种均衡算法适合于集群中的所有服务器都有相同的软硬件配置并且平均服务请求相对均衡的情况。
  • 权重轮循均衡(Weighted Round Robin):根据服务器的不同处理能力,给每个服务器分配不同的权值,使其能够接受相应权值数的服务请求。譬如:服务器 A 的权值被设计成 1,B 的权值是 3,C 的权值是 6,则服务器 A、B、C 将分别接收到 10%、30%、60%的服务请求。此种均衡算法能确保高性能的服务器得到更多的使用率,避免低性能的服务器负载过重。
  • 随机均衡(Random):把来自客户端的请求随机分配给内部中的多个服务器,在数据足够大的场景下能达到相对均衡的分布。
  • 权重随机均衡(Weighted Random):此种均衡算法类似于权重轮循算法,不过在分配处理请求时是个随机选择的过程。
  • 一致性哈希均衡(Consistency Hash):根据请求中某一些数据(可以是 MAC、IP 地址,也可以是更上层协议中的某些参数信息)作为特征值来计算需要落在的节点上,算法一般会保证同一个特征值每次都一定落在相同的服务器上。一致性的意思是保证当服务集群某个真实服务器出现故障,只影响该服务器的哈希,而不会导致整个服务集群的哈希键值重新分布。
  • 响应速度均衡(Response Time):负载均衡设备对内部各服务器发出一个探测请求(例如 Ping),然后根据内部中各服务器对探测请求的最快响应时间来决定哪一台服务器来响应客户端的服务请求。此种均衡算法能较好的反映服务器的当前运行状态,但这最快响应时间仅仅指的是负载均衡设备与服务器间的最快响应时间,而不是客户端与服务器间的最快响应时间。
  • 最少连接数均衡(Least Connection):客户端的每一次请求服务在服务器停留的时间可能会有较大的差异,随着工作时间加长,如果采用简单的轮循或随机均衡算法,每一台服务器上的连接进程可能会产生极大的不平衡,并没有达到真正的负载均衡。最少连接数均衡算法对内部中需负载的每一台服务器都有一个数据记录,记录当前该服务器正在处理的连接数量,当有新的服务连接请求时,将把当前请求分配给连接数最少的服务器,使均衡更加符合实际情况,负载更加均衡。此种均衡策略适合长时处理的请求服务,如 FTP 传输。
  • …………