许式伟的架构课

服务端的发展史

  • 1946年 第一台电子计算机问世
  • 1954年 第一门高级语言 Fortran发布
  • 1971年 电子邮件诞生
  • 1974年 Internet诞生
  • 1989年 万维网(WWW)诞生
  • 1993年 第一个Web服务器 NCSA Httpd诞生 它也是 Apache的前身
  • 1998年 CDN 诞生
  • 2006年 Amazon发布 弹性计算云 EC2
  • 2013年 Docker诞生
  • 2014年 K8S诞生

桌面端的迭代是交互的迭代 是人机交互的革命

服务器则转向了 数据中心操作系统(DCOS) 之路

服务器程序的需求

规模

桌面程序是为单个用户服务的, 它的关注点主要在于用户交互体验的升级

服务器程序是所有用户共享的, 为所有用户服务的. 一台物理机的资源始终是有限的, 能够服务的用户数必然存在上限, 一个服务端程序在用户达到一定程度后, 需要分布式话, 泡着多台机器上以服务用户

连续服务时长

用户在单个桌面程序的连续使用时间通常都不会太长

但是服务端程序需要 7*24小时不间断服务, 当用户达到一定基数后, 每一秒都会有用户使用, 不存在关闭这样的概念

质量需求

一个桌面程序实例运行崩溃, 它只影响一个用户

一个服务器程序实例崩溃, 可能会影响到几十上百万的用户, 这是不可接受的, 这意味着即使服务器的实例崩溃了, 它的工作也必须能立即转交给其他实例重新做, 并且要做到用户无感知

服务端的体系架构

服务器开发的宏观视角 - 图1

从宏观视角来看, 一个服务端程序首先应该是一个多实例的分布式程序. 其宏观体系架构如下

服务器开发的宏观视角 - 图2

流量调度

什么是流量调度, 解释前 我们需要了解这几个概念

  • 连接数
  • IOPS
  • 流量, 入向流量和出向流量

一个服务端程序的服务请求,通常是一个请求包(Request) 和一个应答包(Response)构成. 这样一问一答就是一次完整的服务.

连接数, 有时候也叫并发数, 指的是同时在服务中的请求数. 也就是那些已经发送请求(Request), 但是还没有收完应答(Response)的请求数量

IOPS, 指的是平均每秒完成的请求数量. 它可以用来判断服务端程序的做事效率

流量分为 入向流量出向流量.

  • 入向流量估算 平均每秒收到的请求包数量 * 请求包平均大小
  • 出向流量估算 平均每秒返回的应答包数量 * 应答包平均大小

不考虑存在无效的请求包,也就是存在有问无答的情况(但实际生产环境下肯定是有的)的话,那么平均每秒收到的请求包(Request)数量、平均每秒返回的应答包(Response)数量就是 IOPS。故此:

  • 入向流量 ≈ IOPS * 请求包平均大小
  • 出向流量 ≈ IOPS * 应答包平均大小

所谓的流量调度, 就是把海量客户并发的请求包按特定策略分派到不同服务端程序实例的过程

DNS流量调度与负载均衡

DNS是一种最基础的方式

服务器开发的宏观视角 - 图3

一个域名通过DNS解析到多个IP, 每个IP对应不同服务端程序实例, 这样就完成了流量调度. 这里我们没有用到常规意义的负载均衡, 但是的确完成了流量调度

升级不便

要想升级IP1对应的服务端实例, 必须先把IP1从DNS解析中取出, 等IP1这个实例没有流量的时候才能升级, 然后把IP1加回DNS解析中

好像问题不大, 但是 DNS是有层层缓冲的 客户端也有自己的缓冲就算你写明了TTL是15min , 实际上过了一天依然会有用户访问IP1

如果你要升级很多个实例, 或者是紧急修补, 这个时间是不能接受的

流量调度不均衡

DNS服务器确实有能力做一定的流量均衡. 比如第一次域名解析返回IP1优先, 第二次域名解析让IP2优先, 但是域名解析的负载均衡不代表流量均衡

  1. 用户端DNS会有缓冲
  2. DNS本身也有层层缓冲

所以样的把控粒度是非常粗糙的, 实际结果并不可控

网络层均衡负载

LVS

这一层是在网络层(IP层) 做均衡负载

章文嵩博士发起的负载均衡软件 LVS(Linux Virtual Server) 就工作在这一层, 以LVS为代表概括一下LVS的工作原理

LVS支持三种调度模式

  • VS/NAT: 通过网络地址转换(NAT) 技术做调度. 请求和响应都会经过调度器中转, 性能最差
  • VS/TUN: 把请求报文通过 IP 隧道转发至真实服务器, 而真实服务器将响应直接返回给客户, 所以调度器只处理请求报文 性能优于 VS/NAT
  • VS/DR : 通过改写请求报文的MAC地址, 将请求发送到真实服务器, 真实服务器将响应直接返回给客户. 这种做法相比 VS/TUN 少了 IP隧道的开销, 性能最好

重点介绍下 VS/DR技术

服务器开发的宏观视角 - 图4

  • 假设客户端的 IP 和 MAC 为 CIP , CMAC
  1. 客户端发起请求, 其IP报文中, 源IP为用户的CIP, 目标 IP 是 VIP; 源MAC地址为 CMAC, 目标地址为 RMAC, 将请求转发到真实的业务服务实例 RS (Real Server)
  2. 请求包到达LVS调度器(Director Server) 我们保持源IP和目标IP不变, 仅仅修改目标MAC地址为RMAC, 将请求转发到真实的业务服务器RS(Real Server)
  3. RS收到数据包并处理, 直接响应发送给客户端

这里的关键技巧在于, 将VIP绑定在多台机器上, 所以我们把它叫做虚拟IP(Virtual IP), 它既绑定在LVS调度器(Director Server). 在真实的业务服务器实例RS(Real Server)上, 我们把VIP绑定在 lo口上, 并对ARP请求做了抑制, 这样就避免了IP冲突

:::tips ARP协议(Address Resolution Protocol) 根据IP地址获取一个物理地址

:::

LVS在底层做均衡负载, 相比于其他负载均衡技术来说, 其特点是通用性强, 性能优势高

不足之处

加入某个服务器实例RS挂掉了, 但是LVS调度器还没有感知到, 那么在这个周期内转发到该实例的请求都会失败, 这种失败只能靠客户端重试解决 (因为LVS仅仅只是修改了MAC地址, 应答不经过它了)

应用层负载均衡

如何避免请求失败的情况出现?

  • 服务端重试

如何做服务端重试?

  • 应用层负载均衡, 有时候我们也叫应用网关

HTTP协议是应用测最为广泛的应用层协议, 当前的应用网关, 绝大多数都是HTTP应用网关

Nginx和Apache都是大家耳熟能详的HTTP应用网关, 因为知道应用层协议的细节, 所以HTTP应用网关的能力通常非常强大

HTTP网关收到一个HTTP请求后, 根据一定的调度算法把请求转发给后端真实的业务服务器实例RS(Real Server), 收到RS的应答后, 再把它转发给客户端

整个过程的逻辑非常简单, 而且重试也非常好做

在发现某个RS实例挂了以后 HTTP网关可以将同一个HTTP请求 重新发个其他RS实例

当然为了支持重试, HTTP请求需要被保存起来, 不保存请求也是有可能的, 但是只能支持业务示例完全关掉 HTTP 请求一个字节都没发过去的场景, 断点或者异常崩溃等情况,就无法重试了

大部分HTTP请求不大, 在内存中存储即可, 但是文件上传型的请求可能就要依赖其他手段来存储了

优雅升级

有了负载均衡后, 不仅实现了流量的均衡调度, 业务服务器的升级也会方便很多

对于LVS调度器, 升级的核心步骤

  • 升级系统通知LVS下线要升级的业务服务器
  • LVS调度器将该实例从RS集合中去除, 这样就没有新流量到它了
  • 升级系统通知要升级的RS实例退出
  • 要升级的RS实例处理完所有处理中的请求然后主动退出
  • 升级系统更新RS实例到新版版本, 然后重启
  • 升级系统将RS实例重新加回RS集合参与调度

对于前端是HTTP应用网关这种负载均衡场景

  • 升级系统通知升级的业务服务器实例退出
  • 要升级的RS实例进入退出状态, 这时拒绝新请求(返回一个特殊的 Status Code); 处理完所有请求 RS实例主动退出
  • 升级系统更新RS实例到新版本, 重启

HTTP的应用网关因为支持了重试, 所以升级的步骤变得简单很多

数据库与其他形式存储(DB/Storage)