😳

高可用架构(1):负载均衡

前言

    2024年对我来说,在技术视野上新的变化是从个人视角的开发转向到企业中有关高并发、高流量的架构设计。在美团工作的半年里,虽然大部分时间确实是在做最低级的功能测试,但在各模块联测、验证以及用例设计、缺陷定位的过程中,对体系结构或多或少能够窥见一二。下半年在中国结算做开发,尽管负责的模块在开发上并没有什么技术含量,但是由于公司里的技术人员本不算多,可以说每个模块的开发人员都要深入架构,负责每一层的配置。

    如果说之前的工作更多是在研究如何实现功能,那么今年更多是从全局和宏观角度下,了解我需要使用的工具以及当前企业中的最佳实践。然而架构中的每一个节点背后都是极其复杂的原理,可以学习和展开的内容非常多,因此本系列会持续更新,如果内容有浅显和疏漏的部分,还请读者海涵。

千万流量内的集中式架构

    用户访问量低时,部署结构比较简单。用户流量集中发往中央处理器,由中央服务器进行反向代理:中央服务器将任务下达节点服务器,节点服务器执行任务并返回结果。
    在中央处理器进行任务调度时,可以通过ip地址,将节点服务器部署在不同地区,以获取更快的响应速度。然而这种解决方案的节点服务器负载由用户决定,流量更大的省份,服务器常常面临更大的压力。此时可以引入负载均衡,设置算法由中央服务器主动分配流量。

多级负载均衡

软硬件负载均衡

    软件负载均衡解决方案是指在一台或多台服务器相应的操作系统上安装一个或多个附加软件来实现负载均衡。硬件负载均衡解决方案是直接在服务器和外部网络间安装负载均衡设备,这种设备通常是一个独立于系统的硬件,我们称之为负载均衡器(F5负载均衡不低于20万/台)。

多层负载均衡

    多层负载均衡中的“层”指的是OSI七层网络模型中的“层”。

  1. 二层负载均衡(VIP+MAC地址):数据链路层,外部请求流量经过虚拟IP地址,负载均衡收到流量请求后分配后端实际的MAC地址进行响应。
    这种方式负载方式虽然控制粒度比较粗,但是优点是负载均衡服务器的压力会比较小,负载均衡服务器只负责请求的进入,不负责请求的响应(响应是有后端业务服务器直接响应给客户端),吞吐量会比较高。
  2. 四层负载均衡(TCP/UDP):传输层,使用IP+PORT接收外部流量请求,转发到对应的机器上。
  3. 七层负载均衡(HTTP):应用层,使用虚拟的URL或IP地址接收外部流量请求,转发到对应的处理服务器。

    二层负载均衡通常可以由交换机来完成,它的另一个名字是链路聚合技术。因此现代的负载均衡通常指第四和第七层的负载均衡,是针对网络应用的负载均衡,是脱离交换机的。
    由于四层负载均衡是基于网络层和传输层工作的,因此并不能理解应用协议(如HTTP/FTP/MySQL等),只能通过IP和端口号设计负载均衡算法。而七层负载均衡还可以通过URL、浏览器类别、语言、Cookie信息等来设计算法。
    常见的四层均衡服务器有商业硬件负载均衡器F5、基于Linux Kernel实现的LVS。常见的七层负载均衡是Nginx。

多级挂载

    中央处理器会承受所有流量,因此负载均衡常常成为系统的瓶颈。由于四层负载均衡是基于网络层和传输层工作的,性能远高于工作在应用层工作的七层负载均衡,一种常见的部署方式是把不同类型的负载均衡器级联部署,将七层负载均衡器作为四层负载均衡器的下游服务器。

    四层负载均衡器可以提供多业务、多个IP地址的接入能力(将不同应用部署在不同IP上,暴露不同的端口),比如将公司内部不同的业务转发给不同的Nginx服务。七层负载均衡器可以把来自四层负载均衡的同一个虚拟IP的流量按照应用协议(域名/URL/MySQL等)做进一步精细化控制,转发到不同后端服务器集群上。
    如果有需要,还可以在四层负载均衡器上游挂载二层负载均衡器(交换机),按照MAC地址分发流量,最终由四层负载均衡器响应客户端,降低四层负载均衡的压力。
    负载均衡集群也拥有类似心跳检测的机制,称为“健康检查”。

  1. 当后端服务器实例被判定为异常后,负载均衡实例自动将新的请求转发给其他正常的后端服务器,而不会转发到异常的后端服务器。
  2. 当异常实例恢复正常后,负载均衡将其恢复至负载均衡服务中,重新转发请求给此实例。
  3. 若健康检查探测到所有后端服务都有异常时,请求将会被转发给所有后端服务器。

    七层负载均衡器可以利用四层负载均衡器的健康检查服务实现平滑的扩容和下线,四层负载均衡也可以利用哈希等算法实现集群化的服务,从而保证多级负载均衡系统的高可用和高扩展。

云平台和负载均衡——Ingress和NodePort

    在容器化部署的集群中(以K8S为例),部署在容器(pod/service)中的服务只能通过内网IP地址访问,要想对外暴露服务有以下三种方式。

  1. NodePort
  2. LoadBalance
  3. Ingress
1
2
3
4
5
注:在k8s集群中,pod是可以被调度的最小资源。
集群中的每个pod都会获得自己的、独一无二的集群范围IP地址。
可以将pod看作是独立的虚拟机。
service用于为一组提供服务的pod抽象一个稳定的网络访问地址,是Kubernetes实现微服务的核心概念。
service可以设置pod集群的虚拟IP,并作为pod集群的服务代理。

    当service被创建时,默认的类型是ClusterIP,这种类型只有Cluster内的Pod可以访问。开启Kubernetes Proxy后,外部可以通过指定url来访问service。

1
2
3
4
5
# 开启命令
kubectl proxy --port=8080

# url
http://localhost:8080/api/v1/proxy/namespaces/<NAMESPACE>/services/<SERVICE-NAME>:<PORT-NAME>/

    NodePort是service的另一种类型。NodePort会在所有的节点(虚拟机)上开放指定的端口,所有发送到这个端口的流量都会直接转发到服务。这意味着不同的service会占用不同的端口。
    在NodePort服务的背后,通常是由k8s自带的kube-proxy组件负责实现负载均衡的。如果你需要自定义NodePort负载均衡的行为,可能需要使用外部负载均衡器,或者编写自定义的kube-proxy行为。

    LoadBalancer是service的另一种类型。如果说NodePort是开放虚拟机的端口,那么LoadBalancer则会拥有自己的IP地址。但这也意味着你需要给每一个LoadBalancer提供一个IP地址,这可能是一笔费用。这种类型的负载均衡规则是各家云服务厂商自己实现的,不同厂商之间的规则和算法可能不同。

    Ingress不是一种服务,它和service是同级的概念。它在多个服务前面充当“智能路由”的角色,或者是集群的入口。与前述方法不同的是,它支持一个IP暴露多个service,支持通过识别路径、子域名等方式分发流量、支持证书等功能。

    总结一下,在k8s集群中,在外部网络暴露服务有这些方式:

  1. 通过NodePort形式暴露,前面接一个负载均衡器
  2. 通过LoadBalancer形式暴露,需要云厂商支持
  3. 通过Ingress控制器暴露

    结合多层负载均衡的知识,可以发现NodePort方法是一种四层负载均衡,它无法识别HTTP协议,更加适用于系统内部的TCP/UDP通信,而Ingress是一种七层负载均衡。
    当然,这几种方式可以按照结合使用,比如在Ingress前接入LVS、Nginx等负载均衡器,在Ingress之后接入类型为NodePort的service等。
    到目前为止,千万流量已经经过了交换机的虚拟IP分发、四层/七层负载均衡器的不断“削弱”,我们的服务完成了负载均衡的集群化部署。

Ingress和Gateway

    简单来说,Gateway API是Ingress的继任者。Gateway API是在2022年引入K8S的新概念,目前仍在迅速发展中。这里摘取一段云原生网关演进史 的介绍,具体内容有待后续的学习,如果有进一步的认识会更新博文。


虽然 Ingress 是当前管理集群服务外部流量的标准方法,但在复杂的环境中,往往需要依赖自定义注释和自定义资源定义 (CRD) 来实现所需的操作,从而无疑增加了管理难度和维护成本。

传统的 Ingress 控制器提供了基本的 HTTP 路由功能,但在面对更复杂的流量管理需求时,其灵活性和扩展性显得力不从心。用户不得不通过添加各种自定义注释或引入额外的 CRD 来实现高级功能,如细粒度的流量控制、安全策略、多租户隔离等。这种方式不仅增加了配置的复杂性,还可能导致不同环境下的行为不一致,进而影响系统的稳定性和可维护性。

Kubernetes Gateway API 带来了令人期待的变革。作为一种更现代化的解决方案,Gateway API 提供了一个基于角色的、适应性强的框架,与使用 Kubernetes 服务网络的组织角色保持一致,简化了网络管理和流量控制。Gateway API 不仅保留了 Ingress 的优点,还在多个方面进行了改进和扩展。它提供了更丰富的路由功能,支持多种协议和高级流量管理策略,能够更好地适应复杂的微服务环境和高并发场景。

此外,Gateway API 注重安全性和可扩展性,通过细粒度的策略控制和灵活的配置选项,确保在多租户环境下的资源隔离和安全性。它引入了控制平面和数据平面的分离,允许不同团队基于各自的角色和职责,独立管理和配置流量策略。这不仅提高了系统的可维护性和可扩展性,还增强了团队协作的效率。

附:负载均衡算法

轮询法

    将用户的请求轮流分配给服务器,但是为了避免并发的线程影响“数数”过程(保证pos变量修改的互斥性),需要引入重量级的悲观锁synchronized,这将会导致轮询代码的并发吞吐量发生明显的下降。

随机法

    随机挑选一个服务器,请求足够大时接近轮询。

地址哈希法

    哈希法常有的缺点:如果服务器有上下线,哈希算法将会重置。如果用户已经和某服务器建立了session和cookie,甚至已经设置缓存,可能丢失会话或者造成长时间延迟。

加权轮询法

    不同的服务器性能不同,可以设置权重。

最小连接数法

    将任务分配给此时具有最小连接数的节点,因此它是动态负载均衡算法。一个节点收到一个任务后连接数就会加1,当节点故障时就将节点权值设置为0,不再给节点分配任务。

    上述算法都是最基础的负载均衡算法,任意一个有关负载均衡的服务都有。七层负载均衡可以通过HTTP协议携带的请求头设计出多种多样的均衡算法(Ingress控制器就有很多很多)。