《深入架构原理与实践》📖✍🏻
一本关于架构的开源书籍:https://www.thebyte.com.cn
深入架构原理与实践
第一章:云原生技术概论
云计算的演进变革
- 物理机时代,业务的工作负载是整台物理机,资源没有隔离,也完全没有服务/资源供应商一说
- 虚拟化技术成熟,可以在一台物理机器上运行多个虚拟机
- 1959 年,Christopher Strachey 在国际信息处理大会上发表《Time Sharing in Large Fast Computer》论文,首次提出了“虚拟化”的概念
- 如果业务需要扩容,那就再开通一个虚拟机,整个过程只要几分钟
- 业务的工作负载由物理机转向虚拟机,资源有了初级的隔离,并且分配/利用更加合理,服务部署的速度和弹性也远超物理机
- 云计算技术成熟,虚拟化技术的成熟使得云计算市场开始真正形成
- 2006 年 8 月 9 日,Google 首席执行官 Eric Schmidt 在搜索引擎大会(SES San Jose 2006)首次提出“云计算”(Cloud Computing)的概念
- 基于虚拟化技术诞生了众多的云计算产品,陆续出现了 IaaS、PaaS、SaaS 以及公有云、私有云、混合云等多种云服务模型
- 容器技术的兴起,打破了 PaaS 行业面临应用分发和交付的困境,大力推动了云原生的发展
- 2013 年,Docker 发布,容器逐步替代虚拟机(Virtual Machine,VM),云计算进入容器时代
- 2017 年底,Kubernetes 赢得容器编排的胜利,云计算进入 Kubernetes 时代
- 云计算从仅提供计算、存储、网络资源的初级阶段,发展成为具备强大软件交付和维护能力的综合性服务平台
- 云计算的演进总结
- 工作负载的变化:从早期的 物理服务器,通过虚拟化技术演进为 虚拟机,再通过容器化技术演进为目前的 容器
- 隔离单元:无论是启动时间还是单元大小,物理机、虚拟机、容器一路走来,实现了 从重量级到轻量级的转变
- 供应商:从闭源到开源(从 VMware 到 KVM、OpenStack,再到 Kubernetes)从单一供应商到跨越多个供应商(从公有云到自建云,再到混合云)
云原生出现的背景
- 软件正在吞噬世界
- 2011 年 8 月 20 日华尔街日报上,Mark Andreessen 发表了名为“Why Software Is Eating the World”的文章
- 部分软件已经变成水电煤一样的社会经济中的基础设施
- 移动互联网在加剧变化
- 按照 规模和变更速度 将软件企业划分为四个象限/四种类型
- 企业 IT(Enterprise IT):规模小、变化慢,容易处理
- 电信(Telcos):规模大、变化慢,主要应对硬件失败
- 初创公司(Startups):规模小、变化快,主要应对软件失败
- 网络规模的互联网企业(Web-Scale):规模大、变化快,软硬件或者说所有东西都会出问题
- 移动互联网时代的用户规模已经开始向人口基数看齐,开始出现各类亿级 DAU 规模的移动应用
- 在移动互联网时代,能够成长并发展起来的这些公司,它们的共同点是:
- 快速变更,不断创新,随时调整
- 提供持续可用的服务,应对各种可能的错误和中断
- 弹性可扩展的系统,应对用户规模的快速增长
- 提供新的用户体验,以移动为中心
- 这样的背景下,对软件质量有了更高的要求,首当其冲的就是 如何解决规模越来越大同时变更越来越快的难题
- 按照 规模和变更速度 将软件企业划分为四个象限/四种类型
- 云原生诞生
- 软件对各行各业的渗透和对世界的改变,以及移动互联网时代巨大的用户基数下快速变更和不断创新的需求 对软件开发方式带来的巨大推动力
- 在过去二十年,云的底层基础设施和平台越来越强大,软件架构的发展也逐渐和云匹配:
- 通过不可变基础设施(镜像)解决本地和远程一致性问题
- 通过服务网格(ServiceMesh)将非业务逻辑从应用程序中剥离
- 通过声明式 API 描述应用程序的状态,而不用管中间的处理过程
- 通过 DevOps 方法论以及一系列工具来提升研发/运维效率
- 应用程序中的非业务逻辑不断被剥离,并下沉到云/基础设施层,代码越来越轻量,工程师的开发工作回归本质
- 软件开发的本质是 解决业务需求,各类“高深”、“复杂”的技术难题是 业务需求的副产物,并不是软件开发的主题
云原生的定义与目标
云原生是一个组合词,“云”表示应用程序运行于分布式云环境中,“原生”表示应用程序在设计之初就充分考虑到了云平台的弹性和分布式特性,充分利用云计算优势对应用程序进行设计、实现、部署、交付的理念方法
- 2015 年,来自 Pivotal 公司的技术产品经理 Matt Stine,首次提出了云原生(Cloud Native)的概念
- 在 Pivotal 最新的官方网站中,对云原生的介绍则是关注 4 个要点:
- DevOps(开发运维)
- Continuous Delivery(持续交付)
- Microservices(微服务)
- Containers(容器化)
- CNCF 对云原生的定义
- 2015 年 CNCF(Cloud Native Computing Foundation,云原生计算基金会)建立,开始围绕云原生的概念打造云原生生态体系
- CNCF 是 Linux 基金会旗下的基金会,成立这个组织的初衷或者愿景是推动云原生计算可持续发展,帮助云原生技术开发人员快速地构建出色的产品
- 起初 CNCF 对云原生的定义包含以下三个方面:
应用容器化:容器化是云原生的基础
面向微服务架构:实施微服务是构建大规模系统的必备要素
应用支持容器的编排调度:编排调度是指能够对容器应用的部署、扩展、运行和生命周期进行自动化管理
- 2018 年 6 月,CNCF 正式对外公布了更新之后的云原生的定义 v1.0 版本:
云原生技术有利于各组织在公有云、私有云和混合云等新型动态环境中,构建和运行可弹性扩展的应用
云原生的代表技术包括:容器、服务网格、微服务、不可变基础设施和声明式 API
这些技术能够构建 容错性好、易于管理和便于观察的松耦合系统
结合可靠的 自动化手段,云原生技术使工程师能够轻松地对系统作出频繁和可预测的重大变更
- 云原生的定义是什么并不重要,关键还是理解实施云原生有什么好处,以及实施云原生所涉及的技术/工具、架构设计的依据等
云原生的目标主要是帮助企业和开发者 构建、运行和管理现代化应用,以便在云环境中实现高效、敏捷和弹性的业务交付
- 可用(Available):通过各种机制来实现应用的高可用,以保证服务提供的连续性
- 规模(Scale):能够适应不同的规模(包括但不限于用户规模/部署规模/请求量),并能够在部署时动态分配资源,以便在不同的规模之间快速和平滑的伸缩,典型场景如:
- 初创公司或新产品线快速成长,用户规模和应用部署规模在短时间内十倍百倍增长
- 促销、季节性、节假日带来的访问量波动
- 高峰时间段的突发流量等
- 敏捷(Agility):快速响应市场需求
- 成本(Cost):充分有效的利用资源
这 4 个核心目标之间,存在彼此冲突的情况:
- 规模和敏捷之间的冲突:规模大而又要求敏捷,比喻为“巨人绣花”
- 规模和可用性之间的冲突:规模大而要求可用性高,比喻为“大象起舞”
- 敏捷和可用性之间的冲突:敏捷而要求高可用,比喻为“空中换发”
而云原生架构必须要在同时满足这 3 个彼此冲突目标的前提下,还要实现成本控制
云原生代表技术概览
- 容器技术
- chroot 阶段:隔离文件系统
- 一个叫做 chroot(Change Root)的系统调用被认为是最早的容器化技术之一
- 可以重定向进程及其子进程的 root 目录到文件系统上的新位置,即分离每个进程的 文件访问权限,使得该进程无法接触到外面的文件
- 通过 chroot 隔离出来的新环境得到了一个非常形象的命名 Jail(监狱),这便是容器最重要的特性 —— 隔离
- LXC 阶段:封装系统
- 2008 年 Linux 内核版本 2.6.24 刚开始提供 cgroups(Control Groups),提供操作系统级别的 资源限制、优先级控制、资源审计和进程控制能力
- 社区开发者就将 cgroups 资源管理能力和 Linux namespace 资源隔离 能力组合在一起,形成了完整的容器技术 LXC(Linux Container)
- Docker 阶段:封装应用
- Docker 的核心创新“容器镜像(container image)”
- 容器镜像打包了整个容器运行依赖的环境,以避免依赖运行容器的服务器的操作系统,从而实现“build once,run anywhere”
- 容器镜像一但构建完成,就变成只读状态,成为不可变基础设施的一份子
- 与操作系统发行版无关,核心解决的是容器进程对操作系统包含的库、工具、配置的依赖(注意,容器镜像 无法解决容器进程对内核特性的特殊依赖)
- 开发者基于镜像打包应用所依赖的环境,而不是改造应用来适配 PaaS 定义的运行环境
- 现阶段容器技术体系已经解决了 最核心的两个问题“如何运行软件和如何发布软件”,云计算开始进入容器阶段
- Docker 的核心创新“容器镜像(container image)”
- OCI 阶段:容器标准化
- Linux 基金会联合 Docker 带头成立 OCI(Open Container Initiative,开放容器标准)项目
- OCI 组织着力解决容器的构建、分发和运行标准问题,其宗旨是制定并维护 OCI Specifications(容器镜像格式和容器运行时的标准规范)
- 容器编排阶段:封装集群
- 以 Docker 为代表的容器引擎,是把软件的发布流程从分发二进制安装包,转变为了直接分发虚拟化后的整个运行环境
- 以 Kubernetes 为代表的容器编排框架,就是把大型软件系统运行所依赖的集群环境也进行了虚拟化
- 云原生阶段:百花齐放
- 2015 年 7 月 21 日,Google 带头成立了 Cloud Native Computing Foundation(CNCF,云原生基金会)
- OCI 和 CNCF 这两个围绕容器的基金会共同制定了一系列行业事实标准,基于接口标准的具体实现不断涌现,呈现出一片百花齐放的景象
- chroot 阶段:隔离文件系统
- 微服务
- 微服务架构是一种面向服务的架构,由松耦合的具有有限上下文的元素组成
- 松耦合(Loosely Coupled):意味着每个服务可以独立的更新,更新一个服务无需要求改变其他服务
- 限界上下文(Bounded Contexts):意味着每个服务要有明确的边界性,你可以只关注自身软件的发布,而无需考虑谁在依赖你的发布版本
- 微服务和它的消费者严格通过 API 进行交互,不共享数据结构、数据库等,基于契约的微服务规范要求服务接口是稳定的,而且向下兼容
- 微服务架构的特征是:服务之间独立部署,拥有各自的技术栈,各自界定上下文
- 微服务带来的技术挑战
- 微服务架构首先是一个分布式的架构,软件架构从巨石应用向微服务架构转型的过程中带来了一系列的非功能性需求,例如:
- 服务发现(Service Discovery)问题:解决“我想调用你,如何找到你”的问题
- 服务熔断(Circuit Breaker)问题:缓解服务之间依赖的不可靠问题
- 负载均衡(Load Balancing)问题:通过均匀分配流量,让请求处理更加及时
- 安全通讯问题:包括协议加密(TLS)、身份认证(证书/签名)、访问鉴权(RBAC)等
- 解决这些问题需要编写和维护大量非功能性代码,这些代码与业务代码逻辑混在一起,基础设施不完善的话,实施微服务会很痛苦,服务越多越悲剧
- 微服务架构首先是一个分布式的架构,软件架构从巨石应用向微服务架构转型的过程中带来了一系列的非功能性需求,例如:
- 后微服务时代
- 之所以选择在应用服务层面,而非基础设施层面去解决这些分布式问题,主要是 因为硬件构建的基础设施无法追赶上软件构成的应用服务的灵活性
- 被业界广泛认可、普遍采用的 通过虚拟化基础设施去解决分布式架构问题 的开端,应该要从 2017 年 Kubernetes 赢得容器战争的胜利开始算起
- 一旦虚拟化的硬件能够跟上软件的灵活性,那些与业务无关的技术性问题便有可能从软件层面剥离,并悄无声息地解决于硬件基础设施之内
- 但对于 Kubernetes,由于基础设施粒度更粗糙,通常只能管理到容器层面,对单个远程服务的有效管理就相对困难,服务的监控、认证、授权、安全、负载均衡等都有可能面临细化管理的需求
- 为了解决这一类问题,微服务基础设施很快进行了第二次进化,引入了今天被称为“服务网格”(Service Mesh)的模式
- 微服务架构是一种面向服务的架构,由松耦合的具有有限上下文的元素组成
- 服务网格
- 服务网格(ServiceMesh)是一个 基础设施层,用于处理服务间通信。云原生应用有着复杂的服务拓扑,服务网格保证 请求在这些拓扑中可靠地穿梭。在实际应用当中,服务网格通常是由一系列轻量级的 网络代理 组成的,它们与应用程序部署在一起,但 对应用程序透明
- 服务网格的关键在于 Sidecar 模式,服务网格将具有流控能力的网络代理以 Sidecar 的方式部署,各个微服务之间通过 Sidecar 发现和调用目标服务,从而在服务之间形成一种网络状依赖关系
- 在 Kubernetes 的工作负载 Pod 中可以运行多个容器,其中 所有业务容器之外的其他容器均可称为 Sidecar,如日志收集 Sidecar、请求代理 Sidecar 和链路追踪 Sidecar 等
- 服务网格本质是通过 iptables 劫持发送到应用容器的流量,将原本在业务层处理的分布式通信治理相关的技术问题,下沉到具有流控能力的 Sidecar 中处理,实现业务与非业务逻辑解耦 的目的
- 不可变基础设施
- “可变”的基础实施与传统运维操作相关,比如手动对服务器进行变更,部署的是 Apache,换成 Nginx(在原有的基础上做原地更新)
- 不可变基础设施的 核心思想是任何基础设施的运行实例一旦创建之后就变成只读状态,如需修改应先修改基础设施的配置模版(例如 yaml、Dockerfile)
- 从容器的角度看,镜像就是一个不可变基础设施,开发工程师交付的产物从一个有着各种依赖条件的安装包变成一个不依赖任何环境的镜像文件
- 对比可变基础设施,不可变基础设施的最大的优势是一致性,快速拉起成千上万一模一样的服务,服务的版本升级、回滚也成为常态
- 声明式设计
- 声明式设计是一种软件设计理念和做法:“向一个工具描述想要让一个事物达到的目标状态,由工具内部去解决如何令这个事物达到目标状态”
- 命令式设计:命令“机器”如何去做事情(how),这样不管你想要的是什么(what),它都会按照你的命令实现
- 声明式设计:告诉“机器”你想要的是什么(what),让机器想出如何去做(how),比如 SQL、Kubernetes 的 YAML
- DevOps
- 发展,瀑布 → 敏捷
- 瀑布开发,该模型下整个软件开发流程严格遵循需求、设计、开发、测试和部署几个阶段,需要等上一个阶段完成工作后,才会进行下一阶段的工作
- 敏捷开发,一种持续增量、不断迭代的开发模式,快速发布一个可运行但不完美的版本投入市场,在后续迭代中根据用户的反馈改进产品,从而逼近产品的最终形态
- 迭代是敏捷开发理论的核心,具体的敏捷研发方法有极限编程、精益软件开发、Scrum 等
- DevOps 出现的背景
- 虽然敏捷开发提升了开发效率,但它的范围仅限于开发和测试阶段,并没有覆盖到部署端,运维部门并没有在这其中得到收益
- 相反的,甚至可以说“敏捷”加重了运维的负担。因为运维追求的目标是稳定,而频繁的变更往往就是出现问题的根源
- 从存在的意义上说,DevOps 完善了敏捷开发存在的短板,实现了真正的闭环
- DevOps(Development 和 Operations 的合成词)是一种重视“软件开发人员(Dev)”和“IT 运维技术人员(Ops)”之间沟通合作的文化、运动或惯例
- 通过自动化“软件交付”和“架构变更”的流程,来使得构建、测试、发布软件能够更加地快捷、频繁和可靠
- 运维会在项目开发阶段就介入,了解开发所使用的系统架构和技术路线,并制定好相关的运维方案;而开发人员也会参与到后期的系统部署和日常发布中,并提供优化建议,而不再是把代码甩给运维了事
- DevOps 的成功实践也离不开工具的支持,这其中就包括最重要的自动化 CI/CD 流水线,通过自动化的方式打通软件从构建、测试到部署发布的整个流程,还有包括实时监控、事件管理、配置管理、协作平台等一系列工具的配合
- 微服务架构理念、容器技术和云计算的发展,让 DevOps 的实施更加便捷,这也解释了为何 DevOps 理念在十多年前就已提出,但直到近几年才开始被企业广泛关注和实践
- 发展,瀑布 → 敏捷
传统架构向云原生架构的演进之路
为了解决单体架构“复杂度问题”,使用微服务架构
为了解决微服务间“通讯异常问题”,使用治理框架 + 监控
为了解决微服务架构下大量应用“部署问题”,使用容器
为了解决容器的“编排和调度问题”,使用 Kubernetes
为了解决微服务框架的“侵入性问题”,使用服务网格
为了让服务网格有“更好的底层支撑”,将服务网格运行在 Kubernetes 上
但站在整个系统的角度看,复杂度并没有减少和消失,要实现“强大底层系统”付出的成本(人力成本、资源成本、技术试错成本)是非常昂贵的,为了降低成本,选择上云托管,将底层系统的复杂度交给云基础设施,让云提供保姆式服务,最终演变为无基础架构设计
云原生架构技术栈
第二章: 构建“足够快”的网络服务
Latency Numbers Every Programmer Should Know
秒(s)、毫秒(ms)、微秒 (μs)、纳秒 (ns)之间关系:$1s = 10^3ms=10^6μs=10^9ns$
HTTPS 优化分析
cURL 是一个开源项目,主要的产品是 curl(命令行工具)和 libcurl(C 语言的 API 库),两者功能均是:基于网络协议,对指定 URL 进行网络传输
- 请求阶段分析
- 一个完整、未复用连接的 HTTPS 请求需要经过以下 5 个阶段:DNS 域名解析、TCP 握手、SSL 握手、服务器处理、内容传输
- 各阶段耗时分析
- HTTPS 请求的各个阶段可以使用 curl 命令进行详细的延迟分析
- HTTPS 的优化总结
- 域名解析优化:减少域名解析产生的延迟,例如使用预解析提前获取域名解析结果,那么 HTTPS 连接就能减少一个 RTT
- 对传输内容进行压缩:传输数据的大小与耗时成正比,压缩传输内容是降低请求耗时最有效的手段之一
- SSL 层优化:升级 TLS 算法和 HTTPS 证书,例如升级 TLS 1.3 协议,可将 SSL 握手的 RTT 从 2 个减少到 1 个
- 传输层优化:升级拥塞控制算法以提高网络吞吐量,将默认的 Cubic 升级为 BBR 对于大带宽、长链路的弱网环境尤其有效
- 网络层优化:使用商业化的网络加速服务,通过路由优化数据包,实现动态服务加速
- 使用更现代的 HTTP 协议:升级至 HTTP/2,进一步升级到基于 QUIC 协议的 HTTP/3
RTT(Round-Trip Time)一个网络数据包从起点到目的地然后再回到起点所花费的时长
域名解析的原理
DNS(Domain Name System,域名系统)是互联网中最重要的基础设施,它的主要职责是实现域名的解析,也就是 将域名转换为 IP 地址
graph TB root["."] --- com[".com"] root --- org[".org"] root --- be[".be"] com --- google["google.com"] com --- chess["chess.com"] be --- linux["linux-training.be"] %% 样式定义 style root fill:#FFFFFF,stroke:#333,stroke-width:2px style com fill:#FFDDC1,stroke:#333,stroke-width:2px,rx:10px,ry:10px style org fill:#FFDDC1,stroke:#333,stroke-width:2px,rx:10px,ry:10px style be fill:#FFDDC1,stroke:#333,stroke-width:2px,rx:10px,ry:10px style google fill:#D4E4FF,stroke:#333,stroke-width:2px,rx:10px,ry:10px style chess fill:#D4E4FF,stroke:#333,stroke-width:2px,rx:10px,ry:10px style linux fill:#D4E4FF,stroke:#333,stroke-width:2px,rx:10px,ry:10px %% 连接线样式 linkStyle default stroke:#FF6347,stroke-width:2px,stroke-opacity:0.8
域名是一种 树状结构,最顶层的域名是根域名(注意是一个点“.”,它是 .root 的含义,不过现在“.root”已经默认被隐藏),然后是顶级域名(Top Level Domain,简写 TLD,例如 .com),再是二级域名(例如 google.com)
通常情况下的域名解析过程,其实就是从“域名树”的根部到顶部,不断 递归查询 的过程:
- 用户向“DNS 解析器”(Recursive resolver)发出解析域名请求,“DNS 解析器”也称 LocalDNS,例如电信运营商的 114.114.114.114
- “DNS 解析器” 判断是否存在解析缓存
- 如存在,直接返回缓存的结果
- 如不存在,向就近的“根域名服务器”查询域名所属“TLD 域名服务器”(Top-Level Domains nameserver)
- 根域名服务器返回相应的 TLD 域名服务器地址
- “DNS 解析器”向 TLD 服务器请求该域名的“权威域名服务器”(Authoritative nameserver)
- TLD 服务器返回权威域名服务器的地址
- “DNS 解析器”向权威域名服务器查询域名的具体解析记录(如 A 记录或 CNAME 记录)
- 获取解析记录后,“DNS 解析器”将结果返回给用户
sequenceDiagram participant Client as 用户 participant Resolver as DNS 解析器 (LocalDNS) participant Root as 根域名服务器 participant TLD as TLD 域名服务器 participant Authoritative as 权威域名服务器 Client->>Resolver: 请求解析域名 alt 存在缓存 Resolver->>Client: 返回缓存的结果 else 不存在缓存 Resolver->>Root: 查询 TLD 域名服务器 Root->>Resolver: 返回 TLD 域名服务器地址 Resolver->>TLD: 查询权威域名服务器 TLD->>Resolver: 返回权威域名服务器地址 Resolver->>Authoritative: 查询域名解析记录 Authoritative->>Resolver: 返回解析记录 Resolver->>Client: 转发解析记录 end
实际上“根域名服务器”的数量远不止 13 台,截止 2024 年 7 月,全世界共有 1,845 台根域名服务器
权威域名服务器通常是指顶级域名以下的管理二级、三级、四级等域名的服务器,多个域名解析到同一个 IP 地址(通过 CNAME 记录),服务器仍然可以根据 Host 头信息准确区分并响应不同的请求
域名解析故障时排查
如果请求一个 HTTPS 接口,出现服务不可用、Unknown host 等错误时,除了用 ping 测试连通性外,可以用 nslookup 或者 dig 命令确认域名解析是否出现问题
- nslookup 返回的结果比较简单,但从中可以看出用的哪个“DNS 解析器”,域名的解析是否正常
- 当怀疑系统默认的“DNS 解析器”异常时,可以使用 dig 命令,通过切换不同的“DNS 解析器”,分析解析哪里出现异常
使用 HTTPDNS 解决“中间商”问题
“域名解析器”是 DNS 查询中的第一站,它作为客户端与“域名服务器”的中间人帮我们去整棵 DNS 树上进行解析,然后将解析的结果返回给客户端
但作为一个“中间商”,“域名解析器”很容易出现域名劫持、解析时间过长、解析调度不精准等问题,这些问题的根源在于 域名解析经历了过多的中间环节,服务质量不可控,为了解决上述问题,一种新型的 DNS 解析模式 —— HTTPDNS 应运而生
HTTPDNS 的工作原理:
- 客户端内部集成 HTTPDNS 模块,跳过“操作系统定义的解析服务”(LocalDNS,也就是默认基于 UDP 协议的域名解析系统)
- 替换为使用 HTTPS 协议请求更可靠的“软件定义的解析服务”,直接从“权威域名服务器”同步解析记录
- 避免了“中间商赚差价”,逻辑更可控,也能准确判断客户端地区和运营商,得到更精准的解析结果
通过使用 HTTPDNS ,再结合客户端解析缓存、热点域名预解析、懒加载等优化策略,能明显改善传统域名解析带来的各类问题
对传输内容进行压缩
对传输内容进行压缩是提升 HTTP 服务可用性的关键手段,一般使用 Gzip 对内容进行压缩,但针对 HTTP 类型的文本内容还有一个更高压缩率的算法 Brotli,压缩效果 Gzip 高出 17% - 30%
sequenceDiagram autonumber participant Client as Client participant Server as Server Client->>+Server: 请求资源
GET /doc HTTP/1.1
Accept-Encoding: br, gzip Note right of Client: 客户端通过
Accept-Encoding
告知服务器支持
压缩算法:br, gzip Server-->>-Client: 返回资源
HTTP/1.1 200 OK
Content-Encoding: br
Vary: Accept-Encoding Note left of Server: 服务器通过 Vary
告知客户端资源已
使用 br 算法压缩
HTTPS 原理及实践
HTTPS 加密原理
非对称加密 算法通常需要更多的计算资源,尤其在加密或解密大量数据时计算消耗更大。因此使用计算效率更高的 对称加密 算法来加密 HTTP 内容,非对称加密算法来加密对称加密的密钥:
- 客户端与服务端会进行协商,确定一个双方都支持的对称加密算法,例如 AES
- 确认对称加密算法后,客户端会随机生成一个对称加密密钥 K
- 客户端使用服务端的公钥加密密钥 K,并将密文传输给服务端,此时,只有服务端的私钥能够解密密钥 K
实现了”降低加/解密的耗时,同时又保证密钥传输的安全性“,,达成既要安全又要效率的目标 (机密性 + 完整性),但“如何将服务器公钥传输给客户端呢?”
公钥仍有被劫持的可能性:
sequenceDiagram participant Client as Client participant Middle as Middle participant Server as Server Client->>Server: 请求公钥 Server->>Middle: 发送公钥 Middle->>Client: 替换公钥 Client->>Client: 本地保存假公钥 Client->>Middle: 假公钥加密 hello Middle->>Middle: 内容篡改 Middle->>Server: 真公钥加密 hi Server->>Middle: 真私钥加密 good Middle->>Middle: 内容篡改 Middle->>Client: 假私钥加密 bad
上述问题的根本原因是,浏览器无法确认收到的公钥是不是网站自己的, 因为公钥本身是明文传输的
所有证明的源头都是一条或多条不证自明的“公理”,若想证明某身份证号一定是小明的,可以看他身份证,而身份证是由政府作证的,这里的“公理”就是“政府机构可信”,这也是社会正常运作的前提
CA 机构(CA,Certificate Authority,证书认证机构)是如今互联网世界正常运作的前提,而 CA 机构颁发的“身份证”就是数字证书
网站在使用 HTTPS 前,需要向 CA 机构申领一份数字证书,数字证书里含有 证书持有者、域名、公钥、过期时间等信息,服务器把证书传输给浏览器,浏览器从证书里获取公钥就行了,那“证书本身的传输过程中,如何防止被篡改?”
把证书原本的内容生成一份“签名”,比对证书内容和签名是否一致就能判别是否被篡改。这就是数字证书的“防伪技术”,这里的“签名”就叫数字签名:
- CA 机构拥有非对称加密的私钥和公钥
- CA 机构对证书明文数据 T 进行 hash (完整性)
- 对 hash 后的值用私钥加密,得到数字签名 S
明文和数字签名共同组成了数字证书,这样一份数字证书就可以颁发给网站了,数字证书是为了证明某公钥是可信的,即“该公钥是否对应该网站”(身份认证)
拥有 CA 的公钥 是验证数字证书真伪的关键,浏览器通常内置了多个受信任的 CA 的公钥,以便自动验证网站的数字证书,浏览器收到该证书后会校验证书原文的 hash 和签名解密后的 hash 值是否一致
CA 机构的公钥本身也可以用数字证书来证明,操作系统、浏览器本身会预装一些它们信任的根证书,由根证书为起点,透过层层信任,使终端实体证书的持有者可以获得转授的信任,以证明身份(信任链或数字证书链)
从整个流程来看,HTTPS 安全性的关键在于根证书是否被篡改。如果根证书被篡改,那么信息传递也就不再是‘绝对’安全的
HTTPS 优化实践
使用 TLS1.3 协议
- 2018 年发布的 TLS 1.3 协议优化了 SSL 握手过程,将握手时间缩短至 1 次 RTT,如果复用之前的连接,甚至可以实现 0 RTT
使用 ECC 证书
- HTTPS 数字证书分为 RSA 证书和 ECC 证书,二者的区别在于:
- RSA 证书使用的是 RSA 算法生成的公钥,兼容性好,但不支持 PFS(Perfect Forward Secrecy,完美前向保密,保证即使私钥泄露,也无法破解泄露之前通信内容)
- ECC 证书使用的是椭圆曲线算法(Elliptic Curve Cryptography)生成的公钥,它的 计算速度快,安全性高,支持 PFS,能以 更小的密钥长度提供更高的安全性
- 相较于 RSA 证书,ECC 证书的唯一缺点是兼容性稍差,例如 Android 4.0 以上版本才能支持 ECC 证书
- ECC 证书的生效与客户端和服务端协商的密码套件(Cipher Suite)直接相关,密码套件决定了通信双方使用的加密、认证算法和密钥交换算法
- HTTPS 数字证书分为 RSA 证书和 ECC 证书,二者的区别在于:
调整 https 会话缓存
- 在 HTTPS 连接建立后,会生成一个 session,用于保存客户端和服务器之间的安全连接信息,如果 session 未过期,后续连接可以复用先前的握手结果,从而提高连接效率
开启 OCSP stapling
- 客户端在首次下载数字证书时会向 CA 发起 OCSP(在线证书状态协议)请求,以验证证书是否被撤销或过期
- OCSP Stapling 是一种 TLS 扩展,它将 OCSP 查询的工作交由服务器处理,服务器会预先获取 OCSP 响应并将其缓存
- 当客户端发起 TLS 握手请求时,服务器将证书的 OCSP 信息与证书链一起发送给客户端,从而避免了客户端在验证证书时可能出现的阻塞问题
使用 https://myssl.com/ 服务验证优化是否生效
第三章:深入 Linux 内核网络技术
OSI 网络分层模型
层级 | 名称 | 含义 |
---|---|---|
7 | 应用层(Application Layer) | 应用层是 OSI 模型的顶层,直接与用户的应用程序交互,提供网络服务如电子邮件、文件传输、网页浏览等,常见的应用层协议有 HTTP、FTP、SMTP 等 |
6 | 表示层(Presentation Layer) | 表示层负责数据的格式化和转换,确保发送方和接收方能够理解彼此传输的数据格式,它还处理数据的加密、解密和压缩等操作 |
5 | 会话层(Session Layer) | 会话层管理应用程序之间的会话或连接,负责建立、维护和终止通信会话,它还提供会话恢复功能,使得通信中断后可以从上次中断的位置继续传输 |
4 | 传输层(Transport Layer) | 传输层负责端到端的数据传输管理,提供可靠的传输服务,如数据分段、重组、流量控制和错误校正等,常见的传输层协议有 TCP 和 UDP |
3 | 网络层(Network Layer) | 网络层负责不同网络之间的数据路由和转发,它使用 IP 地址来确定数据包的传输路径,并确保数据能够从源设备传输到目的设备,即使它们位于不同的网络中 |
2 | 数据链路层(Data Link Layer) | 数据链路层负责节点之间的数据传输,提供错误检测和纠正功能,它确保数据帧能够在同一局域网(LAN)内可靠传输,数据链路层使用 MAC(Media Access Control,介质访问控制)地址来标识网络上的设备 |
1 | 物理层(Physical Layer) | 物理层是 OSI 模型的第一层,负责实际的硬件传输,如电缆、光纤、开关和集线器等,它处理二进制数据(即 bit)在物理介质上的传输,包括信号的电气/光学特性、传输速率和传输模式等 |
数据链路层的数据单元被称为 帧(Frames),网络层的数据单元为称为 数据包(Packets),传输层的数据单元被称为 数据段(Segments),应用层数据单元被称为 数据(Data),用“数据包”泛指以上数据单元
Linux 系统收包流程
数据包进入网卡(eth0)后,Linux 内核中各个模块相互协作
- 当外部网络发送数据包到服务器时,首先由网卡 eth0 接收该数据包
- 网卡通过 DMA(Direct Memory Access,直接内存访问)技术,将数据包直接拷贝到内核中的 RingBuffer(环形缓冲区)等待 CPU 处理
- RingBuffer 是一种首尾相接的环形数据结构,它的主要作用是作为缓冲区,缓解网卡接收数据的速度快于 CPU 处理数据的速度问题
- 数据包成功写入 RingBuffer 后,网卡产生 IRQ(Hardware Interrupt Request,硬件中断),通知内核有新的数据包到达
- 内核收到硬件中断后,立即调用对应的中断处理函数,通常情况下,中断处理函数会简单地标记有新数据到达,并唤醒 ksoftirqd 内核线程来处理 软中断(SoftIRQ)
- 软中断处理过程中,内核调用网卡驱动 提前在内核中注册的 NAPI(New API)poll 接口,从 RingBuffer 中提取数据包,并生成 skb(Socket Buffer)数据
- skb 是 Linux 内核中用于管理网络数据包的主要结构,它包含了网络包的所有信息,包括头部、数据负载等,并在内核的各个网络协议层之间传递
- skb 被传递到内核协议栈中进行处理。这里涉及多个网络层次的处理操作:
- 网络层(L3 Network layer):根据主机中的路由表,判断数据包路由到哪一个网络接口(Network Interface),这里的网络接口可能是稍后介绍的虚拟设备,也可能是物理网卡 eth0 接口
- 传输层(L4 Transport layer):如解/封数据包,处理网络地址转换(NAT)、连接跟踪(conntrack)等操作
- 内核协议栈处理完成后,数据包被传递到 socket 接收缓冲区,应用程序随后利用 系统调用(如 Socket API)从缓冲区中读取数据
数据包的处理流在不同层级之间需要进行多次上下文切换和拷贝操作,导致延迟增加,对于处理大规模并发连接的网络密集型系统,Linux 内核造成的瓶颈就变得不可忽视,除了优化内核网络协议栈外,业界出现了“绕过内核”这一思想的技术,例如 XDP 和 DPDK 技术
第四章:负载均衡技术
负载均衡概述
graph LR Client([Clients]) --> LB([Load Balancer]) LB --> RS1([RealServer1]) LB --> RS2([RealServer2]) LB --> RS3([RealServer3]) %% 节点样式 style Client fill:#C0C0C0,stroke:#333,stroke-width:2px style LB fill:#FFD700,stroke:#333,stroke-width:2px style RS1 fill:#ADD8E6,stroke:#333,stroke-width:2px style RS2 fill:#ADD8E6,stroke:#333,stroke-width:2px style RS3 fill:#ADD8E6,stroke:#333,stroke-width:2px %% 箭头样式 linkStyle default stroke:#333,stroke-width:2px
从整体架构来看,中间的 负载均衡器 承担下述职责:
- 服务发现:识别系统中可用的后端服务器,并获取它们的地址,以便负载均衡器与后端通信
- 健康检查:确定哪些后端服务器处于健康状态并能够接收请求
- 负载均衡:基于适当的算法将请求均匀分配到健康的后端服务器上
合理使用负载均衡能为分布式系统带来诸多好处:
- 命名抽象:客户端通过预设机制(如 DNS 或内置库)访问负载均衡器,而 无需了解后端服务器的拓扑结构或配置
- 容错:通过健康检查和多种负载均衡算法,将请求均匀转发至正常的后端服务器,故障服务器则会被移出 负载均衡池,便于运维人员从容修复
- 成本和性能收益:分布式系统通常是异构的,后端服务器分布在多个网络区域(Zone/Region),负载均衡器通过策略 优先将请求保持在同一网络区域内,从而提高服务性能(减少延迟)并降低资源成本(减少跨区域带宽费用)
从网络层处理请求的层面看,所有的负载均衡可总结为两类:
- 四层负载均衡(比如 K8s 的 Service, K8s 是在应用层通过抽象和控制维护了一个集群内的“虚拟网络系统”)
- 并非仅指其在 OSI 模型的第四层传输层工作
- 而是指四层负载均衡所有工作模式的共同特点是,维持传输层连接(如 TCP、UDP)特性
- 沿用惯例,来自客户端的请求,无论是在网络层处理,还是在传输层的处理,统称为四层负载均衡处理
- 七层负载均衡(比如 K8s 的 Ingress)
特性 | 四层负载均衡(L4) | 七层负载均衡(L7) |
---|---|---|
工作层次 | 传输层(TCP/UDP) | 应用层(HTTP/HTTPS) |
处理速度 | 较快(在内核层面处理,低延迟) | 相对较慢(增加了应用层的处理开销) |
灵活性 | 较低,基于 IP 和端口进行路由 | 高,基于内容、请求头等进行路由 |
功能 | 主要实现流量分发 | 支持内容缓存、内容路由、SSL 终止(SSL Termination)等高级功能 |
适用场景 | 实时应用、简单的 TCP/UDP 服务 | Web 应用、微服务架构、复杂请求处理 |
负载均衡调度算法
负载均衡的另一个重要职责:“选择谁来处理用户请求”,也就是负载均衡器所采用的调度算法
一些常见的负载均衡算法:
- 轮询均衡算法(Round-Robin):按依次循环的方式将请求调度到不同的服务器上
- 该算法最大的特点是实现简单,轮询算法假设所有的服务器处理请求的能力都一样
- 调度器会将所有的请求平均分配给每个真实服务器
- 随机均衡算法(Random):此种负载均衡算法类似于轮询调度,不过在分配处理请求时是随机的过程
- 由概率论可以得知,随着客户端调用服务端的次数增多,其实际效果趋近于平均分配请求到服务端的每一台服务器也就是达到轮询的效果
- 最小连接均衡算法(Least-Connection):该算法中 调度器需要记录各个服务器已建立连接的数量,然后把新的连接请求分配到当前连接数最小的服务器
- 最小连接均衡算法特别适合于服务器处理时间不一致的场景
- 例如,当某些请求可能占用较长时间,而另一些请求很快就会完成时,最小连接算法可以有效避免某些服务器因处理大量复杂请求而过载
- 哈希均衡算法(Consistency Hash):将请求中的某些特征数据(例如 IP、MAC 或者更上层应用的某些信息)作为特征值来计算需要落在的节点
- 哈希算法会保证同一个特征值的请求每一次都会落在相同的服务器上
- 一致性哈希是一种改进的哈希算法,设计目的是解决传统哈希均衡在节点增减时的 重分布 问题
不考虑服务器的处理能力的负载均衡算法,实际上是一种“伪均衡”算法,考虑各个服务器的处理能力存在差异,负载均衡算法又有了对服务器“加权”的补充
负载均衡拓扑类型
中间代理拓扑
graph LR Client([Client]) --> LB([Load Balancer]) LB --> RS1([RealServer1]) LB --> RS2([RealServer2]) LB --> RS3([RealServer3]) %% 节点样式 style Client fill:#C0C0C0,stroke:#333,stroke-width:2px,rx:10px,ry:10px style LB fill:#FFD700,stroke:#333,stroke-width:2px,rx:10px,ry:10px style RS1 fill:#ADD8E6,stroke:#333,stroke-width:2px,rx:10px,ry:10px style RS2 fill:#ADD8E6,stroke:#333,stroke-width:2px,rx:10px,ry:10px style RS3 fill:#ADD8E6,stroke:#333,stroke-width:2px,rx:10px,ry:10px %% 箭头样式 linkStyle default stroke:#333,stroke-width:2px
典型的中间代理拓扑的方案有:
- 硬件设备:Cisco、Juniper、F5 Networks 等公司的产品
- 云软件解决方案:阿里云的 SLB(Server Load Balancer),AWS 的 ELB(Elastic Load Balancer)、Azure 的 Load Balancer 和 Google Cloud 的 Cloud Load Balancing 等
- 纯软件方案:Nginx、HAProxy、Envoy 等
中间代理模式的优点在于简单,用户只需通过 DNS 连接到负载均衡器,无需关注其他细节,缺点是,中间代理可能存在单点故障风险,尤其是负载均衡这种集中式的设计,如果负载均衡器出现问题,会导致整个系统的访问中断
边缘代理拓扑(中间代理拓扑的一个变种)
graph LR Client([Client]) --> Internet([Internet]) Internet --> LB([Load Balancer]) LB --> RS1([RealServer1]) LB --> RS2([RealServer2]) LB --> RS3([RealServer3]) %% 节点样式 style Client fill:#C0C0C0,stroke:#333,stroke-width:2px,rx:10px,ry:10px style Internet fill:#F5F5F5,stroke:#333,stroke-width:2px,stroke-dasharray: 5 5,rx:10px,ry:10px style LB fill:#FFD700,stroke:#333,stroke-width:2px,rx:10px,ry:10px style RS1 fill:#ADD8E6,stroke:#333,stroke-width:2px,rx:10px,ry:10px style RS2 fill:#ADD8E6,stroke:#333,stroke-width:2px,rx:10px,ry:10px style RS3 fill:#ADD8E6,stroke:#333,stroke-width:2px,rx:10px,ry:10px %% 箭头样式 linkStyle default stroke:#333,stroke-width:2px
将负载均衡器以 SDK 库的形式嵌入到客户端
graph LR subgraph Client Service[Service]:::service ClientLibrary[Client library]:::client end ClientLibrary --> RS1[RealServer1] ClientLibrary --> RS2[RealServer2] ClientLibrary --> RS3[RealServer3] %% 样式定义 classDef service fill:#FFFFFF,stroke:#333,stroke-width:2px,rx:5px,ry:5px classDef client fill:#C0C0C0,stroke:#333,stroke-width:2px,rx:5px,ry:5px style Client fill:none,stroke:#333,stroke-width:2px,rx:10px,ry:10px style RS1 fill:#ADD8E6,stroke:#333,stroke-width:2px,rx:10px,ry:10px style RS2 fill:#ADD8E6,stroke:#333,stroke-width:2px,rx:10px,ry:10px style RS3 fill:#ADD8E6,stroke:#333,stroke-width:2px,rx:10px,ry:10px %% 箭头样式 linkStyle default stroke:#333,stroke-width:2px
Sidecar 拓扑(客户端内嵌库拓扑的一个变种)
graph LR subgraph Container Service[Service] Sidecar[Sidecar Proxy]:::sidecar Service <--> Sidecar end Sidecar --> RS1[RealServer1] Sidecar --> RS2[RealServer2] Sidecar --> RS3[RealServer3] %% 节点样式 style Service fill:#FFFFFF,stroke:#333,stroke-width:2px,rx:5px,ry:5px style Sidecar fill:#C0C0C0,stroke:#333,stroke-width:2px,rx:5px,ry:5px style Container fill:none,stroke:#333,stroke-width:2px,rx:15px,ry:15px style RS1 fill:#ADD8E6,stroke:#333,stroke-width:2px,rx:10px,ry:10px style RS2 fill:#ADD8E6,stroke:#333,stroke-width:2px,rx:10px,ry:10px style RS3 fill:#ADD8E6,stroke:#333,stroke-width:2px,rx:10px,ry:10px %% 箭头样式 linkStyle default stroke:#333,stroke-width:2px
四层负载均衡
所谓的“四层负载均衡”(Layer 4 Load Balancer,简称 L4),并非仅指其在 OSI 模型的第四层(传输层)工作,而是指四层负载均衡所有工作模式的共同特点是,维持传输层连接(如 TCP、UDP)特性,沿用惯例,来自客户端的请求,无论是在网络层处理,还是在传输层的处理,统称为四层负载均衡处理
四层负载均衡器的不同工作模式涉及多个网络层:
- 在第二层(数据链路层)通过改写 MAC 地址实现请求转发
- 在第三层(网络层)改写 IP 地址完成请求路由
- 在第四层(传输层)修改 UDP 或 TCP 的端口和连接地址,并通过 NAT 方式实现请求转发
网络地址转换(Network Address Transform)是指将专用网络地址转换为网络中的公用地址,实现将
公网{IP:PORT}
映射成内网{IP:PORT}
- 由于专用网本地 IP 地址是可重用的,所以 NAT 大大节省了 IP 地址的消耗
- 同时,它隐藏了内部网络结构,从而降低了内部网络受到攻击的风险
四层负载均衡工作模式
- DR 模式
- Tunnel 模式
- NAT 模式
七层负载均衡
早期的七层负载均衡,仅实现应用层请求的代理,如把非业务功能,如 路由请求、鉴权、监控、缓存、限流和日志记录 等集中在一层处理,并在一个控制台统一管理,此即为集群入口的高阶模式 —— 网关(API Gateway)
网关 | 特点 |
---|---|
OpenResty | 基于 Nginx 和 LuaJIT 的高性能 Web 平台。通过 LuaJIT 引擎,用户可以在 Nginx 中编写 Lua 脚本,在请求处理的不同阶段(如请求、响应、重写、日志等)动态地执行自定义逻辑 |
Kong | 社区活跃、成熟度高、Postgres 存储、二次开发成本高 |
Spring Cloud Gateway | Spring Cloud Gateway 是 Spring 生态系统中的一个网关解决方案,适用于 SpringBoot 和 SpringCloud 构建的微服务系统 |
Traefik | 与 Docker、Kubernetes 等容器编排系统紧密结合 |
Envoy | Envoy 是 Lyft 开发的一款面向服务网格的高性能网络代理,支持高级的路由控制、负载均衡策略、服务发现和健康检查等,Envoy 与 Istio 等服务网格解决方案紧密结合,通常作为它们的数据平面代理使用 |
协议支持:网关对应用层协议了解的越多,就可以处理更复杂的事情,如系统可观测、高级负载均衡和路由等
- 云原生网关的典型代表 Envoy 支持如下七层协议的解析和路由:HTTP/1、HTTP/2、gRPC、Redis、MongoDB、DynamoDB 等等
动态配置: 服务管理的动态配置包括路由、上游服务(Upstream)、SSL 证书、消费者等,数据的替换和更新不会产生任何中断,从而将线上流量的影响降低到最低
SSL 卸载:通过 SSL 卸载将业务的 SSL 协商以及加解密处理从原来的服务器迁移到负载均衡设备上来,从而减轻服务器的 SSL 协商以及加解密的工作负担
流量治理:微服务架构中,服务间的通信治理是必不可少的环节,如超时、重试、限速、熔断、流量镜像、缓存等等
- 现代网关系统在服务以及流量的管理上可对多业务进行收敛统一处理,降低多套网关的运维成本
可观测性:传统的监控方式已经无法适应云原生的场景,服务治理的可观测性输出是网关系统提供的最重要的特性
- 系统度量、分布式跟踪以及自定义日志等功能现在几乎是七层负载均衡解决方案的标配
- 需丰富的可观测数据并不是没有代价的,负载均衡器需要做一些额外的工作才能产生这些数据
- 但是这些数据带来的收益要远远大于为产生它们而增加的那点性能损失
可扩展性:例如典型网关 OpenResty,通过编写可插拔的插件能够轻松地对网关系统实现各种流量处理以及各类自定义功能
- 譬如流量限速、日志记录、安全检测、故障注入等等
高可用以及无状态设计:现代网关系统不仅提供数据面实现,还提供控制面实现,二者目标都在朝向无状态设计,可以轻松地实现水平扩展,网关架构整体上也默认高可用,不存在单点故障