2023-08-12
原文作者:Ressmix 原文地址:https://www.tpvlog.com/article/78

一、引言

前面的读写分离分库分表主要是从存储压力来考虑性能问题,本章将介绍的负载均衡主要从计算能力的角度来考虑性能。

单服务器无论如何优化,无论采用多好的硬件,总会有一个性能天花板,当单服务器的性能无法满足业务需求时,就需要设计高性能集群来提升系统整体的处理性能。高性能集群的本质很简单——通过增加更多的服务器来提升系统整体的计算能力。

高性能集群设计的复杂度主要体现在任务分配这部分,需要设计合理的任务分配策略,将计算任务分配到多台服务器上执行。这个任务分配器,就是“负载均衡器”。常见的负载均衡系统包括3种: DNS负载均衡硬件负载均衡软件负载均衡

1.1 DNS负载均衡

DNS是最简单也是最常见的负载均衡方式,一般用来实现 地理级别 的均衡。例如,北方的用户访问北京的机房,南方的用户访问上海的机房。DNS负载均衡的本质是DNS解析同一个域名可以返回不同的IP地址。例如,同样是www.baidu.com,北方用户解析后获取的地址是61.135.165.224(这是北京机房的IP),南方用户解析后获取的地址是14.215.177.38(这是上海机房的IP)。

下面是DNS负载均衡的简单示意图:

202308122217373321.png

DNS负载均衡实现简单、成本低,但也存在粒度太粗、负载均衡算法少等缺点。

优点:

  • 简单、成本低:负载均衡工作交给DNS服务器处理,无须自己开发或者维护负载均衡设备;
  • 就近访问,提升访问速度:DNS解析时可以根据请求来源IP,解析成距离用户最近的服务器地址,可以加快访问速度,改善性能。

缺点:

  • 更新不及时:DNS缓存的时间比较长,修改DNS配置后,由于缓存的原因,还是有很多用户会继续访问修改前的IP,这样的访问会失败,达不到负载均衡的目的,并且也影响用户正常使用业务;
  • 扩展性差:DNS负载均衡的控制权在域名商那里,无法根据业务特点针对其做更多的定制化功能和扩展特性;
  • 分配策略比较简单:DNS负载均衡支持的算法少;不能区分服务器的差异(不能根据系统与服务的状态来判断负载),也无法感知后端服务器的状态。

1.2 硬件负载均衡

硬件负载均衡是通过单独的硬件设备来实现负载均衡功能,这类设备和路由器、交换机类似,可以理解为一个用于负载均衡的基础网络设备。目前业界典型的硬件负载均衡设备有两款:F5A10
这类设备性能强劲、功能强大,但价格都不便宜,一般只有大型公司才会考虑使用此类设备。普通业务量级的公司一是负担不起,二是业务量没那么大,用这些设备也是浪费。

优点:

  • 功能强大:全面支持各层级的负载均衡,支持全面的负载均衡算法,支持全局负载均衡;
  • 性能强大:硬件负载均衡可以支持100万以上的并发,而软件负载均衡能支持到10万级并发就很不错了;
  • 稳定性高:商用硬件负载均衡,经过了良好的严格测试,经过大规模使用,稳定性高;
  • 支持安全防护:硬件均衡设备除具备负载均衡功能外,还具备防火墙、防DDoS攻击等安全功能。

缺点:

  • 定制化较为困难

1.3 软件负载均衡

软件负载均衡是指通过负载均衡软件来实现负载均衡功能,常见的有NginxLVS,其中Nginx是软件的7层负载均衡,LVS是Linux内核的4层负载均衡。下面是Nginx的负载均衡架构示意图:

202308122217380352.png

4层和7层的区别就在于协议和灵活性,Nginx支持HTTP、E-mail协议;而LVS是4层负载均衡,和协议无关,几乎所有应用都可以做,例如,聊天、数据库等。

优点:

  • 简单:无论是部署还是维护都比较简单;
  • 便宜:只要买个Linux服务器,装上软件即可;
  • 灵活:4层和7层负载均衡可以根据业务进行选择,也可以根据业务进行比较方便的扩展,例如,可以通过Nginx的插件来实现业务的定制化功能。

缺点:

  • 性能一般:Ngxin的性能是万级,一个Nginx大约能支撑5万并发,LVS的性能是十万级;
  • 一般不具备防火墙和防DDoS攻击等安全功能。

二、负载均衡架构

前面我们介绍了3种常见的负载均衡机制:DNS负载均衡、硬件负载均衡、软件负载均衡,每种方式都有一些优缺点,但并不意味着在实际应用中只能基于它们的优缺点进行非此即彼的选择,反而是基于它们的优缺点进行组合使用。具体来说,组合的基本原则为:

  • DNS负载均衡用于实现地理级别的负载均衡
  • 硬件负载均衡用于实现集群级别的负载均衡
  • 软件负载均衡用于实现机器级别的负载均衡

以一个假想的实例来说明一下这种组合方式,如下图所示:

202308122217385943.png

上述整个系统的负载均衡分为三层:

  • 地理级别负载均衡:www.xxx.com部署在北京、广州、上海三个机房,当用户访问时,DNS会根据用户的地理位置来决定返回哪个机房的IP,图中返回了广州机房的IP地址,这样用户就访问到广州机房了。
  • 集群级别负载均衡:广州机房的负载均衡用的是F5设备,F5收到用户请求后,进行集群级别的负载均衡,将用户请求发给3个本地集群中的一个,我们假设F5将用户请求发给了“广州集群2”。
  • 机器级别的负载均衡:广州集群2的负载均衡用的是Nginx,Nginx收到用户请求后,将用户请求发送给集群里面的某台服务器,服务器处理用户的业务请求并返回业务响应。

需要注意的是,上图只是一个示例,一般在大型业务场景下才会这样用,如果业务量没这么大,则没有必要严格照搬这套架构。例如,一个普通的公司管理系统,完全可以不需要DNS负载均衡,也不需要F5设备,只需要用Nginx作为一个简单的负载均衡就足够了。

三、负载均衡算法

负载均衡算法数量较多,而且可以根据一些业务特性进行定制开发,抛开细节上的差异,根据算法期望达到的目的,大体上可以分为下面几类:

  • 任务平分类:负载均衡系统将收到的任务平均分配给服务器进行处理,这里的“平均”可以是绝对数量的平均,也可以是比例或者权重上的平均;
  • 负载均衡类:负载均衡系统根据服务器的负载来进行分配,这里的负载并不一定是通常意义上我们说的“CPU负载”,而是系统当前的压力,可以用CPU负载来衡量,也可以用连接数、I/O使用率、网卡吞吐量等来衡量系统的压力;
  • 性能最优类:负载均衡系统根据服务器的响应时间来进行任务分配,优先将新任务分配给响应最快的服务器;
  • Hash类:负载均衡系统根据任务中的某些关键信息进行Hash运算,将相同Hash值的请求分配到同一台服务器上。常见的有源地址Hash、目标地址Hash、session id hash、用户ID Hash等。

接下来介绍一下常见的负载均衡算法以及它们的优缺点。

3.1 普通轮询

负载均衡系统收到请求后,按照顺序轮流分配到服务器上。轮询是最简单的一个策略,无须关注服务器本身的状态。但如果服务器直接宕机了,或者服务器和负载均衡系统断连了,这时负载均衡系统是能够感知的,也需要做出相应的处理。

“简单”是轮询算法的优点,也是它的缺点。

3.2 加权轮询

负载均衡系统根据服务器权重进行任务分配, 权重一般是根据硬件配置进行静态配置的 ,采用动态的方式计算会更加契合业务,但复杂度也会更高。

加权轮询是普通轮询的一种特殊形式,其主要目的就是为了解决不同服务器处理能力有差异的问题。例如,集群中有新的机器是32核的,老的机器是16核的,那么理论上我们可以假设新机器的处理能力是老机器的2倍,负载均衡系统就可以按照2:1的比例分配更多的任务给新机器,从而充分利用新机器的性能。

加权轮询解决了普通轮询算法中无法根据服务器的配置差异进行任务分配的问题,但同样存在无法根据服务器的状态差异进行任务分配的问题。

3.3 负载最低优先

负载均衡系统将任务分配给 当前负载最低 的服务器,这里的负载根据不同的任务类型和业务场景,可以用不同的指标来衡量。例如:

  • LVS这种4层网络负载均衡设备,可以以“连接数”来判断服务器的状态,服务器连接数越大,表明服务器压力越大;
  • Nginx这种7层网络负载系统,可以以“HTTP请求数”来判断服务器状态(Nginx内置的负载均衡算法不支持这种方式,需要进行扩展);
  • 如果我们自己开发负载均衡算法,可以根据业务特点来选择指标衡量系统压力。如果是CPU密集型,可以以“CPU负载”来衡量系统压力;如果是I/O密集型,可以以“I/O负载”来衡量系统压力。

负载最低优先的算法解决了轮询算法中无法感知服务器状态的问题,由此带来的代价是复杂度要增加很多:

  • 负载均衡系统需要统计每个服务器当前建立的连接,其应用场景仅限于负载均衡接收的任何连接请求都会转发给服务器进行处理,否则如果负载均衡系统和服务器之间是固定的连接池方式,就不适合采取这种算法。例如,LVS可以采取这种算法进行负载均衡,而一个通过连接池的方式连接MySQL集群的负载均衡系统就不适合采取这种算法进行负载均衡;
  • CPU负载最低优先的算法要求负载均衡系统以某种方式收集每个服务器的CPU负载,而且要确定是以1分钟的负载为标准,还是以15分钟的负载为标准,不存在1分钟肯定比15分钟要好或者差。不同业务最优的时间间隔是不一样的,时间间隔太短容易造成频繁波动,时间间隔太长又可能造成峰值来临时响应缓慢。

负载最低优先算法基本上能够比较完美地解决轮询算法的缺点,因为采用这种算法后,负载均衡系统需要感知服务器当前的运行状态。当然,其代价是复杂度大幅上升。负载最低优先算法如果本身没有设计好,或者不适合业务的运行特点,算法本身就可能成为性能的瓶颈,或者引发很多莫名其妙的问题。所以负载最低优先算法虽然效果看起来很美好,但实际上真正应用的场景反而没有轮询(包括加权轮询)那么多。

3.4 性能最优优先

负载最低优先类算法是站在服务器的角度来进行分配的,而性能最优优先类算法则是 站在客户端的角度来进行分配的,优先将任务分配给处理速度最快的服务器,通过这种方式达到最快响应客户端的目的

性能最优优先类算法本质上也是感知了服务器的状态,只是通过 响应时间 这个外部标准来衡量服务器状态而已。因此性能最优优先类算法存在的问题和负载最低优先类算法类似,复杂度都很高,主要体现在:

  • 负载均衡系统需要收集和分析每个服务器每个任务的响应时间,在大量任务处理的场景下,这种收集和统计本身也会消耗较多的性能;
  • 为了减少统计上的消耗,可以采取抽样统计:即不统计所有任务的响应时间,而是抽样统计部分任务的响应时间来估算整体任务的响应时间。抽样统计虽然能够减少性能消耗,但使得复杂度进一步上升,采样率太低会导致结果不准确,采样率太高会导致性能消耗较大,找到合适的采样率也是一件复杂的事情。

3.5 Hash类

负载均衡系统根据任务中的某些关键信息进行Hash运算,将相同Hash值的请求分配到同一台服务器上,这样做的目的主要是为了满足特定的业务需求。例如:

源地址Hash:
将来源于同一个源IP地址的任务分配给同一个服务器进行处理,适合于存在事务、会话的业务。例如,当我们通过浏览器登录网上银行时,会生成一个会话信息,这个会话是临时的,关闭浏览器后就失效。网上银行后台无须持久化会话信息,只需要在某台服务器上临时保存这个会话就可以了,但需要保证用户在会话存在期间,每次都能访问到同一个服务器,这种业务场景就可以用源地址Hash来实现。

ID Hash:
将某个ID标识的业务分配到同一个服务器中进行处理,这里的ID一般是临时性数据的ID(如session id)。例如,上述的网上银行登录的例子,对sessionId 进行hash同样可以实现同一个会话期间,用户每次都是访问到同一台服务器的目的。

四、总结

本文介绍了常见的负载均衡架构设计模式及负载均衡算法。目前业界用的最多的是软负载均衡器——Nginx,后续进阶篇中将详细介绍其特性和原理。

阅读全文