网络架构梳理

不懂很多网络概念,在一些开会、对接场合出了很多洋相,趁机好好补补。

概念

网关

网关提供了一个系统间通信的代理,网关和业务服务有本质区别,标准的网关应该是不包含业务逻辑的,一些错误的实现会将业务相关代码硬编码到网关内,比如针对不同终端的请求执行不同的处理逻辑。
接入层的概念和网关容易混淆,接入层是组网中常提到的概念,表示网络中直接面向用户连接或访问网络的部分,如果放到互联网应用架构中,可以包括 CDN、DNS、网关等一系列暴露给外网的组件。
网关的设计需要考虑下游服务的特征,就算你设计了一个能承载百万并发的业务,遇到一个并发承载能力不到 2 的下游服务,那也是白搭。

前置机

用于跨系统的金融交易的中间设备,主要解决系统间通信时协议的适配问题。
其实运行服务器的宿主机也算是一种前置机,一些传统企业会把 Tomcat 直接跑在一台 Windows 电脑上,这台电脑其实就是前置机。

运营商

指提供网络服务的供应商,如中国联通、中国电信、中国移动等。

专线

网络专线就是为某个机构拉一条独立的网线,也就是一个独立的局域网,例如军事,银行等。
专线的优点是安全性好,QoS 可以得到保证。缺点是租用价格也相对比较高,而且管理也需要专业人员。

跳板机(Jump Server / 堡垒机)

一种网络设备,主要被用作跳板来批量操作远程设备,好处是可以统一管理账户、单点登录、操作行为控制、审计等。

QPS

每秒查询数,或者说每秒查询请求的吞吐量,是处理服务器单位时间内处理流量的一个指标,衡量网站性能的标准之一。

并发线程数

衡量服务器同时能处理的请求数量,QPS 可以用于度量平均负载能力,而并发线程数可以用于度量瞬时的负载能力。

健康检查

Nginx 作为网关有时并不只有反向代理、负载均衡的作用,比如 tegine 就提供了它所代理服务的健康检查功能。

集线器(hub)

hub 工作在 OSI 的物理层

网卡(network card)

网卡工作在 OSI 的物理层

交换机(switch)

switch 工作在 OSI 的数据链路层

路由器(router)

router 工作在 OSI 的网络层

一次请求的执行过程

DNS

将域名解析为 IP,因为 IP 不好记忆
浏览器会在一段时间内记忆解析结果,这样就省去了这一步查询的代价,当然也可以手动在 HOSTS 文件中指定解析规则。

CDN

如果是一些比较大又不怎么改变的静态文件,一个显而易见的优化方案是缓存,CDN 究其根本也是一种缓存。

服务网关

如果有多个服务实例可提供相同的服务,那么势必需要在 DNS 的域名解析中将域名与多个地址进行绑定,这样的方案有如下问题:

  • 如何检查这些实例的健康情况,同时在发现出现问题的时候增删服务实例地址?即所谓的服务高可用问题。
  • 把这些服务实例地址都暴露到外网,会不会涉及到安全问题?即使可以解决安全问题,那么也需要每台机器都做安全策略。
  • 由于 DNS 协议的特点,增删服务实例并不是实时的,有时候会影响到业务。

为了解决这些问题,就引入了反向代理网关这一组件。它提供如下的功能:

  • 负载均衡功能:根据某些算法将请求分派到服务实例上。
  • 提供管理功能,可以给运维管理员增减服务实例。
  • 由于它决定了服务请求流量的走向,因此还可以做更多的其他功能:灰度引流、安全防攻击(如访问黑白名单、卸载 SSL 证书)等。

LVS 工作在四层,即请求来到 LVS 这里时是根据四层协议来决定请求最终走到哪个服务实例;而 Nginx 工作在七层,主要用于 HTTP 协议,即根据 HTTP 协议本身来决定请求的走向。需要说明的是,Nginx 也可以工作在四层,但是这么用的地方不是很多,可以参考 nginx 的 stream 模块。

LVS 有好几种工作模式,以下表述仅针对 Full NAT 模式

LVS 有如下的组成部分:

  • Direct Server(以下简称 DS):前端暴露给客户端进行负载均衡的服务器。
  • Virtual Ip 地址(以下简称 VIP):DS 暴露出去的 IP 地址,做为客户端请求的地址。
  • Direct Ip 地址(以下简称 DIP):DS 用于与 Real Server 交互的 IP 地址。
  • Real Server(以下简称 RS):后端真正进行工作的服务器,可以横向扩展。
  • Real IP 地址(以下简称 RIP):RS 的地址。
  • Client IP 地址(以下简称 CIP):Client 的地址。

LVS工作流程
客户端进行请求时,流程如下:

  1. 使用 VIP 地址访问 DS,此时的地址二元组为<src:CIP,dst:VIP>
  2. DS 根据自己的负载均衡算法,选择一个 RS 将请求转发过去,在转发过去的时候,修改请求的源 IP 地址为 DIP 地址,让 RS 看上去认为是 DS 在访问它,此时的地址二元组为<src:DIP,dst:RIP A>
  3. RS 处理并且应答该请求,这个回报的源地址为 RS 的 RIP 地址,目的地址为 DIP 地址,此时的地址二元组为<src:RIP A,dst:DIP>
  4. DS 在收到该应答包之后,将报文应答客户端,此时修改应答报文的源地址为 VIP 地址,目的地址为 CIP 地址,此时的地址二元组为<src:VIP,dst:CIP>

虚拟 IP(VIP)

LVS 的基础技术之一就是虚拟 IP(VIP),虚拟 IP 即为一个网卡设置多个 ip,相当于别名。

有什么作用

  1. 布网需要
  2. 简单的负载均衡
    实现高可用性 HA(High Availability)的最有效方法就是实时检测和自动切换:
    • 心跳(实时检测),采用定时发送一个数据包,如果机器多长时间没响应,就认为是发生故障,自动切换到热备的机器上去。
    • 虚拟 IP(自动切换),虚拟 IP 就是一个未分配给真实主机的 IP,也就是说对外提供数据库服务器的主机除了有一个真实 IP 外还有一个虚 IP,使用这两个 IP 中的 任意一个都可以连接到这台主机,所有项目中数据库链接一项配置的都是这个虚 IP,当服务器发生故障无法对外提供服务时,动态将这个虚 IP 切换到备用主机。
  3. 多 ip 访问测试
  4. 特定软件对多 ip 的需要

相关命令

1
2
3
4
5
6
7
8
9
# 设置静态ip
ifconfig eth0 192.168.0.105 netmask 255.255.255.0 up
# 设置虚拟ip
ifconfig eth0:1 192.168.0.107 netmask 255.255.0.0
ifconfig eth0:1 down
# ping一下目标ip
ping 192.168.0.107
# 查看arp缓存表
arp -a

在设置 ip 别名时,如果增加的是和局域网同一网段的 ip(如 192.168.6.100),那么除了本机外局域网内其他机器都可以 ping 通这个 ip。如果增加的是奇形怪状的 ip,那么就只有本机可以 ping 通而已,后者主要用于本机测试需要。
重启后之前设置的 vip 会失效,如果需要长时间使用 ip 别名,最好将别名信息保存起来,一般方法有两个:1.将设置 vip 的命令保存到/etc/rc.local(作用相当于开机自动执行命令);2.手动在/etc/sysconfig/network-scripts 下编写 ip 别名的网卡配置文件,以下是其中部分需要特别注意的,其他部分直接从原来网卡的配置中粘过来即可:

1
2
3
4
5
6
7
8
9
# 3Com Corporation 3c905B 100BaseTX [Cyclone]    //硬件型号,忽略不计
DEVICE=eth0:0 //虚拟网络接口,随意
ONBOOT=yes //系统启动时激活
BOOTPROTO=static //使用静态ip地址
IPADDR=192.168.6.100 //该虚拟网络接口的ip别名,随意
NETMASK=255.255.255.0 //子网掩码,对应ip别名
GATEWAY=192.168.6.1 //网关,对应ip别名
HWADDR=00:10:5A:5E:B1:E4 //网卡MAC地址,无需更改
USERCTL=no //是否给予非root用户设备管理权限

实现原理

其实现原理主要是靠 TCP/IP 的 ARP 协议
因为 ip 地址只是一个逻辑地址,在以太网中 MAC 地址才是真正用来进行数据传输的物理地址,每台主机中都有一个 ARP 高速缓存,存储同一个网络内的 IP 地址与 MAC 地址的对应关系,以太网中的主机发送数据时会先从这个缓存中查询目标 IP 对应的 MAC 地址,会向这个 MAC 地址发送数据。操作系统会自动维护这个缓存。这就是整个实现的关键。
下边就是我电脑上的 arp 缓存的内容。

1
2
3
(192.168.1.219) at 00:21:5A:DB:68:E8 [ether] on bond0
(192.168.1.217) at 00:21:5A:DB:68:E8 [ether] on bond0
(192.168.1.218) at 00:21:5A:DB:7F:C2 [ether] on bond0

192.168.1.217、192.168.1.218 是两台真实的电脑,
192.168.1.217 为对外提供数据库服务的主机。
192.168.1.218 为热备的机器。
192.168.1.219 为虚 IP。
其中,219、217 的 MAC 地址是相同的。
再看看 217 宕机后的 arp 缓存

1
2
3
(192.168.1.219) at 00:21:5A:DB:7F:C2 [ether] on bond0
(192.168.1.217) at 00:21:5A:DB:68:E8 [ether] on bond0
(192.168.1.218) at 00:21:5A:DB:7F:C2 [ether] on bond0

这就是奥妙所在。当 218 发现 217 宕机后会向网络发送一个 ARP 数据包,告诉所有主机 192.168.1.219 这个 IP 对应的 MAC 地址是 00:21:5A:DB:7F:C2,这样所有发送到 219 的数据包都会发送到 mac 地址为 00:21:5A:DB:7F:C2 的机器,也就是 218 的机器。

RPC

面向公网的服务,一般都是以域名的形式提供给外部调用者,然而对于服务内部之间的互相调用,域名形式还不够,其原因在于:

  • DNS 服务发现的粒度太粗,只能到 IP 地址级别,而服务的端口还需要用户自己维护。
  • 对于服务的健康状况的检查,DNS 的检查还不够,需要运维的参与。
  • DNS 对于服务状态的收集很欠缺,而服务状态最终应该是反过来影响服务被调用情况的。
  • DNS 的变更需要人工的参与,不够智能以及自动化。

综上,内网间的服务调用,通常而言会自己实现一套“服务发现”类的系统,其包括以下几个组件:

  • 服务发现系统
    用于提供服务的寻址、注册能力,以及对服务状态进行统计汇总,根据服务情况更改服务的调用情况。比如,某个服务实例的响应慢了,此时分配给该实例的流量响应的就会少一些。而由于这个系统能提供服务的寻址能力,所以一些寻址策略就可以在这里做,比如灰度某些特定的流量只能到某些特定的实例上,比如可以配置每个实例的流量权重等。
  • 一套与该服务系统搭配使用的 RPC 库
    其提供以下功能:
    • 服务提供方:使用 RPC 库注册自己的服务到服务发现系统,另外上报自己的服务情况。
    • 服务调用方:使用 RPC 库进行服务寻址,实时从服务发现系统那边获取最新的服务调度策略。
    • 提供协议的序列化、反序列化功能,负载均衡的调用策略、熔断限流等安全访问策略,这部分对于服务的提供方以及调用方都适用。

ServiceMesh

ServiceMesh 并没有见公司内部有实践过,感觉现阶段应该还算是比较冷门的技术。

前面的服务发现系统中,需要一个与之配套的 RPC 库,然而这又会有如下的问题:

  • 如果需要支持多语言,该怎么做?每个语言实现一个对应的 RPC 库吗?
  • 库的升级很麻烦,比如 RPC 库本身出了安全漏洞,比如需要升级版本,一般推动业务方去做这个升级是很难的,尤其是系统做大了之后。
    可以看到,由于 RPC 库是嵌入到进程之中的组件,所以以上问题很麻烦,于是就想出了一个办法:将原先的一个进程拆分成两个进程,如下图所示。
    ServiceMesh工作流程
    在服务 mesh 化之前,服务调用方实例通过自己内部的 RPC 库来与服务提供方实例进行通信。
    在服务 mesh 化之后,会与服务调用方同机部署一个 local Proxy 也就是 ServiceMesh 的 proxy,此时服务调用的流量会先走到这个 proxy,再由它完成原先 RPC 库响应的工作。至于如何实现这个流量的劫持,答案是采用 iptables,将特定端口的流量转发到 proxy 上面即可。
    有了这一层的分拆,将业务服务与负责 RPC 库作用的 Proxy 分开来,上面的两个痛点问题就变成了对每台物理机上面的 mesh proxy 的升级维护问题,多语言也不是问题了,因为都是通过网络调用完成的 RPC 通信,而不是进程内使用 RPC 库。
    然而这个方案并不是什么问题都没有的,最大的问题在于,多了这一层的调用之后,势必有影响原来的响应时间。

监控

如果从大的系统性能角度,需要看响应时间、并发连接数、吞吐量、资源利用率、事务成功率这几个重要的指标。这几个指标都是可以从中间件中观察到的。只不过这些指标我们往往不从中间件中监控。例如,响应时间、吞吐量往往从用户的端到端计算,或者从日志里面计算,资源利用率从操作系统层面监控。这里介绍的指标是中间件特有的监控指标。

协议

以下关于协议的一些名词基本讨论范围都限制在 TCP/IP 协议栈内。

  • tcp、http、https
    TCP/IP 协议栈中最常见的几个协议之一。

  • 端口

  • 丢包
    丢包指的是网络请求中的数据包(packet)无法到达目的地的情况,
    引起丢包的可能性包括通道阻塞(packet drop)、数据包损坏(corrupted packet),还有一种可能性是MTU定小了:http 请求响应丢包问题

  • 网络抖动
    分组延迟的变化程度。如果网络发生拥塞排队延迟将影响到端到端的延迟,并导致通过同一连接传输的分组延迟各不相同,而抖动就是用来描述这一延迟变化的程度。对于实时性要求较高的场景,抖动是一个比较重要的参数。

  • 长 ping
    指 ping 的时候发送特别大的数据包,比如:

    1
    ping -c 10 -s 65500 localhost

    如果数据包更大一点,超过包处理程序的默认尺寸,超出可用缓冲区大小,这很容易导致系统进入非稳定状态,是一种典型的缓存溢出(Buffer Overflow)攻击。

  • 跳 ping
    打游戏时网络不稳定的说法,游戏中 RTT(Round-Trip Time 往返时延)不稳定、时常跳到很高的值,表示跳 ping 了,此时 ping 一下远程服务器可以发现大量的超时和丢包现象。ping 不稳定可能是电脑到路由器、本地到游戏服务器这两段网络引起的,前者一般出现在无线连接的情况下,可以用网线代替无线来减小这一段的延迟,更多情况下是本地发到游戏服务器的包出现了延迟,可能是服务供应商、带宽问题(例如同一网段内用户太多了)、网络设备本身的问题等。

  • hang 住
    指程序停住不动了,这里不是指死锁,因为真实环境中死锁并不多见,更有可能是程序 hang 在了某个 IO 请求上。
    为了排查 hang 住的情况,如果是 Java 程序一般使用jstack,如果是 C/C++程序一般使用stracelsof命令:程序 HANG 住的问题的追踪

  • 5XX
    以 5 开头的 HTTP 状态码表示服务端出现问题,其中有几个常见的包括:
    500 - Internal Server Error - 内部错误,一般是代码有 Bug 报错了。
    501 - Not Implemented - 请求方法不对,比如应该传 GET 却传成了 ET(不过一般人应该不会犯这种低级错误)。
    502 - Bad Gateway - ,网关错误,比较常见,如果某段时间一直出现 502,那么很有可能是应用挂掉了;如果是偶尔出现 502,则有可能是 CPU 使用率高、QPS 增加,导致应用服务器不可响应或响应不过来,可以按如下流程排查:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    # 检查进程是否存在
    ps -ef | grep java
    # 检查端口是否开放
    sudo netstat -lntp | grep {PORT}
    # 应用程序健康检查(首先得确保我们的应用里有这个健康检查机制)
    curl -l 127.0.0.1:{PORT}/health
    # 检查接入层(比如对外提供服务的是Nginx,假设Nginx的日志是access.log)有没有进来
    tail -300f access.log | grep xxxx
    # 直接访问一次接入层
    curl -l 10.10.10.10:80/app
    # 检查接入层有没有报错(同样假设Nginx的错误日志是error.log)
    tail -300f error.log | grep xxxx
    # 检查Nginx超时时间配置,是不是可能因为配得过小了而导致触发超时报错,超时时间配置项为proxy_read_timeout
    # 调大超时时间可以一定程度上缓解,但是优化链路本身的请求耗时才能从根本上解决问题
    vim /opt/nginx/nginx.conf

    503 - Service Unavailable - 服务不可用,服务器或许就是正在维护或者暂停了,还有的时候是 CPU 占用的频率大导致的。
    504 - Gateway Time-out - 网关超时,一般是所连接的服务器无响应导致的。
    505 - HTTP Version not supported - 版本不支持,可以尝试调整浏览器支持的 HTTP 版本,一般只有在 IE 上才可能出现。

网络安全

  • 密码登录、扫码登录、验证码登录、授权登录、静默登录、二次登录
    前三种就是非常常见的登录功能。
    授权登录是一种短期登录方案,它需要一个三方平台提供登录页面,并在登录成功之后能够跳转回来,并提供能获取用户信息的登录标识,现在最通用的设计是OAuth
    静默登录虽然是用户无法感知的,但是原则上,无论任何登录都是需要用户信息,实际上只是后台“偷偷地”从三方那获取到了用户信息而已,静默登录出现在页面需要嵌入到其他应用中的场景下,比如某个 XX 公司的产品要通过小程序的形式嵌入到微信里。
    二次登录出现在操作需要更高的权限的时候,比如,虽然能够通过静默登录进入 XX 公司产品的小程序页面,但是某些操作需要更明确的用户标识,比如手机号、证件号等,就需要在执行这些操作的时候能提示用户进行登录、实名认证等。
  • 越权
    用户能够执行一些超过自己所获取到的权限的操作。
    比如,某学校的后台管理系统通过 URL 参数来区分登录身份,role=STUDENT 表示学生登录、role=TEACHER 表示教师登录,通过简单修改 role 参数我们就可以在学生登录的前提下执行教师权限才能执行的操作了。

参考

网络基础

  1. 6.829: Computer Networks

TCP/IP

  1. 概述
    涨姿势!请您收好这一份详细、清晰的计算机网络基础学习指南
  2. 抓包
    抓包工具 Charles 的使用心得

HA 集群

  1. HA(高可用) 技术全面介绍
  2. Nginx / Tegine
    ngx_http_upstream_check_module
  3. LVS
    Linux 服务器集群系统
  4. CDN
    程序员要搞明白 CDN,这篇应该够了
    CDN 学习笔记一(CDN 是什么?)

安全

  1. 登录
    Cookie 还是 Token,这是一个问题
  2. 攻击防御
    服务器遭受攻击后,这样排查处理不背锅!
  3. OWASP
    github - OWASP
    Category:OWASP Top Ten Project
    OWASP Top 10 - 2017
    2017 OWASP TOP 10 — OWASP-CHINA