4.凤凰架构:构建可靠的大型分布式系统 --- 透明多级分流系统
Posted enlyhua
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了4.凤凰架构:构建可靠的大型分布式系统 --- 透明多级分流系统相关的知识,希望对你有一定的参考价值。
第4章 透明多级分流系统
对系统进行流量规划时,有2条简单、普适的原则能指导我们进行设计:
a) 第一条原则是尽可能减少单点部件。如果某些单点是无可避免的,则应尽最大限度减少到达单点部件的流量。
b) 另外一条关键的原则是 奥卡姆剃刀原则。作为一名架构设计者,你应对多级分流的手段有全面的理解与充分的准备,同时清晰的意识到这些设施不是越多越好。在
实际构建系统时,你应当在有明确需求、真正必要的时候再去考虑部署它们。不是每一个系统都追求高并发、高可用的,根据系统的用户量、峰值流量和团队本身的技术
与运维能力来考虑如何部署这些设施才是合理的做法,在满足需求的前提下,最简单的系统就是最好的系统。
4.1 客户端缓存
浏览器的缓存机制几乎是在万维网刚刚诞生时就存在的,在HTTP协议设计之初变确定了服务端与客户端之间"无状态(Stateless)"的交互原则,即每次
请求都是独立的,每次请求无法感知也不能依赖另外一个请求的存在,这既简化了HTTP服务器的设计,也为其水平扩展能力留下广茂的空间。但无状态并非只有
好的一面,由于每次请求都是独立的,服务端不保存此前请求的状态和资源,所以也不可避免的导致其携带了重复的数据,导致网络性能降低。http协议对此
问题的解决方案是客户端缓存,在http 1.0到1.1,再到2.0版本的演进中,逐步形成了现在被称为"状态缓存" "强制缓存" 和 "协商缓存" 的http缓存
机制。
状态缓存是指不经过服务器,客户端直接根据缓存信息对目标网站的状态判断,以前只有 301/Moved Permancently(永久重定向)这一种,后来在
RFC6797 中增加了HSTS(HTTP Strict Transport Security)机制,用于避免 301/302 跳转 https 时可能产生的降级中间人攻击,这也属于另外
一种缓存。
无论协商缓存还是强制缓存,原理都是在服务器对客户端请求的响应中附带一些条件,要求客户端在遇到相同的请求时,先判断一下条件是否成立,如果
满足,就直接用上一次服务器给与的响应来代替,不必重新访问。这两种机制的区别在于它们采用了不同的判断条件来解决资源在客户端和服务器间的一致性
问题。
4.1.1 强制缓存
根据约定,强制缓存在浏览器的地址输入、页面链接跳转、新开窗口、前进和后腿中均可生效,但在用户主动刷新页面时应当自动失效。HTTP协议中设有
以下两类Header实现强制缓存:
1.Expires
Expires 是 http/1.0 协议中开始提供的Header,后面跟一个截止的时间参数。当服务器返回某个资源时带有该header,意味着服务器承若
在截止时间之前不会资源不会发生变动,浏览器可直接采用该缓存,不再重新发送请求。
Expires 是http协议最初版本中提供的缓存机制,设计非常直观易懂,但考虑的并不周全,它至少存在以下几个明显的问题:
1.受限于客户端的本地时间;客户端可以修改本地时间;
2.无法处理涉及用户身份的私有资源;譬如,某些资源被登录用户缓存在自己的浏览器上是合理的,但如果被代理服务器或者内容分发网络缓存
起来,则可能被其他未认证的用户所获取;
3.无法描述"不缓存"的语义。譬如,浏览器为了提高性能,往往会自动在档次会话中缓存某些MIME类型的资源,在http 1.0的服务器中就
缺乏强制手段不允许浏览器缓存某个资源。以前为了实现这类功能,通常不得不使用脚本,或者手工在资源后面增加时间戳(譬如,
xx.js?t=123456789)来保证每次资源都会重新获取。
关于"不缓存"的语义,在http/1.0中其实预留了"Pragma:no-cache"来表达,但Pragma参数在http/1.0中并没有确切描述其具体行为,随后
被http/1.1 中出现的Cache-Control所替代。尽管现在主流浏览器都支持Pragma,但行为仍然是不确定的,并没有什么实际价值。
2.Cache-Control
Cache-Control 是 http/1.1 协议中定义的强制缓存header,它的语义比 Expires 丰富很多了,如果Cache-Control和Expires同时
存在,并且语义存在冲突的话,规定以Cache-Control为准。
Cache-Control 在客户端的请求header或者服务器的响应header中都可以存在,它定义了一系列的参数,且允许自行扩展,其中标准的参数
主要有以下几个:
1.max-age和s-maxge
max-age 后面跟一个以秒为单位的数字,表明相对于请求时间(在Date Header中会标明请求时间)多少秒内缓存是有效的,即多少秒以内
不需要重新从服务器中获取资源。相对时间避免了Expires中采用的绝对时间可能受客户端时钟影响的问题。s-maxage中的s是"share"的缩写,
意味着"共享缓存"的有效时间,即允许被CDN、代理等持有的缓存有效时间,用于提示CDN这类服务器应在合适让缓存失效。
2.public和private
指明是否涉及用户身份的私有资源,如果是public,则可以被代理、cdn等缓存;如果是private,则只能由用户的客户端进行缓存;
3.no-cache和no-store
no-cache指明该资源不应该被缓存,哪怕是同一个会话中对同一个url地址的请求,也必须从服务器获取,令强制缓存完全失效,但此时下一节
中的协商缓存机制依然是生效的;no-store不会强制会话中相同url资源的重复获取,但禁止浏览器、cdn等形式保存该资源。
4.no-transform
禁止以任何形式修改资源。譬如,某些cdn、透明代理支持自动gzip压缩图片或文本,以提升网络性能,而no-transform禁止了这样的行为,
它不允许Content-Encoding、Content-Range、Content-Type 进行任何形式的修改。
5.min-fresh和only-if-cached
这2个参数是仅用于客户端的请求header。min-fresh 后面跟着一个以秒为单位的数字,用于建议服务器返回一个不少于该时间的缓存资源(
即包含max-age且不少于min-fresh的数字)。only-if-cached 表示客户端要求不给它发送资源的具体内容,此时客户端仅能使用实现缓存的
资源来进行相应,若缓存不能命中,就直接返回503/Service Unavailable错误。
6.must-revalidate和proxy-revalidate
must-revalidate 表示在资源过期后,一定要从服务器中获取,即超过了 max-age 时间后,就等同于 no-cache的行为,
proxy-revalidate 用于指示代理、cdn等设备资源过期后的缓存行为,除对象不同外,语义与muast-revalidate完全一致。
4.1.2 协商缓存
强制缓存是基于时效性的,但无论是人还是服务器,其实大多数情况下并没有什么把握去承若某项资源多久不会发生变化。另外一种基于变化检测的缓存
机制,在一致性上会有比强制缓存更好的表现,但需要一次变化检测的交互作用,性能上就会略差一点。这种基于检测的缓存机制,通常被称为"协商缓存"。
另外,应注意在http的协商缓存与强制缓存并没有互斥性,这两套机制是并行的。譬如,当强制缓存存在时,直接从强制缓存中返回资源,无需进行变动检查。
而当强制缓存超过时效,或者被禁止(no-cache / must-revalidate)时,协商缓存扔可以正常工作。协商缓存有两种变动检查机制,分别是根据资源的
修改时间进行检查,以及根据资源唯一标识是否发生变化进行检查,它们都是靠一组成对出现的请求,响应header来实现的。
1.Last-Modified 和 If-Modified-Since
Last-Modified 是服务端的响应header,用于告诉客户端这个资源的最后修改时间。对于带有这个header的资源,当客户端需要再次请求时,
会通过If-Modified-Since 把之前受到的资源最后的修改时间发回服务端。
如果此时服务端发现资源在该时间后没有被修改过,就返回一个 304/Not Modified 的响应,无需附带消息体,即可达到节省流量的目的。
如果此时服务端发现资源在该时间之后有变动,就会返回 200/OK 的完整响应,在消息体中包含最新的资源。
2.ETag 和 If-None-Match
ETag 是服务端的响应header,用于告诉客户端这个资源的唯一标识。http服务端可以根据自己的意愿来选择如何生成这个标识,譬如apache
服务端的ETag值默认是对文件的索引节点(INode)、大小和最后修改时间进行哈希计算后得到的。对于带有这个header的资源,当客户端需要再次请求
时,会通过 If-None-Match 把之前收到的资源唯一标识发送回服务端。
如果此时服务端发现资源的唯一标识一致,就返回一个 304/Not Modified 的响应,无需附带消息体,即可达到节省流量的目的。
如果此时服务端发现资源在该时间之后有变动,就会返回 200/OK 的完整响应,在消息体中包含最新的资源。
ETag 是http中一致性最强的缓存机制,譬如,Last-Modified 标注的最后修改只能精确到秒,如果某些文件在1s内被修改多次的话,它将不能
准确标注文件的修改时间;又如果某些文件会被定期生成,可能内容并没有什么变化,但Last-Modified却改变了,导致文件无法有效使用缓存,这些
情况Last-Modified 都有可能产生资源一致性的问题,只能使用ETag解决。
ETag 也是http性能最差的缓存机制,在每次请求时,服务端都必须对资源进行哈希计算,相比简单获取一下修改时间,开销要大很多。ETag和
Last-Modified是允许一起使用的,服务器会优先验证ETag,在ETag一致的情况下,再去对比Last-Modified,这是为了防止有一些http服务单
未将文件修改日志纳入哈希范围内。
到了这里,http 的协商缓存机制已经能很好的适用于通过url获取单个资源的场景,为什么强调"单个资源"呢?在http协议的设计中,一个url地址
是有可能提供多份不同版本的资源的,譬如,一段文字的不同语言版本,一个文件的不同编码格式,一份数据的不同压缩方式等。因此,针对请求的缓存机制,
也必须能够提供对应的支持。为此,http协议设计了以 Accept-*(Accept, Appcpt-Language, Accept-Charset, Accept-Encoding)开头的
一套请求header,这些header被称为http的内容协商机制,与之对应的,对于一个url能够获取多个资源的场景,缓存也需要有明确的标识来获知根据什么
内容返回给用户正确的资源。此时就要用到 Vary Header,Vary 后面应该跟随一组其他header的名字,如:
HTTP/1.1 200 OK
Vary: Accept, User-Agent
以上响应的含义是根据MIME类型和浏览器类型来缓存资源,获取资源时也需要根据请求header中对应的字段来筛选出合适是资源版本。
根据约定,协商缓存不仅在浏览器的地址输入,页面链接跳转,新开窗口,前进,后退中生效,并且在用户主动刷新页面(F5)时也是生效的,只有用户强制
刷新缓存(Ctrl+F5)或者明确禁止缓存(譬如在DevTools中设定)时才会失效,此时客户端向服务端发出的请求自动带有"Cache-Control: no-cache"。
4.2 域名解析
如访问 www.icyfenix.com.cn,需要经历一个递归的过程。首先dns会将域名还原为 "www.icyfenix.com.cn.",注意多了一个点".",它是
".root"的含义。早期的域名必须带有这个点才能被dns正确解析,如今几乎所有的操作系统、dns服务器都可以自动补上这个点,下面开始按如下步骤解析:
1.客户端先检查本地dns缓存,查看是否存在活着的该域名的地址记录。dns是以存活时间(TTL)来衡量缓存的有效情况的,所以,如果某个域名改变
了ip地址,dns服务器并没有任何机制去通知缓存了该地址的机器去更新或者失效缓存,只能依靠ttl超期后重新获取来保证一致性。后续每一级dns
查询都会有类似的缓存查操作。
2.客户端将地址发送给本机操作系统中配置的本地dns(Local DNS),这个本地dns服务器可以由用户手工设置,也可以在dhcp分配或者拨号的时候从
PPP服务器中自动获取。
3.本地dns收到查询请求后,会按照 "是否有 www.icyfenix.com.cn 的权威服务器" -> "是否有 icyfenix.com.cn 的权威服务器" ->
"是否有 com.cn 的权威服务器" -> "是否有 cn 的权威服务器"的顺序,依次查询自己的地址记录,如果都没有查询到,就会一直到最后点号代表的
根域名服务器为止。
a) 权威域名服务器(Authoritative DNS)
负责翻译特定域名的dns服务器,"权威"意味着域名应该翻译出怎么样的结果是由这个服务器决定的。dns翻译域名时无需像查电话本一样刻板
的一对一翻译,根据来访机器,网络链路,服务内容等各种信息,可以玩出很多花样。
b) 根域名服务器(Root DNS)
固定的,无需查询的定级域名(Top-Level Domain)服务器,可以默认它们已内置在操作系统代码中。全世界一共13组根域名服务器(注意
不是13台),每一组根域名通过任播的方式建立起一大群镜像,如今已经超过1000台根域名的镜像了。选择13是由于dns主要采用udp(在传输需要
稳定的时候也可以采用tcp)来进行数据交换,为分片的udp数据包在ipv4下的最大有效值为512字节,最多可以存放13组地址记录。
4.现在假设本地dns是全新的,上面不存在任何域名的权威服务器记录,所以当dns查询请求按步骤3的顺序一直查到根域名服务器之后,它将会得到"cn的
权威服务器"的地址记录,然后通过 "cn的权威服务器" 得到 "com.cn 的权威服务器",最后得到能够解释"www.icyfenix.com.cn"的权威服务器
地址。
5.通过"www.icyfenix.com.cn 的权威服务器地址",查询 www.icyfenix.com.cn 的地址记录。地址记录并不一定是指ip地址,在rfc规范中
定义的地址记录类型已经有十几种,譬如ipv4下的ip地址为 A记录,ipv6下的 AAAA记录,主机别名为cname的记录等等。
前面提到过,每种记录类型中还可以包含多条记录,以一个域名下配置多条不同的A记录为例,此时权威服务器可以根据自己的策略来进行选择。典型的
应用是智能线路:根据访问者所处的不同地区,不同服务商等因素来确定返回最合适的A记录,将访问者路由到最合适的数据中心,达到智能加速的目的。
dns系统多级分流的设计使得dns系统能够承受住全球网络流量的不间断的攻击,但也并非全无特点。典型的问题是响应速度。
有一种"dns预取"(DNS Prefetching)的前端优化手段来避免这类问题:如果网站后续要使用来自其他域的资源,那就在网页加载时生成一个link
请求,促使浏览器提前对该域名进行解释,譬如下面代码:
<link rel="dns-prefetch" href="//domain.not-icyfenx.cn">
而另外一种可能更严重的缺陷是dns的分级查询意味着每一级都有可能受到中间人攻击的威胁,产生被劫持的风险。要攻陷位于递归链条顶层的服务器(
譬如根域名服务器,cn权威服务器)的链路是比较困难的,它们都有专业的安全防护措施。但很多位于递归底层或者来自本地运营商的本地dns服务器的安全防护
则相对松懈,甚至不少地区的运营商自己就会主动劫持,专门返回一个错的ip地址,通过在这个ip上代理用户请求,给特定类型资源(主要是html)注入广告。
为此,出现了httpdns(也称为 DNS Over HTTP)。它将原本的dns解析服务器开放为一个基于https协议的查询服务,替代基于udp传输协议的dns域名
解析,通过持续代替操作系统直接从权威dns或者可靠的本地dns获取解析数据,从而绕过传统本地dns。这样做的好处是"免去了中间商赚差价"的环节,不怕
底层的域名劫持,有效的避免了dns不可靠导致的域名生效缓慢,本地ip不准确,产生的智能线路切换错误等。
4.3 传输链路
很多人第一直觉可能会认为传输链路是开发者完全不可控的因素,网络路由跳点的数量、运营商铺设线路的质量决定了线路带宽的大小、速率的高低。然后并非
如此,程序发出的请求能够与应用层、传输层协议提出的方式相匹配,也会对传输的效率有极大影响。最容易体现这个点的是前端网页的优化技巧,比如雅虎YSlow-23。
https://cloud.tencent.com/developer/article/1015874
4.3.1 连接数优化
http 是以tcp 为传输层的应用层协议,但 http over tcp 这种搭配只能说是tcp在当今网络中统治地位造成的结果,不能说它们两者就是适配的。
http传输对象的主要特征就是数量多、时间短、资源小、切换快。另外一方面,tcp协议要求必须在三次握手完成之后才能开始数据传输,这可能是一个"百毫秒"
为计时尺度的事件;另外,tcp有慢启动的特点,使得刚刚建立时传输效率是最低的,后面再逐步加速直至稳定。tcp本身是面向长时间、大数据传输来设计的,
在长时间的尺度下,它建立连接的高昂成本才不至于称为瓶颈,它的稳定性和可靠性的优势才能体现出来。
开发人员节省tcp连接的优化措施并非只有好处,也有不良的副作用:
1.如果用雪碧图将多张图合并,如果只用其中一张小图,也必须完整加载整张大图;修改的话,会导致整个缓存失效;
2.如果你使用了媒体内嵌,除了要承受base64编码导致传输容量膨胀了1/3的代价之外,也将无法有效利用缓存;
3.如果你合并了异步请求,这会导致所有的请求的返回时间都受最慢的那个请求的拖累;
4.如果你把图片放到不同的子域下面,将会导致更大的cdn解析负担,而且浏览器对两个不同子域下面的同一张图必须持有2份缓存,也使得缓存的利用率下降。
另外一方面,不是没有尝试去解决 连接成本过高的问题。http/1.0 就已经支持连接复用技术,即持久连接,也称为连接Keep-Alive机制。持久连接
的原理是让客户端对同一个域名长期持有的一个或者多个不会用完即断的tcp连接。典型的做法是在客户端维护一个FIFO队列,在每次去完数据之后一段时间内
先不断开连接,以便获取下一个资源时复用,避免创建tcp连接的成本。
连接复用不是完美的,最明显的副租用就是"队头阻塞"。
2014年,发布了"HTTP 管道"的复用技术。
队头阻塞问题一直持续到了第二代HTTP协议,即 http/2 发布后才算被比较完美的解决。在 http/1.x 中,http请求就是传输过程中最小粒度的信息
单位了,所以如果将多个请求切碎,再混杂在一块传输,客户端势必难以分辨、重组出有效信息。而在http/2 中,帧(Frame)才是最小粒度的信息单位,它可以
用来描述各种数据,譬如请求的Headers,Body,或者用来做控制标识,譬如打开流,关闭流。这里说的流(Stream)是一个逻辑上的数据通道的概念,每个帧
都附带一个流ID来标识这个帧属于哪个流。这样,在同一个tcp连接中传输的多个数据帧就可以根据流ID轻易区分开来,在客户端毫不费力气的将不同的流中的
数据重组不同http请求和响应报文来。这是http/2 的最重要的技术特征之一,被称为http/2 的多路复用(HTTP/2 Multiplexing)技术。
有了多路复用的支持,http/2 就可以对每个域名只维持一个tcp连接并以任意顺序传输任意数量的资源了,这样减轻了服务器的压力,也不需要开发者
去考虑域名分片这种事去突破浏览器对每个域名最多6个连接数的限制了。更重要的是,没有了连接数限制,就无需可以压缩http的请求数了。
在http传输中,header占传输成本的比重是相当大的。但以下几种因素决定了通过合并资源文件减少请求数,对节省header成本并没有太大的帮助:
1.header的传输在ajax(尤其是只返回少量数据的请求)请求中比重是很大的开销,但在图片、样式、脚本这些静态资源的请求中,通常并不占主要
地位。
2.在http/2 中header的压缩的原理是基于字典编码的信息复用。简而言之,同一个连接上产生的请求和响应越多,动态字典累积的越全,头部压缩
效果也就越好。所以http/2 是单域名单连接的机制,合并资源和域名分片反而不利于提高header压缩效果。
3.与http/1.x相比,http/2 本身变得更适合传输小资源,譬如传输1000张10kb的小图,http/2肯定更快,但传输10张1000kb的大图,则
大概率http/1.x会更快些。这是tcp连接数量(相当于多点下载)的影响,但更多的是tcp协议可靠传输机制导致的,一个错误的tcp包会导致所有的
流都必须等待这个包重传成功,这是http/3要解决的问题。因此,把小文件合并成大文件,在http/2 下是毫无意义的。
4.3.2 传输压缩
遗留问题:如何不以断开tcp连接为标志来判断资源已经传输完毕?
http很早就支持GZip压缩,因为http传输的内容主要是文本数据,比如HTML、CSS、Script等,而对于文本数据启用压缩的收益是非常高的,传输数据
量一般会降至原有的20%左右。对于那些不适合压缩的资源,web服务器能根据MIME类型自动判断是否对响应进行压缩,这样,已经采用过压缩算法存储的资源,
如jpeg,png图片,便不会被二次压缩,空耗性能。
压缩与之前提到的节省tcp的持久连接机制是存在冲突的。在网络时代早期,服务器的处理能力比较弱,为了启用压缩,会把静态资源先预先压缩为.gz
文件的形式存放起来,当客户端可以接收压缩版本的资源时(请求的header中包含 Accept-Encoding: gzip)就返回压缩版本(响应的header中包含
Content-Encoding:gzip),否则返回未压缩的原始版本,这种方式被称为"静态预压缩"(Static Precompression)。而现代web服务器处理能力大幅
提升,已经没有人采用麻烦的预压缩了,都是由服务器对符合条件的请求在输出时进行"即时压缩"(On-The-Fly-Compression),整个压缩过程全部在内存
的数据流中完成,不必等待资源压缩完成后再返回响应,这样可以显著提高"首字节时间"(Time To First Byte, TTFB),改善web性能体验。而这个过程
唯一不好的地方就是服务器再也没有办法给出 Content-Length 这样响应header了,因为输出header时服务器还不知道压缩后资源的确切大小。
到这里,知道及时压缩和持久连接的冲突在哪里了吗?持久连接机制不再依靠tcp连接是否关闭来判断资源请求是否结束,它会重用同一个连接以便向同一个
域名请求多个资源,这样,客户端就必须要有除了关闭连接之外的其他机制来判断一个资源什么时候算传递完毕,这个机制最初(在http/1.0)就只有
Content-Length,即依靠请求header中明确给出资源的长度判断,传输到达该长度即宣告一个资源的传输已经结束了。由于启用即时压缩后就无法给出
Content-Length了,如果是http/1.0的话,持久连接和即时压缩只能二选一,事实上在http/1.0中对两者都支持,却默认都不启用。依靠
Content-Length来判断传输结束的缺陷,不仅仅在即时压缩这个场景,譬如对动态内容(ajax,php等输出),服务器也同样无法知道Content-Length。
在http/1.1 中修复了这个问题,增加了"分块传输编码"(Chunked Transfer Encoding)的资源结束判断机制,彻底解决了Content-Length
与持久连接的冲突问题。原理很简单:在响应header中加入"Transfer-Encoding: chunked"之后,就代表这个响应报文将以分块编码。此时,报文中
body需要改为一系列"分块"来传输。每个分块包含十六进制的长度值和对应长度的数据内容,长度值独占一行,数据从下一行开始,最后一个长度值为0的
分块表示资源结束。
4.3.3 快速UDP网络连接
HTTP是应用层的协议而不是传输层协议,它的设计原本不应该过多的考虑底层的传输细节,从职责上讲,持久连接、多路复用、分块编码这些能力,已经
或多或少超过了应用层的范畴。要从根本上改进http,必须直接替换掉http over tcp的根基,这是HTTP/3协议的设计重点。
2013年,Google在它的服务器及Chrome浏览器上同时启用名为"快速udp网络连接"(Quick UDP Internet Connection,QUIC)的全新传输协议,
(google版本的叫 gQUIC,规范后的叫 iQUIC),不仅能满足http协议,日后还支持smtp,dns,ssh,telnet,ntp等其他上层协议。2018年末,IEIF
正式推出了 HTTP over QUIC 使用 HTTP/3 的版本号,将其确立为最新一代的互联网标准。
UDP没有丢包自动传输的特性,因此 QUIC 的可靠传输能力并不是底层协议提供的,而是完全自己实现的。由QUIC自己实现的好处是能对每个流做单独的
控制,如果在一个流中发生错误,协议栈仍然可以独立的继续为其他流提供服务。这对提高易出错链路的性能非常有用,因为在大多数情况下,tcp协议接收到
数据包丢失或损坏通知之前,可能已经收到了大量正确的数据包,但是在纠正错误之前,其他的正常请求都会等待甚至被重发,这也是前面提到的http/2未能
解决传输大文件慢的根本原因。
QUIC 的另外一个设计目的是面向移动设备的专门支持,由于之前tcp,udp传输协议设计时根本想不到今天移动设备流行的场景,因此没有专门的支持。
QUIC在移动设备上的优势体现在网络切换时的响应速度上,譬如当移动设备在不同wifi热点切换时,或者从wifi切换到移动网络时,如果使用tcp,现在
所有的连接必然超时、中断,然后根据需要重新创建。这个过程必然带来很高的延迟,因为超时和重新握手都需要大量的时间。为此,QUIC提出了连接标识符
的概念,该标识符可以唯一的标识客户端与服务器之间的连接,而无需依靠ip地址。这样,切换网络后,只需要向服务器发送一个包含此标识符的数据包即可
重用既有的连接,因为即使用户的ip地址发生变化,原始连接的连接标识符依然是有效的。
4.4 内容分发网络
如果抛开其他影响服务质量的因素,仅从网络传输的角度看,一个互联网系统的速度取决于下面4个因素:
1.网站服务器接入网络运营商的链路所能提供的出口带宽;
2.用户客户端接入网络运营商的链路所能提供的入口带宽;
3.从网站到用户经过的不同运营商之间的互连节点的带宽,一般来说两个运营商之间只有固定的若干个节点是互通的,所有跨运营商之间的交互都要经过
这些节点;
4.从网站到用户的物理链路传输时延。打游戏的都知道,延时(ping值)比带宽更重要。
以上4个,除了第二个只能通过更换一个更好的宽带才能改善之外,其余3个都能通过内外分发网络才能显著改善。一个运作良好的cdn,能为互联网系统解决跨
运营商、跨地域物理距离所导致的延时问题,能为网站流量带宽起到分流、减负作用。
4.4.1 路由解析
cdn 路由解析的具体工作如下:
1.架设好"icyfenix.cn."的服务器后,在你的cdn服务商上将服务器ip地址注册为源站,注册后你会得到一个cname,即本例中
的"icyfenix.cn.cdn.dnsv1.com.";
2.在你购买的dns服务商上将得到的cname注册为一条cname记录;
3.当第一位用户来访时,将首先发生一次未命中缓存的dns查询,域名服务商解析出cname后,将返回给本地dns,之后链路解析的主导权就开始由
内容分发网络的调度服务接管了;
4.本地dns查询cname时,由于能解析该cname的权威服务器只有cdn服务商所架设的权威dns,这个dns服务将根据一定的均衡策略和参数,若
拓扑结构、容量、延时等,在全国各地能提供服务的cdn缓存节点挑选一个最合适的,并将它的ip代替源站ip地址,返回给本地dns;
5.浏览器从本地dns拿到ip地址后,将ip当做源站服务器进行访问,此时该ip的cnd节点上可能有,也可能没有缓存过源站的资源;
6.经过内容分发后的cdn节点,就有能力代替源站向用户提供所请求的资源了。
4.4.2 内容分发
cdn 如何获取源站资源 和 如何管理(更新)资源,cdn获取源站资源的过程被称为"内容分发",目前主要有两种主流的方式:
1.主动分发(Push)
分发由源站主动发起,将内容从源站或者其他资源库推送到用户边缘的各个cdn缓存节点上。这个推送没有什么业界标准可言,可以选择任何
传输方式(http,ftp,p2p等等)、任何推送策略(满足特定条件、定时、人工等等)、任何推送时间。由于内容分发需要源站、cdn服务双方提供程序
api接口层面的配合,所以它对源站并不是透明的,只对用户一侧单向透明。主动分发一般用于网站预加载大量资源的场景。譬如双十一,淘宝、京东
等各个网络商城会把未来活动所需要的资源推送到cdn缓存节点中,特别常用的甚至会直接缓存到你的手机app存储空间或者浏览器的localStorage
上。
2.被动回源(Pull)
被动回源由用户访问所触发,是全自动、双向透明的资源缓存过程。当某个资源首次被用户请求的时候,若cdn缓存节点发现自己没有该资源,
就会实时从源站获取,这时资源的响应时间可粗略认为是资源从源站到cdn缓存节点的时间,加上资源从cdn发送到用户的时间之和。因此,被动
回源的首次访问通常比较慢(但由于cdn的网络条件一般远高于普通用户,并不一定比用户直接访问源站更慢),不适合应用于数据量较大的资源。
被动回源的优点是可以做到双向透明,不需要源站在程序上做任何配合。比较适合小型站点使用cdn服务的主流选择。
cdn的缓存管理并没有通用的准则。现在,最常见的做法是超时被动失效和手工主动失效相结合。超时被动失效是指给与缓存资源一定的生存期,超过了
生存期就在下一次请求时重新被动回源一次。而手工主动失效是指cdn服务商一般会提供处理失效缓存的接口,在网站更新时,由持续集成的流水线自动调用
该接口来实现缓存更新。
4.4.3 CDN应用
1.加速静态资源分发
2.安全防御
cdn广义上可视作网站的堡垒机,源站只对cdn提供服务,由cdn来对外界其他用户提供服务,这样恶意的攻击者就不容易直接威胁源站。cdn对某些
攻击手段的防御,如对DDoS攻击的防御尤其有效。
3.协议升级
不少cdn提供商都同时对接(代售CA)SSL证书服务,可以实现源站是基于http协议的,而对外开放的网站是基于https的。同理,可以实现源站到
cdn是http/1.x 协议,cdn提供的外部服务是 http/2或者 http/3 协议;实现源站是基于ipv4的,cdn提供的外部服务支持ipv6网络等等。
4.状态缓存
cdn不仅可以缓存源站的资源,还可以缓存源站的状态,譬如可以通过cdn缓存源站的 301/302 状态让客户端直接跳转,也可以通过cdn开启HSTS、
通过cdn进行OCSP装订加速ssl证书访问,等等。有一些情况下甚至可以配置cdn对任意状态码(譬如404)进行一段时间缓存,以减轻源站压力。
5.修改资源
cdn可以在返回资源给用户的时候修改资源的任何内容,以实现不同目的。譬如,可以对源站未压缩的资源自动压缩并修改Content-Encoding,以
节省用户的网络带宽消耗,可以对源站未启用客户端缓存的内容加上缓存header,自动启用客户端缓存等,可以修改CORS的相关header,为源站不支持
跨域的资源提供跨域能力,等等。
6.访问控制
cdn 可以实现ip 黑/白名单功能,如根据不同的ip提供不同的响应结果,根据ip的访问流量来实现QoS控制,根据http的 Referer来实现防盗链
等等。
7.注入功能
cdn可以在不修改源站代码的情况下,为源站注入各种功能。
4.5 负载均衡
4.5.1 数据链路层负载均衡
现在说的"四层负载均衡"其实是多种均衡器工作模式的统称,"四层"是说这些工作模式的特点是维持同一个tcp连接,而不是说它只工作在四层。事实上,
这些模式主要工作在第二层(数据链路层,改写MAC地址)和第三层(网络层,改写IP地址)上,单纯只处理第四层(传输层,改写tcp,udp等协议的内容和端口)
的数据无法做到负载均衡的转发,因为OSI的下三层是媒体层,上四层是主机层,既然流量都到达主机了,就谈不上什么流量转发了,最多只能代理。
链路层负载均衡所做的工作,是修改请求的数据帧中的MAC目标地址,让用户原本发送给负载均衡器的请求的数据帧,被二层交换机根据新的MAC目标地址转发到
服务器集群中对应的服务器(即真实服务器,Real Server)的网卡上,这样真实服务器就获得了一个原本目标不是给它的数据帧。
由于二层负载均衡器在转发请求的过程中只修改了帧的MAC目的地址,不涉及上层协议(没有修改Payload的数据),所以在上层(第三层)看来,所有数据都是
未曾改变过的。由于第三层的数据包,即IP数据包中包含了源(客户端)和目标(均衡器)的IP地址,只有真实服务器保保证自己的ip地址与数据包中的目标Ip地址
一致,这个数据包才能被真正处理。因此,使用这种负载均衡模式时,需要把真实服务器物理集群中所有机器的虚拟ip地址(Virtual IP Address,VIP)配置成
和负载均衡器的虚拟IP一样,才能使经均衡器转发后的数据包在真实服务器中顺利使用。也真实因为实际处理请求的真实物理服务器ip和数据请求中的目的ip是一致
的,所以响应结果就不需要经过负载均衡器进行地址交换,而是可将响应结果的数据包直接从真实服务器返回给用户的客户端,避免了负载均衡器网卡带宽称为瓶颈,
因此数据链路层负载均衡器的效率是相当高的。
整个请求、转发、响应的链路形成了一个"三角",这种模式也称为"三角传输模式,DSR",也称为"单臂模式",或者"直接路由,DR"。
虽然数据链路层负载均衡的效率很高,但它并不适用于所有场合,除了无法适用于那些需要感知应用层协议信息的负载均衡场景外(所有四层负载均衡都不行),
它在网络侧受到的约束也很大。二层负载均衡直接修改目标MAC地址的工作原理决定了它与真实服务器的通信必须是二层可达的,也就是必须位于同一个子网中,无法
跨VLAN。所以,优势(效率高)和劣势(不能跨子网)共同决定了数据链路层负载均衡最适合作为数据中心的第一级均衡设备,用来连接其他的下级负载均衡器。
4.5.2 网络层负载均衡
在第三层网络层传输的是分组数据包(Packet),这是一种在分组交换网络(PSN)中传输的结构化数据单位。通过改变IP地址来实现数据包的转发,具体有2种
常见的修改方式:
1.第一种
保持源数据包不变,新创建一个数据包,把源数据包的header和payload整体作为新数据包的payload,并在这个新包的header中写入真实服务器的ip
作为目标地址,然后把它发送出去。经过三层交换机的转发,真实服务器收到数据包后,必须在接收入口设计一个针对的拆包机制,由负载均衡器自动添加的那层
header扔掉,还原出原数据包进行使用。这样,真实服务器就同样拿到了一个原本不是发给它(目标ip不是它)的数据包,达到了流量转发的目的,这种模式
称为"IP隧道传输(IP Tunnel)"。
尽管因为要封装新的数据包,ip隧道转发模式的效率要比直接路由模式的效率低,但由于没有改变原数据包中的任何信息,所以ip隧道转发模式仍然具备
三角传输的特性,负载均衡器转发来的请求,可以由真实服务器去直接应对,无需经过负载均衡器原路返回。而且由于ip隧道工作在网络层,所以可以跨域VLAN,因此拜托了直接路由模式中网络侧的约束。
缺点:
1.它要求真实服务器必须支持"IP隧道协议",即它得学会自己拆包扔掉一层Header,这不是什么大的问题,现在主流Linux都支持;
2.这种模式仍然必须通过专门的配置,必须保证所有的真实服务器与负载均衡器有相同的虚拟ip地址,因为回复该数据包时,需要使用这个虚拟ip
作为响应数据包的源地址,这样客户端收到的数据包才能正确解析。这个限制相对麻烦,它与"透明"的原则冲突,需由系统管理员介入。
2.第二种
而且对服务器虚拟ip的配置并不是任何情况下都可行的,尤其是当几个服务公共一台服务器的时候,此时就必须考虑第二种修改方式---修改目标数据包;
直接修改数据包的header中的目标地址,修改后原本由用户发给负载均衡器的数据包也会被三层交换机转发到真实服务器的网卡上,而且因为没有经过ip隧道的
额外包装,也就无需拆包看。但这个问题是这种模式是通过修改目标ip地址才能达到真实服务器,如果真实服务器直接将应答包返回客户端,即这个应答数据包
的源ip是真实服务器的ip,也即均衡器修改后的ip,则客户端可能不认识该ip,自然就无法正常处理这个应答了。因此,只有让应答的流量继续回到负载均衡
器,由负载均衡器把应答包的源ip改成自己的ip,再发给客户端,才能保证客户端与真实服务器之间的正常通信。这种模式被称为"NAT模式"。
在流量比大的时候,nat模式的负载均衡会带来较大的性能损失,比起直接路由和ip隧道,甚至会出现数量级的下降。这点是显而易见的,由负载均衡器
代表整个服务器集群来应答,各个服务器的响应数据都会争抢均衡器的出口带宽。
还有一种更彻底的nat模式:即负载均衡器在转发的时候,不仅会修改目标Ip地址,还会修改源ip地址,这样源地址就改成负载均衡器自己的ip地址,
称作Source NAT(SNAT)。好处是,真实服务器无需配置网关就能够让应答的流量经过正常的三层路由回到负载均衡器上,做到彻底透明。缺点是,由于做了
SNAT,真实服务器处理请求时就无法拿到客户端的真实ip,从真实服务器的视角来看,流量都来自负载均衡器。
4.5.3 应用层负载均衡
前面介绍的四层负载均衡器工作模式都属于"转发",即直接将承载tcp报文的底层数据格式(ip数据包或以太网帧)转发到真实服务器上,此时客户端与响应请求
的真实服务器维持着同一条tcp隧道。但工作在四层之后的负载均衡器就无法再转发了,只能代理,此时真实服务器、负载均衡器、客户端三者之间由2条独立的tcp
通道来维持通信。
"代理"这个词,根据"哪一方能感知"的原则,可以分为:
1.正向代理
就是我们通常简称的代理,指在客户端设置,代表客户端与服务器通信的代理服务,它是客户端可知,而对服务器是透明的;
2.反向代理
指在服务器侧设置的,代表真实服务器与客户端通信的代理服务,此时它对客户端来说是透明的;
3.透明代理
是指对双方都是透明的,配置在网络中间设备上的代理服务,譬如,架设在路由器上的透明翻墙代理。
七层负载均衡器属于反向代理。七层的网络性能比不上四层,它比四层多一轮tcp握手,有着跟nat转发模式一样的带宽问题,而且通常要耗费更多的cpu,
因为可用的解析规则比四层丰富。所以如果用七层做下载站、视频站这种流量应用肯定是不合适的,起码不能作为第一级均衡器。七层可以感知应用层通信的
具体内容。
七层可以实现的功能:
1.所有cdn可以做的缓存方面的工作,七层都可以做,譬如 静态资源缓存、协议升级、安全防护、访问控制等;
2.七层可以实现更加智能化的路由。譬如,根据session路由,可以实现亲和性的集群;根据url路由,实现专职化服务(相当于网关);根据用户身份
路由,实现对部分用户的特殊服务,等等;
3.某些安全手段可以由七层来抵御,譬如有一种DDoS手段SYN Flood攻击,控制众多客户端,使用虚假ip地址对同一目标大量发送syn报文。从技术原理
上看,由于四层无法感知上层协议的内容,这些syn都会被转发到后端真实服务器;而在七层下,这些syn攻击都会在负载均衡器上被过滤掉。
4.在很多微服务架构的系统中,链路治理措施都需要在七层中进行,譬如服务降级、熔断、异常注入等。一台服务器只有出现物理层面的故障,导致无法
应答tcp响应,四层才会感知;如果一台服务器能应答,只是一直在报500,四层是无法感知的。
4.5.4 均衡策略与实现
负载均衡器的2大职责是,"选择谁来处理用户请求"和"将用户请求转发过去"。策略有:
1.轮询
2.权重轮询
3.随机
4.权重随机
5.一致性哈希
6.响应速度
7.最少连接数
从实现角度来说,负载均衡器的实现分为 "软件负载均衡器"和"硬件负载均衡器"。软件负载均衡器又分为,直接建设在操作系统内核的均衡器和应用程序形式
的均衡器两种。前者的代表是LVS,后者有nginx,HAProxy,KeepAlived等。硬件往往会采用应用专用集成电路来实现,有专用处理芯片的支持,避免操作系统的
损耗,以达到最高性能,如F5等。
4.6 服务端缓存
系统是否真的需要缓存?在软件中引入缓存的负面作用要明显大于硬件缓存带来的负面作用:从开发角度来说,引入缓存会增加系统的复杂度,因为你要考虑缓存
的失效、更新、一致性问题(硬件也需要考虑,但不需要你考虑);从运维角度来说,缓存会掩盖一些缺陷,让问题在更久之后的时间暴露,出现在远离发生现场的位置
上;从安全角度来说,缓存可能会泄露某些保密数据。
引入缓存的理由:
1.为缓解cpu压力而引入缓存;
2.为缓解IO压力而引入缓存;
缓存是以空间换时间的手段,但它的出发点是缓解cpu和IO资源在峰值流量下的压力,"顺带"而非"专门"的提升响应性能。言外之意是,如果可以通过增强cpu、
IO本身的性能来满足需要的话,那升级硬件往往是更好的解决方案。
4.6.1 缓存属性
1.吞吐量
缓存的吞吐量使用OPS值(每秒操作数,Operation per Second,ops/s)来衡量,反映了对缓存进行并发读、写操作的效率,即缓存本身的工作效率
高低。
环形缓冲区:一种拥有读、写两个指针的数据复用结构。
a) 缓存淘汰策略
1.FIFO:优先淘汰最早进入被缓存的数据。
2.LRU:优先淘汰最久未被访问过的数据。
3.LFU:淘汰最不经常使用的数据。
4.TinyLFU
5.W-TinyLFU
2.命中率
即成功从缓存中返回结果次数与总请求次数的比值,反映了引入缓存的价值高低,命中率越低,引入缓存的收益越小,价值越低。
3.扩展功能
即缓存除了基本的读写功能外,还提供哪些额外的管理功能,譬如最大容量、失效时间、失效事件、命中率统计,等。
1.加载器
2.淘汰策略
3.失效策略
4.事件通知
5.并发级别
6.容量控制
7.引用方式
8.统计信息
9.持久化
4.分布式缓存
缓存可以分为"进程内缓存"和"分布式缓存"两大类,前者只为节点本身提供服务,无网络访问操作,速度快但缓存的数据不能在各服务节点中共享,后者
相反。
1.从访问角度来说
a) 复制式缓存
集群中每个节点都有一份副本,随着节点的增加而性能下降;
b) 集中式缓存
集中式缓存是目前分布式缓存的主流形式,它的读写都需要网络访问。好处是不会随着节点的增加而性能下降,坏处是性能达不到进程内缓存那样
的高性能。还有一个关键特点,它与使用缓存的应用分处在独立的进程空间中。其好处是能够为异构语言提供服务。坏处是,如果要缓存对象等复杂的
类型的话,基本上只能靠序列化来支撑具体语言的类型系统。不仅有序列化成本,还容易导致传输成本的显著增加。
2.从数据一致性角度来说
分布式缓存与进程内缓存它们是互补的而非互斥。可以搭配使用,构成多级缓存。进程内缓存作为一级缓存,分布式作为二级。尽管多级缓存结合了进程内
缓存和分布式缓存的优点,但它的代码入侵性比较大,需要开发者承担多次查询、多次回填的工作,也不便于管理,如超时、刷新等策略都要设置多遍,数据更新
是麻烦,很容易出现各个节点一级缓存和二级缓存数据不一致的情况。
一种常见的设计原则是 变更以分布式缓存中的数据为准,访问以进程内的数据优先。大致的做法是,当数据发生变动的时候,在集群内以推送通知(简单的
以redis的pub/sub,求严谨的话可以引入zookeeper或者etcd来处理),让各个节点的一级缓存中的相应数据自动失效。当访问缓存时,提供统一封装好的
一、二级缓存联合查询接口,接口外部只是查询一次,接口内部自动实现优先查询一级缓存,未获取到数据再自动查询二级缓存的逻辑。
4.6.2 缓存风险
1.缓存穿透
如果查询的数据在数据库中根本不存在,缓存里自然没有,且每次都会触及末端的数据库。缓存起不到缓解压力的作用,这种查询不存在的数据的现象被
称为"缓存穿透"。
缓存穿透,可能是业务本身固有的问题,也可能是恶意攻击。办法有:
1.对于业务逻辑本身不能避免的缓存穿透,可以约定在一定时间内对返回为空的key值进行缓存(注意是正常返回单结果为空,不应该把异常的也当做
空值来缓存),使得在一定时间内缓存最多被穿透一次。
2.对于恶意攻击导致的缓存穿透来说,通常会在缓存之前设置一个布隆过滤器来解决。所以恶意攻击是指请求者刻意伪造数据库中肯定不存在的key,
然后发送大量请求进行查询。布隆过滤器是用最小的代价来判断某个元素是否存在于某个集合的办法,如果布隆过滤器给出的判定结果是请求的数据
不存在,直接返回即可,连缓存都不用查询。
2.缓存击穿
如果缓存中某些热点数据忽然因为某种原因失效了,譬如由于超时失效,此时又有多个针对该数据的请求访问同时发送过来,这些请求将全部未能命中,
达到真实的数据源中,导致其压力剧增,这种现象被称为"缓存击穿"。办法有:
1.加锁同步,以请求该数据的key值为锁,使得只有第一个请求可以流入真实的数据源中,对其他线程则采取阻塞或者重试策略。如果是进程内缓存出现
问题,施加普通的互斥锁即可,如果是分布式缓存中出现问题,就施加分布式锁,这样数据源就不会大量收到针对同一数据的请求了。
2.热点数据由代码手动管理。缓存击穿是仅针对热点数据自动失效才引发的问题,对于这类数据,可以直接由开发者通过代码来有计划的完成更新、失效、
避免由缓存的策略自动管理。
3.缓存雪崩
缓存击穿是针对单个热点数据失效,由大量请求击穿缓存而给真实数据源带来压力。还有一种可能更普遍的情况,即不是针对单个热点数据的大量请求,
而是由于大批不同的数据在短时间内一起失效,导致这些数据的请求都击穿缓存达到数据源,同样令数据源在短时间内压力剧增。
出现这种情况,往往是因为系统有专门的缓存预热功能,或者大量公共数据是由某一次冷加载的,使得由此载入缓存的大批数据具有相同的过期数据,在
同一时刻一起失效;也可能是因为缓存服务由于某些原因崩溃后重启,造成数据大量同时失效。这种现象被称为缓存雪崩。避免缓存雪崩办法有:
1.提升缓存系统可用性,建设分布式缓存的集群;
2.启用透明多级缓存,这样各个服务节点一级缓存中的数据通常会具有不一样的加载时间,也就分散了它们的过期时间;
3.将缓存的生存期从固定时间改为一段时间内随机的时间。
4.缓存污染
是指缓存中的数据与真实数据源中的数据不一致的情况。多数是由开发者更新缓存不规范造成的。
缓存更新的策略有,Cache Aside、Read/Write Through、Write Behind Caching 等。其中最简单、成本最低的是 Cache Aside 是指:
1.读数据时,先读缓存,如果没有再读数据源,然后将数据放入缓存,再响应请求;2
2.写数据时,先写数据源,然后失效(而不是更新)掉缓存。
读数据方面一般不会出错,但是写数据时,就必须要专门强调2点。
1.先后顺序是先数据源后缓存。如果采用先失效缓存后写数据源的顺序,那一定存在一段时间内缓存已经删除,但数据源还没有修改完成的情况,此时
新的请求到来,缓存未能命中,就会直接流到真实数据源中。这样请求读到的数据依然是旧数据,随后又重新回填到缓存中。当数据源的修改完成后,
结果出现的是数据源是新的,而缓存中是旧数据的情况。
2.应当是失效缓存,而不是去尝试更新缓存。如果去更新缓存,更新的过程中数据源又被其他请求再次修改的话,缓存又要面临多次赋值的复杂时序
问题。所以直接失效缓存,等下次用到的时候自动回填,期间无论是被修改了多少次都不影响。
2.凤凰架构:构建可靠的大型分布式系统 --- 访问远程服务
17.凤凰架构:构建可靠的大型分布式系统 --- 技术演示工程实践