最近我们的门户网页接连出了两次故障,由于这个系统已经运行了很多年,维护人员也换了好几波,趁这个机会把这一块梳理了一遍。虽然没有用到什么新的知识,还是把这一块知识系统整理了一下。
1 直接通过ip地址进行调用
当我们费尽心思开发了一个服务,通过网络接口向外提供相应的功能,总是希望接口能被人调用。在互联网络中,服务是通过ip地址被寻址的,这样调用关系是这样的:
2 通过DNS域名进行调用
但是ip地址实在是不便于记忆,于是我们可以为自己的服务取一个有意义的名字,例如niubility.com
,并注册到DNS
(Domain Name System
)中,调用者先到DNS中查询服务的ip地址,然后再进行调用,这时是这样调用的:
3 引入四层负载均衡中间层
这时还只有一个服务实例提供服务,如果这个实例故障或实例所在的机器故障了,就无法提供服务了,我们很自然的想到同时部署多个实例,这样即使部分实例故障了,其它健康的实例可以继续提供服务。这时的调用关系是这样的:
但是DNS只是记录域名到ip地址的映射关系,它并没有探测和感知实例的健康状态,调用者查询域名时,得到的可能是故障的服务实例的ip地址。而且DNS的TTL时间往往比较长(几分钟到几十小时),网络中在你不知道的地方处处存在对DNS的缓存,导致对DNS的更新生效时间比较长,这时我们可以引入一个四层负载均衡层,DNS域名不再直接维护服务实例的映射,只需要维护四层负载均衡提供的vip地址的映射,相对于服务实例来说,vip可以长期保持稳定不变。这时的调用关系是这样的:
四层负载均衡设备一般提供以下几项核心功能:
-
负载均衡
四层负载均衡设备根据一定的调度算法将流量分发给后端的服务实例,常用的调度算法包括:随机(Random)、轮询(Round Robin,RR)、加权轮询(Weighted Round Robin,WRR)、自适应加权轮询(Adaptive Weighted Round Robin,AWRR)、最小连接数(Least Connections,LC)、源地址哈希、一致性哈希、五元组哈希、MCID算法等。四层负载均衡设备在调度时,往往是基于流(五元组确定一条流)的,对于tcp来说,syn报文转发给了哪个实例,这条流的后续报文都会转发给该实例。
-
健康检测并自动摘除非健康实例
四层负载均衡设备可以主动对后端服务实例进行健康探测,当发现服务实例异常时,则将实例从活跃实例列表中删除,后续不会再将流量转发给该实例了。当发现服务实例恢复正常时,则又将实例添加到活跃实例列表中,继续转发流量。
常用的健康探测方法:
-
tcp
基于tcp的三次握手机制进行健康探测。主动探测者向被探测实例发送tcp syn报文,如果在超时时间内接收到被探测实例返回的syn + ack报文,则认为实例是健康的。如果在超时时间内没有接收到syn + ack报文,则此次探测失败,如果连续指定次数(如3次)探测都失败,则认为该实例当前处于非健康状态。
-
udp
基于ICMP ping的方式进行健康探测。探测者ping实例,如果能ping通,表示实例当前是健康的。
-
-
连接超时重置
对于tcp连接,如果负载均衡设备检测到某条tcp连接在超时时间内都没有报文交互,会主动向后端实例发送RST报文,关闭连接,从而释放连接资源
-
增加/删除、屏蔽/去屏蔽后端实例
在一些情况下,服务实例局部功能异常,但是健康检测是正常的,这时负载均衡设备并不会自动将服务实例摘除,需要主动向负载均衡设备屏蔽或删除该实例。或者服务实例进行无损升级时,需要先将实例从负载均衡设备中屏蔽或删除,使不再有流量调度给该实例,待该实例不再有流量时再进行升级。在屏蔽实例时,需要注意考虑长连接的情况。因为对于已经建立的长连接,即使实例被屏蔽了,负载均衡设备往往不会主动断开它,需要等长连接关闭后才确保不会再有流量了
由于负载均衡设备是流量转发的枢纽,它本身的可靠性成了整个系统的关键。所以负载均衡层往往采用集群的方式,单个节点失效,只会影响当前节点正在处理的流量,这些流量重新发起连接时,又会由其它正常的节点进行流量转发。而这种可靠性的保证是通过网络层的路由协议实现的,节点故障时,网络层路由收敛往往非常迅速(排除故障节点)。下图是一个简化的网络连接示意图,四层负载均衡集群的所有设备通过ospf协议向网络发布相同的vip,路由器根据五元组将流量转发给某个具体的负载均衡节点。当某个节点故障时,网络重新计算路由拓扑,不再将流量转发给故障节点。
4 引入应用层负载均衡中间层
四层负载均衡设备工作在网络层,通过NAT(Network Address Translation)技术修改报文的源目的地址和端口号实现对流量的转发。但是很多时候,我们希望根据http应用层的url、cookie或指定header值进行转发,根据引入四层负载均衡中间层的思路,我们又引入一个中间层:应用层(七层)负载均衡。这时调用关系变成了这样的:
这里还有一个疑问:七层负载均衡和四层负载均衡的核心功能都是转发流量,为什么不把它们做在一个设备里,而要分开在独立的设备里呢?主要原因还是网络功能分层和解耦,他们工作在不同的网络协议层,具有自己独立的专业知识和边界,解耦后便于各自聚焦于自己需要解决的核心问题,也便于整个系统的维护和扩展。事实上,在大型系统中,这两个系统往往由独立的两个团队负责的,七层负载均衡集群的规模也远大于四层负载均衡集群的规模。
既然负载均衡层是所有服务流量的统一入口,那么很自然的可以将一些系统公共功能、通用功能在负载均衡层中实现。七层负载均衡的核心功能可以包括:
-
流量接入、负载均衡、健康探测与排除故障节点、屏蔽与去屏蔽节点
这些功能与四层负载均衡功能相同,也是应用层负载均衡的核心基础功能。它根据一定的负载均衡算法将流量调度给后端服务实例。支持的调度算法有Random、RR、WRR等,调度的依据可以包括应用层的信息,如url、cookie、指定header值等
-
统一权限认证和授权
对进入的流量统一进行认证,往往支持basic auth、JWT、TLS、证书管理、OAuth、RBAC等跟权限与认证管理相关的功能
-
系统可靠性与流量管理
在这一层非常适合应用一些常用的系统可靠性保障手段和流量管理方法,如熔断降级、限流、重试(非幂等性写且非连接错误一定不要在这一层重试)、超时、流量隔离、流量整形、无损升级、金丝雀或蓝绿发布等
5 服务注册中心与东西向流量负载均衡(接入管理)
上面主要考虑系统外部流量接入的方式,但是现在的系统越来越复杂庞大,一个系统往往拆分成众多的微服务。系统内这些微服务之间也需要相互调用,而且系统内部相互调用的流量(东西向流量)往往比系统外部调用的流量(南北向流量)还要大。与上面的分析过程相似,微服务间可以直接通过ip地址调用,但是有诸多不便,所以可以借鉴引入DNS机制的思路,可以通过服务名称进行相互调用,这就是服务注册中心。与古老的DNS系统不同的是服务注册中心支持服务动态注册和解注册,同时支持对服务实例进行健康探测(主动探测或被动探测)。同时,东西向流量如果也有进行集中管理的需求,典型的场景有微服务需要支持金丝雀发布,也可以为东西向流量引入流量网关组件。这样服务接入方案如下所示:
6 跨az、region高可用
上面的方案中,每一个功能单元都是一个集群,消除了单点故障,但是这些集群往往分布在同一个机房(az)/区域(region)中,如果出现集群级故障、机房级故障甚至区域级故障,服务还是不可用。很自然的,我们又考虑将这些服务同时部署到多个机房、多个区域中。同时有几个问题需要特殊考虑: 1)不同的四层负载均衡集群往往发布不同的vip(虽然也有anycast技术,支持在不同的region同时发布相同的vip),那么域名下会有多个vip。上面已经分析过,域名下配置多个ip时会有一些问题。但是,既然注册中心可以探测服务实例的健康状态,那么DNS也可以支持相同的功能,这时DNS就升级为智能DNS(Smart DNS,SDNS)。SDNS一般支持以下核心功能:
-
根据亲和性解析
根据用户所在的地址位置、所属的运营商等信息为用户解析最有的ip地址。比如用户在华北,则返回华北服务集群的地址;在华东,则返回华东服务集群的地址;电信用户,则返回电信的地址
-
服务实例健康探测
SDNS可以根据服务实例的服务质量(时延、丢包情况)和服务实例是否存活情况,返回最有的ip地址
2)类似的,注册中心也支持跨区域注册,并且在调用时,优先调用本区域的服务实例;在本区域实例不可用时,则跨区域调用(考虑降级调用)。
这样得到一个"形式上良好"的服务接入方案如下: