透明多级分流系统
请求从浏览器出发,在域名服务器的指引下找到系统的入口,经过网关、负载均衡器、缓存、服务集群等一系列设施,最后触及到末端存储于数据库服务器中的信息,然后逐级返回到用户的浏览器之中。
-
本地缓存、内容分发网络、反向代理:客户端或网络的边缘,能够
-
集群中自动扩缩的服务节点:线性拓展,易于
-
服务注册中心、配置中心:对系统运行有全局性的影响,要时刻保持着
-
系统入口的路由、网关或者负载均衡器(一次网络请求中无可避免至少有一个是单点的部件)、位于请求调用链末端的传统关系数据库:
系统流量规划原则:
-
尽可能减少单点部件,如果某些单点是无可避免的,则应尽最大限度减少到达单点部件的流量。
-
奥卡姆剃刀原则:如无必要,勿增实体。根据系统的用户量、峰值流量和团队本身的技术与运维能力来考虑,最简单的系统就是最好的系统。
客户端缓存
强制缓存
当服务器返回某个资源时带有该 Header 的话,意味着服务器承诺截止时间之前资源不会发生变动,浏览器可直接缓存该数据,不再重新发请求
HTTP/1.1 200 OK
Expires: Wed, 8 Apr 2020 07:28:00 GMT
缺点:
-
受限于客户端的本地时间,可能会造成缓存提前失效或超期持有。
-
无法处理涉及到用户身份的私有资源,容易被代理服务器或者内容分发网络缓存起来,则可能被其他未认证的用户所获取。
-
无法描述“不缓存”的语义。
HTTP/1.1 200 OK
Cache-Control: max-age=600
在客户端的请求 Header 或服务器的响应 Header 中都可以存在,它定义了一系列的参数,且允许自行扩展
-
max-age和s-maxage:后跟一个以秒为单位的数字,s-maxage,意味“共享缓存”的有效时间,即允许被 CDN、代理等持有的缓存有效时间。
-
public和private:指明是否涉及到用户身份的私有资源
-
no-cache和no-store:no-cache 指明该资源不应该被缓存,no-store 不强制会话中相同 URL 资源的重复获取,但禁止浏览器、CDN 等以任何形式保存该资源。
-
no-transform:禁止资源被任何形式地修改。禁止某些 CDN、透明代理支持自动 GZip 压缩图片或文本,以提升网络性能
协商缓存
基于变化检测的缓存机制,在一致性上会有比强制缓存更好的表现,但需要一次变化检测的交互开销,性能上略差。
与强制缓存并行工作,强制缓存优先。
变动检查机制:
-
根据资源的修改时间进行检查
Last-Modified 和 If-Modified-Since:Last-Modified 是服务器的响应 Header,用于告诉客户端这个资源的最后修改时间。对于带有这个 Header 的资源,当客户端需要再次请求时,会通过 If-Modified-Since 把之前收到的资源最后修改时间发送回服务端。
如果此时服务端发现资源在该时间后没有被修改过,就只要返回一个 304/Not Modified 的响应即可
HTTP/1.1 304 Not Modified
Cache-Control: public, max-age=600
Last-Modified: Wed, 8 Apr 2020 15:31:30 GMT如果此时服务端发现资源在该时间之后有变动,就会返回 200/OK 的完整响应,在消息体中包含最新的资源
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 是服务器的响应 Header,用于告诉客户端这个资源的唯一标识。对于带有这个 Header 的资源,当客户端需要再次请求时,会通过 If-None-Match 把之前收到的资源唯一标识发送回服务端。
如果此时服务端计算后发现资源的唯一标识与上传回来的一致,说明资源没有被修改过,就只要返回一个 304/Not Modified 的响应即可。
HTTP/1.1 304 Not Modified
Cache-Control: public, max-age=600
ETag: "28c3f612-ceb0-4ddc-ae35-791ca840c5fa"如果此时服务端发现资源的唯一标识有变动,就会返回 200/OK 的完整响应,在消息体中包含最新的资源
HTTP/1.1 200 OK
Cache-Control: public, max-age=600
ETag: "28c3f612-ceb0-4ddc-ae35-791ca840c5fa"
Content
Etag 是 HTTP 中一致性最强的缓存机制,譬如,Last-Modified 标注的最后修改只能精确到秒级,Etag 却又是 HTTP 中性能最差的缓存机制,体现在每次请求时,服务端都必须对资源进行哈希计算。
Etag 和 Last-Modified 一起使用,服务器会优先验证 Etag,在 Etag 一致的情况下,再去对比 Last-Modified,这是为了防止有一些 HTTP 服务器未将文件修改日期纳入哈希范围内。
域名解析
域名——>IP地址
-
客户端先检查本地的 DNS 缓存,查看是否存在并且是存活着的该域名的地址记录。DNS 是以
-
客户端将地址发送给本机操作系统中配置的本地 DNS(Local DNS)
-
本地 DNS 收到查询请求后,会按照“是否有
www.icyfenix.com.cn
的权威服务器”→“是否有icyfenix.com.cn
的权威服务器”→“是否有com.cn
的权威服务器”→“是否有cn
的权威服务器”的顺序,依次查询自己的地址记录,如果都没有查询到,就会一直找到最后点号代表的根域名服务器为止。-
权威域名服务器(Authoritative DNS):是指负责翻译特定域名的 DNS 服务器
-
根域名服务器(Root DNS)是指固定的、无需查询的
-
-
当 DNS 查询请求按步骤 3 的顺序一直查到根域名服务器之后,它将会得到“
cn
的权威服务器”的地址记录,然后通过“cn
的权威服务器”,得到“com.cn
的权威服务器”的地址记录,以此类推,最后找到能够解释www.icyfenix.com.cn
的权威服务器地址。 -
通过“
www.icyfenix.com.cn
的权威服务器”,查询www.icyfenix.com.cn
的地址记录,地址记录类型已经
根据访问者所处的不同地区(譬如华北、华南、东北)、不同服务商(譬如电信、联通、移动)等因素来确定返回最合适的 A 记录,将访问者路由到最合适的数据中心,达到智能加速的目的
“
<link rel="dns-prefetch" href="//domain.not-icyfenx.cn">
内容分发网络
为互联网系统解决跨运营商、跨地域物理距离所导致的时延问题,能为网站流量带宽起到分流、减负的作用。
路由解析
由 CDN 的缓存节点接管了用户向服务器发出的资源请求
内容分发
获取源站资源的过程。
-
主动分发Push:由源站主动发起,将内容从源站或者其他资源库推送到用户边缘的各个 CDN 缓存节点上。
-
被动回源Pull:由用户访问所触发全自动、双向透明的资源缓存过程。当CDN 缓存节点发现自己没有该资源,就会实时从源站中获取。
CDN应用
-
加速静态资源
-
安全防御:CDN 在广义上可以视作网站的堡垒机,源站只对 CDN 提供服务,由 CDN 来对外界其他用户服务,这样恶意攻击者就不容易直接威胁源站
-
协议升级:实现源站是 HTTP 协议的,而对外开放的网站是基于 HTTPS 的。
-
状态缓存
-
访问控制:实现 IP 黑/白名单功能,根据不同的来访 IP 提供不同的响应结果,根据 IP 的访问流量来实现 QoS 控制、根据 HTTP 的 Referer 来实现防盗链
负载均衡
调度后方的多台机器,以统一的接口对外提供服务。
-
四级负载均衡:性能强,七层负载均衡:功能强
-
多级混合负载均衡:低层负载均衡在前,高层负载均衡在后
“四层”的意思是说这些工作模式的共同特点是维持着同一个 TCP 连接,而不是说它只工作在第四层。
事实上,这些模式主要都是工作在二层(数据链路层,改写 MAC 地址)和三层(网络层,改写 IP 地址)上,单纯只处理第四层(传输层,可以改写 TCP、UDP 等协议的内容和端口)的数据无法做到负载均衡的转发,因为 OSI 的下三层是媒体层(Media Layers),上四层是主机层(Host Layers),既然流量都已经到达目标主机上了,也就谈不上什么流量转发,最多只能做代理了。
数据链路层负载均衡
数据帧Frame,MAC目标地址和MAC源地址。
工作原理:修改请求的数据帧中的 MAC 目标地址,让用户原本是发送给负载均衡器的请求的数据帧,被二层交换机根据新的 MAC 目标地址转发到服务器集群中对应的服务器(后文称为“真实服务器”,Real Server)的网卡上
需要把真实物理服务器集群所有机器的
这种负载均衡模式也常被很形象地称为三角传输模式、单臂模式、直接路由
。
二层负载均衡器与真实的服务器的通信必须是二层可达的,必须位于同一个子网当中,无法跨 VLAN。最适合用来做数据中心的第一级均衡设备,用来连接其他的下级负载均衡器。
网络层负载均衡
分组数据包Packets,源IP地址、目的IP地址
-
新建一个数据包,将原来数据包的 Headers 和 Payload 整体作为另一个新的数据包的 Payload,在这个新数据包的 Headers 中写入真实服务器的 IP 作为目标地址,然后把它发送出去。 拆包、"IP隧道"、同样需要真实服务器与均衡器有着相同的虚拟 IP 地址
-
改变目标数据包:直接把数据包 Headers 中的目标地址改掉,修改后原本由用户发给均衡器的数据包,也会被三层交换机转发送到真实服务器的网卡上。
为了避免暴露真实服务器IP地址,只能让应答流量继续回到负载均衡,由负载均衡把应答包的源 IP 改回自己的 IP,再发给客户端。 NAT模式
NAT模式缺点:由负载均衡器代表整个服务集群来进行应答,各个服务器的响应数据都会互相挣抢均衡器的出口带宽,整个系统的瓶颈很容易就出现在负载均衡器上。
SNAT ” Source NAT“:均衡器在转发时,不仅修改目标 IP 地址,连源 IP 地址也一起改了,源地址就改成均衡器自己的 IP。
优点:真实服务器无须配置网关就能够让应答流量经过正常的三层路由回到负载均衡器上,做到了彻底的透明。
缺点:真实服务器处理请求时就无法拿到客户端的 IP 地址
应用层负载均衡
四层以上的负载均衡无法转发,只能进行代理。
真实服务器、负载均衡器、客户端三者之间由两条独立的 TCP 通道来维持通信
根据“哪一方能感知到”的原则,可以分为“正向代理”、“反向代理”和“透明代理”三类。
正向代理:在客户端设置的、代表客户端与服务器通信的代理服务,对服务器透明
反向代理:设置在服务器这一侧,代表真实服务器来与客户端通信的代理服务,对客户端透明
工作在应用层,可以感知应用层通信的具体内容,往往能够做出更明智的决策,玩出更多的花样来。
七层代理可以实现的功能:
-
所有 CDN 可以做的缓存方面的工作(除了CDN根据物理位置就近返回这种优化链路的工作):静态资源缓存、协议升级、安全防护、访问控制
-
更智能化的路由:Session 路由,根据 URL 路由,根据用户身份路由
-
抵御
-
链路治理措施都需要在七层中进行,譬如服务降级、熔断、异常注入等
均衡策略与实现
选择谁来处理用户请求
-
轮循均衡(Round Robin):每一次来自网络的请求轮流分配给内部中的服务器,从 1 至 N 然后重新开始。适合于集群中的所有服务器都有相同的软硬件配置并且平均服务请求相对均衡的情况。
-
权重轮循均衡(Weighted Round Robin):根据服务器的不同处理能力,给每个服务器分配不同的权值,使其能够接受相应权值数的服务请求。确保高性能的服务器得到更多的使用率,避免低性能的服务器负载过重。
-
随机均衡(Random):把来自客户端的请求随机分配给内部中的多个服务器,在数据足够大的场景下能达到相对均衡的分布。
-
权重随机均衡(Weighted Random):此种均衡算法类似于权重轮循算法,不过在分配处理请求时是个随机选择的过程。
-
一致性哈希均衡(Consistency Hash):根据请求中某一些数据(可以是 MAC、IP 地址等)作为特征值来计算需要落在的节点上,算法一般会保证同一个特征值每次都一定落在相同的服务器上。
一致性的意思是保证当服务集群某个真实服务器出现故障,只影响该服务器的哈希,而不会导致整个服务集群的哈希键值重新分布。
-
响应速度均衡(Response Time):负载均衡设备对内部各服务器发出一个探测请求(例如 Ping),然后根据内部中各服务器对探测请求的最快响应时间来决定哪一台服务器来响应客户端的服务请求。
仅仅指的是负载均衡设备与服务器间的最快响应时间,而不是客户端与服务器间的最快响应时间。
-
最少连接数均衡(Least Connection):客户端的每一次请求服务在服务器停留的时间可能会有较大的差异。对内部中需负载的每一台服务器都有一个数据记录,记录当前该服务器正在处理的连接数量,当有新的服务连接请求时,将把当前请求分配给连接数最少的服务器,使均衡更加符合实际情况,负载更加均衡。
适合长时处理的请求服务,如 FTP 传输。
软件均衡器:
-
操作系统内核:LVS(Linux Virtual Server),性能更好,无需在内核空间和应用空间中来回复制数据包
-
应用程序形式:Nginx,选择广泛,使用方便,功能不受限于内核版本。
硬件均衡器:
服务端缓存
-
缓解 CPU 压力而做缓存:把方法运行结果存储起来、把原本要实时计算的内容提前算好、把一些公用的数据进行复用
-
缓解 I/O 压力而做缓存:把原本对网络、磁盘等较慢介质的读写访问变为对内存等较快介质的访问,将原本对单点部件(如数据库)的读写访问变为到可扩缩部件(如缓存中间件)的访问,顺带提升响应性能。
缓存四个维度:
-
吞吐量:缓存的吞吐量使用 OPS 值(每秒操作数,Operations per Second,ops/s)来衡量,反映了对缓存进行并发读、写操作的效率,即缓存本身的工作效率高低。
-
命中率:缓存的命中率即成功从缓存中返回结果次数与总请求次数的比值,反映了引入缓存的价值高低,命中率越低,引入缓存的收益越小,价值越低。
-
扩展功能:缓存除了基本读写功能外,还提供哪些额外的管理功能,譬如最大容量、失效时间、失效事件、命中率统计,等等。
-
分布式支持:缓存可分为“进程内缓存”和“分布式缓存”两大类,前者只为节点本身提供服务,无网络访问操作,速度快但缓存的数据不能在各个服务节点中共享,后者则相反
吞吐量
ConcurrentHashMap:吞吐量最高的缓存容器
命中率与淘汰策略
缓存淘汰策略直接影响缓存的命中率
淘汰策略实现方案:
-
FIFO:优先淘汰最早进入被缓存的数据
-
LRU:优先淘汰最久未被使用访问过的数据。LRU 通常会采用 HashMap 加 LinkedList 双重结构(如 LinkedHashMap)来实现,以 HashMap 来提供访问接口,保证常量时间复杂度的读取性能,以 LinkedList 的链表元素顺序来表示数据的时间顺序,每次缓存命中时把返回对象调整到 LinkedList 开头,每次缓存淘汰时从链表末端开始清理数据。
-
LFU:优先淘汰最不经常使用的数据。LFU 会给每个数据添加一个访问计数器,每访问一次就加 1,需要淘汰时就清理计数器数值最小的那批数据。
高昂的维护开销、不便于处理随时间变化的热度变化
扩展功能
-
加载器:许多缓存都有“CacheLoader”之类的设计,加载器可以让缓存从只能被动存储外部放入的数据,变为能够主动通过加载器去加载指定 Key 值的数据,加载器也是实现自动刷新功能的基础前提。
-
淘汰策略:有的缓存淘汰策略是固定的,也有一些缓存能够支持用户自己根据需要选择不同的淘汰策略。
-
失效策略:要求缓存的数据在一定时间后自动失效(移除出缓存)或者自动刷新(使用加载器重新加载)。
-
事件通知:缓存可能会提供一些事件监听器,让你在数据状态变动(如失效、刷新、移除)时进行一些额外操作。有的缓存还提供了对缓存数据本身的监视能力(Watch 功能)。
-
并发级别:对于通过分段加锁来实现的缓存(以 Guava Cache 为代表),往往会提供并发级别的设置。可以简单将其理解为缓存内部是使用多个 Map 来分段存储数据的,并发级别就用于计算出使用 Map 的数量。如果将这个参数设置过大,会引入更多的 Map,需要额外维护这些 Map 而导致更大的时间和空间上的开销;如果设置过小,又会导致在访问时产生线程阻塞,因为多个线程更新同一个 ConcurrentMap 的同一个值时会产生锁竞争。
-
容量控制:缓存通常都支持指定初始容量和最大容量,初始容量目的是减少扩容频率,这与 Map 接口本身的初始容量含义是一致的。最大容量类似于控制 Java 堆的-Xmx 参数,当缓存接近最大容量时,会自动清理掉低价值的数据。
-
引用方式:支持将数据设置为软引用或者弱引用,提供引用方式的设置是为了将缓存与 Java 虚拟机的垃圾收集机制联系起来。
-
统计信息:提供诸如缓存命中率、平均加载时间、自动回收计数等统计。
-
持久化:支持将缓存的内容存储到数据库或者磁盘中,进程内缓存提供持久化功能的作用不是太大,但分布式缓存大多都会考虑提供持久化功能。
分布式缓存
访问角度:
-
复制式缓存:甚少更新但频繁读取的数据,缓存中所有数据在分布式集群的每个节点里面都存在有一份副本,读取数据时无须网络访问,直接从当前节点的进程内存中返回,理论上可以做到与进程内缓存一样高的读取性能;当数据发生变化时,就必须遵循复制协议,将变更同步到集群的每个节点中,复制性能随着节点的增加呈现平方级下降,变更数据的代价十分高昂。
-
集中式缓存:不会随着集群节点数量的增加而产生额外的负担,其坏处自然是读、写都不再可能达到进程内缓存那样的高性能。能够为异构语言提供服务。
缺点:如果要缓存对象等复杂类型的话,基本上就只能靠序列化来支撑具体语言的类型系统,不仅有序列化的成本,还很容易导致传输成本也显著增加。
数据一致性角度:
AP式,最终一致性。
多级缓存:进程内缓存做一级缓存,分布式缓存做二级缓存。
如果能在一级缓存中查询到结果就直接返回,否则便到二级缓存中去查询,再将二级缓存中的结果回填到一级缓存,以后再访问该数据就没有网络请求了。如果二级缓存也查询不到,就发起对最终数据源的查询,将结果回填到一、二级缓存中去。
缓存风险
缓存穿透
查询的数据在数据库中根本不存在,每次都会触及到末端的数据库,缓存就起不到缓解压力的作用。
解决方法:
-
在一定时间内对返回为空的 Key 值依然进行缓存
-
在缓存之前设置一个布隆过滤器来解决,布隆过滤器是用最小的代价来判断某个元素是否存在于某个集合的办法。
缓存击穿
缓存中某些热点数据忽然因某种原因失效了,譬如典型地由于超期而失效,此时又有多个针对该数据的请求同时发送过来,这些请求将全部未能命中缓存,都到达真实数据源中去,导致其压力剧增。
解决方法:
-
加锁同步,以请求该数据的 Key 值为锁,使得只有第一个请求可以流入到真实的数据源中,其他线程采取阻塞或重试策略。
如果是进程内缓存出现问题,施加普通互斥锁即可,如果是分布式缓存中出现的问题,就施加分布式锁
-
通过代码来有计划地完成更新、失效,避免由缓存的策略自动管理。
缓存雪崩
大批不同的数据在短时间内一起失效,导致了这些数据的请求都击穿了缓存到达数据源。载入缓存的大批数据具有相同的过期时间,在同一时刻一起失效。
解决方法:
-
提升缓存系统可用性,建设分布式缓存的集群。
-
将缓存的生存期从固定时间改为一个时间段内的随机时间
缓存污染
缓存中的数据与真实数据源中的数据不一致的现象
Cache Aside模式:
-
读数据时,先读缓存,缓存没有的话,再读数据源,然后将数据放入缓存,再响应请求。
-
写数据时,先写数据源,然后失效(而不是更新)掉缓存。
典型的出错场景是如果某个数据是从未被缓存过的,请求会直接流到真实数据源中,如果数据源中的写操作发生在查询请求之后,结果回填到缓存之前,也会出现缓存中回填的内容与数据库的实际数据不一致的情况。
概率很低,因为修改数据库耗时比其他操作都长得多。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?