大流量时代,如何规划系统流量提升可靠性
本文分享自华为云社区《大流量时代,如何规划系统流量提升可靠性》,作者:breakDawn 。
透明多级分流系统
对系统流量进行规划, 要注意以下 2 个原则
尽可能减少单点部件, 或者减少到达单点部件的流量或者作用
奥卡姆剃刀原则,确定有再有必要的时候才去使用,避免过度设计
1 客户端缓存
即对于某些资源, 在客户端就做缓存,客户端不去重复请求。
1.1 强制缓存
类似 HTTP 协议里在 header 里用到的两种标签,且都是服务端强行控制的,基于时间的
Expires
服务端直接返回数据不会变动的截止时间。
缺点:受限于客户端本地时间、无法表示不缓存除非强制改时间戳、无法表示是否是私有资源(避免私有资源被其他节点缓存)
Cache-Control
这个请求头使用 max-age、private、no-cache 等标签解决了 Expires 里的 3 个缺点。
1.2 协商缓存
协商缓存需要考虑是否真的发生变化。 协商和强制可以共同存在,即强制失效的时候就可以用上协商。
协商缓存不仅存在于地址输入、跳转,也存在 F5 中(但如果 Ctrl+F5 强制刷新则会让缓存失效)
Last-Modified
告诉客户端资源的最后修改时间, 客户端再次请求时也会对这个时间做修改
如果服务端发现在那个时间之后资源未变动,返回 304 Not Modified
如果有变动,就返回 OK,并携带完整的资源
ETag
需要对资源计算哈希值,客户端发请求也会带上自己存的 ETag,每次会比对资源的哈希值是否一致,不一致则返回新资源。
Etag 是一致性最强的本地缓存机制,但也是性能最差的。
2 传输通道优化
本章节大部分以熟知的 HTTP 协议作为主要传输通道协议,讲解如何进行优化
2.1 连接数优化
HTTP 是基于 TCP 的,每次都是重新建立一个 TCP 连接。 因此前端开发人员开发了很多小优化,来减少请求次数,例如雪碧图、分段文档、合并 Ajax 请求之类的。
HTTP1.0 里的长连接(keep-alive 连接复用)为什么不能解决这个问题?
因为存在队首阻塞问题,本质上是基于 FIFO 复用连接, 1 个请求卡住了,后面 9 个请求都阻塞住了,但如果同时支持返回,在顺序混乱的情况下无法正常处理
HTTP2.0 的多路复用解决了这个问题:
以帧作为最小粒度单位,每个帧都携带流 ID 识别是哪个流
客户端可以很容易在不同流中重组 HTTP 请求和响应报文
2.2 传输压缩
HTTP 很早就支持 GZip 压缩来减少大资源的传输量
HTTP1.0 中, 持久连接和传输压缩无法一起使用, 因为压缩后无法识别资源是否传输完毕。
HTTP1.1 中引入了“分块传输编码”,来进行资源结束的判断。
2.3 用 UDP 来加快网络传输
HTTP/3 中,希望能替换掉 HTTP on TCP 的依赖、谷歌推出了快速 UDP 网络连接, 即 QUIC
QUIC 以 UDP 为基础, 可靠传输能力由自己实现
QUIC 专门面向移动设备支持, 移动设备的 ip 地址经常会切换,使用 ip 作为定位不合适, 因此提出了连接标识符来保持连接。
对于不支持 QUIC 的情况,支持回退为 TCP 连接,实现兼容
3 内容分发网络 CDN
CND 可以解决 互联网系统跨运营商、跨地域物理距离所导致的时延问题,为网站流量带宽起到分流、减负的作用。
主要包含以下 4 个工作部分
3.1 路由解析
用户的静态资源请求访问 CDN 是通过 DNS 解析来完成的,甚至可能一个网站会有各种不同地域的 CDN 域名解析地址返回, 通过你的路由配置会自动选择符合地域的 ip 地址
3.2 内容分发
如何分发内容有两种方式:
主动分发, 通过 CND 服务商提供的接口主动推送自己的资源,这样你需要额外编写资源推动的代码。大型活动例如双 11 会优先考虑主动分发预先准备资源。
被动回源, 由用户访问触发,当发现没有资源时,CDN 会去源站请求并返回,则用你不需要新写相关代码,只要在 CDN 那边支持回源你的源站即可。小型站点基本都是用这个方法。
如何更新资源有两种方式:
超时被动失效,CDN 的资源都有有效期,超时了就回源获取
手工主动失效, CDN 服务商提供缓存失效接口,主动触发失效并进行被动回源更新。
现在一般是 1 和 2 结合使用,二者不冲突
4 负载均衡
负载均衡有两种大类
四层负载均衡
指的是计算机七层模型中四层及以下的均衡策略结合
即 数据链路层 + 网络层 均可做均衡
七层负载均衡
指的是在应用层通过实际代码做均衡
4.1 数据链路层负载均衡(四层负载均衡)
通过链路层上的均衡器替换 MAC 地址,进行链路层的均衡
各负载节点的 IP 是一样的(相同的虚拟 IP)
返回时无需经过均衡器,直接返回即可(因为目标 ip、源 ip 基本没变)
缺点:
必须是同一个子网内,无法跨 VLAN,只能作为最接近数据中心的均衡器
4.2 网络层负载均衡(四层负载均衡)
有两种方式:
IP 隧道模式
均衡器在 IP 报文外面包了一层新的 header,header 里指定了目标机器的实际 ip 或者小网 ip。 接收机器要支持解 header,且同样要求作为返回的虚拟 ip 是一致的,也是直接返回无需经过均衡器。
缺点:
用到的服务器都要支持隧道解包能力(linux 系统现在都支持)
虚拟 ip 仍然有较大限制,需要人工介入管理众多机器
NAT 模式
NAT 模式中,就是进行真正的 ip 转换, 且返回时也要返回给 NAT 进行 ip 转换,这样只需要针对 NAT 进行人工管理即可。
缺点在于 NAT 容易成功性能瓶颈
SNAT 会修改源 IP 改为 NAT 的 ip, 可以做到对业务真正透明, 但是代价是如果需要对源 IP 做限制时容易有问题, 因为所有的来源 ip 都是一样的了。
4.3 应用层负载均衡(七层负载均衡)
也叫做七层代理(应用层代理),因为这个负载均衡属于反向代理(即部署在服务端的代理,对客户端不感知)
不适合做下载站、视频站等流量应用
如果瓶颈在服务计算能力,则可以考虑做应用层均衡期
七层代理除负载均衡外的其他功能:
支持做 CDN 类似的缓存能力
施行智能化路由,根据 URL 或者特定用户做特殊服务
抵御安全工具,提前过滤攻击报文
链路治理
4.4 负载均衡策略
轮询均衡
轮流分配,从 1 到 N 再到 1
适用于所有服务器硬件配置完全相同,服务请求需要相对均衡
权重轮询
根据服务器权重分配周期内的轮询次数
随机均衡
适用于数据量足够大的相对均衡分布
权重随机均衡
提升权重高的随机率
一致性哈希均衡
适用于服务器经常可能掉线或者加入,可以避免哈希键全部更新的情况
响应速度均衡
定期探测各个服务器的响应速度,根据速度分配权重
最少连接数均衡
根据连接数分配权重, 适用于长时处理服务例如 FTP 等
软件均衡器包括基于操作系统内核的 LVS、 基于应用程序的 Nginx、KeepAlive、HAProxy
硬件均衡器包括 F5、A10 等公司提供的硬件负载均衡产品
5 服务端缓存
引入缓存的理由:
减缓 CPU 计算压力
缓存 IO 压力
这 2 个缓解只是能峰值时的压力缓解,如果普通的响应都很慢,那就算用了缓存也意义不大。
5.1 缓存的几个属性
缓存需要选型,选型时需要根据实际场景选择你匹配的缓存熟悉
吞吐量
JDK8 改进后的 ConcurrentHashMap 是并发场景下吞吐量最高的缓存容器,但除了吞吐量其他的能力就很弱了。
缓存状态更新思路:
GuavaCache: 同步处理机制,在访问数据时一并更新,分段加锁减少竞争
Caffeine:异步日志提交机制,参考数据库日志,并且还有环形缓冲区容忍有损失的状态变更,读性能非常快, 使用多读少写的情况。
命中率和淘汰策略
基础的三种淘汰方案:
FIFO:先进先出,简单实现,但对于高频访问的缓存命中率低,越常用到越可能先进入队列
LRU:优先淘汰最久未被访问,基于时间, 用 HashMap+链表 List 实现,但每个缓存都要记录时间,且可能淘汰短期内正好没访问且价值高的数据
LFU:优先淘汰最不频繁使用,基于使用次数,可以解决 LRU 的缺点。
自身缺点:
每个缓存专门维护要更新次数的计数器,维护开销大还有加锁问题(LRU 的更新时间不需要考虑加锁,直接覆盖最新即可)
如果某个缓存某时期访问很高,比其他缓存高了一个数量级,后面不再使用,想淘汰很困难
为了解决上面 2 个缺点,有 2 个新的策略:
TinyLFU: 解决修改计数器的开销问题, 采用 Sketch 分析访问数据,用少量数据估计全体数据特征,采用滑动时间窗、热度衰减等处理
W-Tinfy-LFU: 结合了 LRU+LFU 的特点, 考虑热度和时间。
分布式能力
分布式缓存介绍了复制式缓存 JbossCache 以及集中式缓存 Memcached。
jbosscache 的缺点在于写入性能太差,容易因为网络同步速度跟不上写入速度,导致内存中积累过多待发对象引发 omm
memcached 是 C 语言实现的,好处在于读写性能高,缺点在于数据结构太过紧密,非常依赖序列化做跨语言传输,如果 100 个字段中的 1 个字段发生更新,要把 100 个字段都发出去更新
redis 基本打败了各种分布式缓存,成为首选。
对于 redis 等分布式缓存, 是不会追求一致性 C 的
如果一定要一致性 C, 那应该选用 zk 或者 etcd 等分布式协调框架(但他们一般就不会拿来做缓存,因为高并发下吞吐量太低,没有可用性)
进程内缓存和分布式缓存通常结合使用,但容易出现二者数据不一致,写维护策略导致缓存对开发者而言不透明。
一种设置原则是 变更以分布式缓存中的数据为主,访问以进程内缓存的数据优先。
大致做法是数据发生变动时, 在分布式缓存内推送通知, 让一级缓存失效。
访问缓存时,提供封装好的一二级联合查询接口, 让开发者对一二级缓存不感知。
5.2 缓存风险
缓存穿透
大量不存在的缓存打进来
要么是支持对不存在的数据缓存空值
要么是引入布隆过滤器
缓存击穿
同一时间瞬间涌现很多请求,访问数据库有但是缓存里没有的数据,此时可能直接打穿数据库(缓存生效是有延迟的)
可以是用锁、队列来完成同步
对于热点缓存,提前预处理或者配置策略
版权声明: 本文为 InfoQ 作者【华为云开发者联盟】的原创文章。
原文链接:【http://xie.infoq.cn/article/d91f204cc32394a736596f75e】。文章转载请联系作者。
评论