极客时间已完结课程限时免费阅读

26 | 负载均衡:怎样提升系统的横向扩展能力?

26 | 负载均衡:怎样提升系统的横向扩展能力?-极客时间

26 | 负载均衡:怎样提升系统的横向扩展能力?

讲述:唐扬

时长13:21大小12.24M

你好,我是唐扬。
在基础篇中,我提到了高并发系统设计的三个通用方法:缓存、异步和横向扩展。到目前为止,你接触到了缓存的使用姿势,也了解了如何使用消息队列异步处理业务逻辑。那么本节课,我将带你了解一下如何提升系统的横向扩展能力。
在之前的课程中,我也提到过提升系统横向扩展能力的一些案例。比如,08 讲提到可以通过部署多个从库的方式,来提升数据库的扩展能力,从而提升数据库的查询性能,那么就需要借助组件,将查询数据库的请求按照一些既定的策略分配到多个从库上,这是负载均衡服务器所起的作用,而我们一般使用 DNS 服务器来承担这个角色。
不过在实际的工作中,你经常使用的负载均衡的组件应该算是 Nginx,它的作用是承接前端的 HTTP 请求,然后将它们按照多种策略分发给后端的多个业务服务器上。这样,我们可以随时通过扩容业务服务器的方式来抵挡突发的流量高峰。与 DNS 不同的是,Nginx 可以在域名和请求 URL 地址的层面做更细致的流量分配,也提供更复杂的负载均衡策略。
你可能会想到,在微服务架构中我们也会启动多个服务节点承接从用户端到应用服务器的请求,自然会需要一个负载均衡服务器作为流量的入口,实现流量的分发。那么在微服务架构中,如何使用负载均衡服务器呢?
在回答这些问题之前,我先带你了解一下常见的负载均衡服务器都有哪几类,因为这样,你就可以根据不同类型负载均衡服务器的特点做选择了。

负载均衡服务器的种类

负载均衡的含义是:将负载(访问的请求)“均衡”地分配到多个处理节点上。这样可以减少单个处理节点的请求量,提升整体系统的性能。
同时,负载均衡服务器作为流量入口,可以对请求方屏蔽服务节点的部署细节,实现对于业务方无感知的扩容。它就像交通警察,不断地疏散交通,将汽车引入合适的道路上。
而在我看来,负载均衡服务大体上可以分为两大类:一类是代理类的负载均衡服务;另一类是客户端负载均衡服务。
代理类的负载均衡服务以单独的服务方式部署,所有的请求都要先经过负载均衡服务,在负载均衡服务中选出一个合适的服务节点后,再由负载均衡服务调用这个服务节点来实现流量的分发。
由于这类服务需要承担全量的请求,所以对于性能的要求极高。代理类的负载均衡服务有很多开源实现,比较著名的有 LVS、Nginx 等等。LVS 在 OSI 网络模型中的第四层,传输层工作,所以 LVS 又可以称为四层负载;而 Nginx 运行在 OSI 网络模型中的第七层,应用层,所以又可以称它为七层负载(你可以回顾一下02 讲的内容)。
在项目的架构中,我们一般会同时部署 LVS 和 Nginx 来做 HTTP 应用服务的负载均衡。也就是说,在入口处部署 LVS 将流量分发到多个 Nginx 服务器上,再由 Nginx 服务器分发到应用服务器上,为什么这么做呢?
主要和 LVS 和 Nginx 的特点有关,LVS 是在网络栈的四层做请求包的转发,请求包转发之后,由客户端和后端服务直接建立连接,后续的响应包不会再经过 LVS 服务器,所以相比 Nginx 性能会更高,也能够承担更高的并发。
可 LVS 缺陷是工作在四层,而请求的 URL 是七层的概念,不能针对 URL 做更细致的请求分发,而且 LVS 也没有提供探测后端服务是否存活的机制;而 Nginx 虽然比 LVS 的性能差很多,但也可以承担每秒几万次的请求,并且它在配置上更加灵活,还可以感知后端服务是否出现问题。
因此,LVS 适合在入口处承担大流量的请求分发,而 Nginx 要部署在业务服务器之前做更细维度的请求分发。我给你的建议是,如果你的 QPS 在十万以内,那么可以考虑不引入 LVS 而直接使用 Nginx 作为唯一的负载均衡服务器,这样少维护一个组件,也会减少系统的维护成本。
不过这两个负载均衡服务适用于普通的 Web 服务,对于微服务架构来说,它们是不合适的。因为微服务架构中的服务节点存储在注册中心里,使用 LVS 就很难和注册中心交互获取全量的服务节点列表。另外,一般微服务架构中,使用的是 RPC 协议而不是 HTTP 协议,所以 Nginx 也不能满足要求。
所以,我们会使用另一类的负载均衡服务,客户端负载均衡服务,也就是把负载均衡的服务内嵌在 RPC 客户端中。
它一般和客户端应用部署在一个进程中,提供多种选择节点的策略,最终为客户端应用提供一个最佳的、可用的服务端节点。这类服务一般会结合注册中心来使用,注册中心提供服务节点的完整列表,客户端拿到列表之后使用负载均衡服务的策略选取一个合适的节点,然后将请求发到这个节点上。
了解负载均衡服务的分类是你学习负载均衡服务的第一步,接下来,你需要掌握负载均衡策略,这样一来,你在实际工作中配置负载均衡服务的时候,可以对原理有更深刻的了解。

常见的负载均衡策略有哪些

负载均衡策略从大体上来看可以分为两类:
一类是静态策略,也就是说负载均衡服务器在选择服务节点时,不会参考后端服务的实际运行的状态;
一类是动态策略,也就是说负载均衡服务器会依据后端服务的一些负载特性,来决定要选择哪一个服务节点。
常见的静态策略有几种,其中使用最广泛的是轮询的策略(RoundRobin,RR),这种策略会记录上次请求后端服务的地址或者序号,然后在请求时按照服务列表的顺序,请求下一个后端服务节点。伪代码如下:
AtomicInteger lastCounter = getLastCounter();//获取上次请求的服务节点的序号
List<String> serverList = getServerList(); // 获取服务列表
int currentIndex = lastCounter.addAndGet(); //增加序列号
if(currentIndex >= serverList.size()) {
currentIndex = 0;
}
setLastCounter(currentIndex);
return serverList.get(currentIndex);
它其实是一种通用的策略,基本上,大部分的负载均衡服务器都支持。轮询的策略可以做到将请求尽量平均地分配到所有服务节点上,但是,它没有考虑服务节点的具体配置情况。比如,你有三个服务节点,其中一个服务节点的配置是 8 核 8G,另外两个节点的配置是 4 核 4G,那么如果使用轮询的方式来平均分配请求的话,8 核 8G 的节点分到的请求数量和 4 核 4G 的一样多,就不能发挥性能上的优势了
所以,我们考虑给节点加上权重值,比如给 8 核 8G 的机器配置权重为 2,那么就会给它分配双倍的流量,这种策略就是带有权重的轮询策略。
除了这两种策略之外,目前开源的负载均衡服务还提供了很多静态策略:
Nginx 提供了 ip_hash 和 url_hash 算法;
LVS 提供了按照请求的源地址和目的地址做 Hash 的策略;
Dubbo 也提供了随机选取策略以及一致性 Hash 的策略。
但是在我看来,轮询和带有权重的轮询策略能够将请求尽量平均地分配到后端服务节点上,也就能够做到对于负载的均衡分配。在没有更好的动态策略之前,应该优先使用这两种策略,比如 Nginx 就会优先使用轮询的策略。
而目前开源的负载均衡服务中,也会提供一些动态策略,我强调一下它们的原理。
在负载均衡服务器上会收集对后端服务的调用信息,比如从负载均衡端到后端服务的活跃连接数,或者是调用的响应时间,然后从中选择连接数最少的服务,或者响应时间最短的后端服务。我举几个具体的例子:
Dubbo 提供的 LeastAcive 策略,就是优先选择活跃连接数最少的服务;
Spring Cloud 全家桶中的 Ribbon 提供了 WeightedResponseTimeRule 是使用响应时间给每个服务节点计算一个权重,然后依据这个权重,来给调用方分配服务节点。
这些策略的思考点是从调用方的角度出发,选择负载最小、资源最空闲的服务来调用,以期望能得到更高的服务调用性能,也就能最大化地使用服务器的空闲资源,请求也会响应得更迅速。所以我建议你,在实际开发中,优先考虑使用动态的策略。
到目前为止,你已经可以根据上面的分析,选择适合自己的负载均衡策略,并选择一个最优的服务节点。那么问题来了:你怎么保证选择出来的这个节点,一定是一个可以正常服务的节点呢?如果你采用的是轮询的策略,选择出来的是一个故障节点又要怎么办呢?所以,为了降低请求被分配到一个故障节点的几率,有些负载均衡服务器还提供了对服务节点的故障检测功能。

如何检测节点是否故障

24 讲中,我带你了解到在微服务化架构中,服务节点会定期地向注册中心发送心跳包,这样注册中心就能够知晓服务节点是否故障,也就可以确认传递给负载均衡服务的节点一定是可用的。
但对于 Nginx 来说,我们要如何保证配置的服务节点是可用的呢?
这就要感谢淘宝开源的 Nginx 模块nginx_upstream_check_module了,这个模块可以让 Nginx 定期地探测后端服务的一个指定的接口,然后根据返回的状态码来判断服务是否还存活。当探测不存活的次数达到一定阈值时,就自动将这个后端服务从负载均衡服务器中摘除。它的配置样例如下:
upstream server {
server 192.168.1.1:8080;
server 192.168.1.2:8080;
check interval=3000 rise=2 fall=5 timeout=1000 type=http default_down=true;//检测间隔为3秒,检测超时时间是1秒,使用http协议。如果连续失败次数达到5次就认为服务不可用;如果连续成功次数达到2次,则认为服务可用。后端服务刚启动时状态是不可用的
check_http_send "GET /health_check HTTP/1.0\r\n\r\n"; //检测URL
check_http_expect_alive http_2xx; //检测返回状态码为200时认为检测成功
}
Nginx 按照上面的方式配置之后,你的业务服务器也要实现一个“/health_check”的接口,在这个接口中返回的 HTTP 状态码,这个返回的状态码可以存储在配置中心中,这样在变更状态码时,就不需要重启服务了(配置中心在第 33 节课中会讲到)。
节点检测的功能,还能够帮助我们实现 Web 服务的优雅关闭。在24 讲中介绍注册中心时,我曾经提到,服务的优雅关闭需要先切除流量再关闭服务,使用了注册中心之后,就可以先从注册中心中摘除节点,再重启服务,以便达到优雅关闭的目的。那么 Web 服务要如何实现优雅关闭呢?接下来,我们了解一下有了节点检测功能之后,服务是如何启动和关闭的。
在服务刚刚启动时,可以初始化默认的 HTTP 状态码是 500,这样 Nginx 就不会很快将这个服务节点标记为可用,也就可以等待服务中依赖的资源初始化完成,避免服务初始启动时的波动。
在完全初始化之后,再将 HTTP 状态码变更为 200,Nginx 经过两次探测后,就会标记服务为可用。在服务关闭时,也应该先将 HTTP 状态码变更为 500,等待 Nginx 探测将服务标记为不可用后,前端的流量也就不会继续发往这个服务节点。在等待服务正在处理的请求全部处理完毕之后,再对服务做重启,可以避免直接重启导致正在处理的请求失败的问题。这是启动和关闭线上 Web 服务时的标准姿势,你可以在项目中参考使用。

课程小结

本节课,我带你了解了与负载均衡服务相关的一些知识点,以及在实际工作中的运用技巧。我想强调几个重点:
网站负载均衡服务的部署,是以 LVS 承接入口流量,在应用服务器之前,部署 Nginx 做细化的流量分发和故障节点检测。当然,如果你的网站的并发不高,也可以考虑不引入 LVS。
负载均衡的策略可以优先选择动态策略,保证请求发送到性能最优的节点上;如果没有合适的动态策略,那么可以选择轮询的策略,让请求平均分配到所有的服务节点上。
Nginx 可以引入 nginx_upstream_check_module,对后端服务做定期的存活检测,后端的服务节点在重启时,也要秉承着“先切流量后重启”的原则,尽量减少节点重启对于整体系统的影响。
你可能会认为,像 Nginx、LVS 应该是运维所关心的组件,作为开发人员不用操心维护。不过通过今天的学习你应该可以看到:负载均衡服务是提升系统扩展性和性能的重要组件,在高并发系统设计中,它发挥的作用是无法替代的。理解它的原理,掌握使用它的正确姿势,应该是每一个后端开发同学的必修课。

一课一思

在实际的工作中,你一定也用过很多的负载均衡的服务和组件,那么在使用过程中你遇到过哪些问题呢,有哪些注意的点呢?欢迎在留言区与我分享你的经验。
最后,感谢你的阅读,如果这篇文章让你有所收获,也欢迎你将它分享给更多的朋友。
分享给需要的人,Ta购买本课程,你将得18
生成海报并分享

赞 15

提建议

上一篇
25 | 分布式Trace:横跨几十个分布式组件的慢请求要如何排查?
下一篇
27 | API网关:系统的门面要如何做呢?
 写留言

精选留言(35)

  • 面试官问
    2019-11-22
    是否使用客户端负载均衡,跟微服务框架中服务之间通信是使用 RPC 协议还是 HTTP 协议无关,例如Spring Cloud 中的 Ribbon 就是用来进行客户端负载均衡的;一般来说,在系统接入层,使用的是服务端负载均衡,而微服务之间的内部调用,使用的是客户端负载均衡。

    作者回复: 是的

    36
  • 虚竹
    2020-03-25
    老师最后说的nginx启停服务时切流量部分,在刚刚启动时返回500,完全初始化后返回200,是指health_check接口返回的状态码是通过配置中心配置获取的,重启之前改为500,看实例日志全完启动成功之后再改为200?

    作者回复: 是的

    共 2 条评论
    16
  • jc9090kkk
    2020-01-16
    感谢老师分享,这一刻看完有一个疑问,对于负载均衡的一些策略都清楚了,但是对于负载均衡器的承载容量有点疑问,比如说通过nginx来作为负载均衡器,但是它本身的承载容量是有极限的,如果当前nginx的转发容量只能支撑10w/s的访问请求,但是流量如果达到20w/s,那么nginx作为负载均衡器来讲,是否需要增加新的负载均衡器一起协调工作,那么同时存在两个负载均衡器的话,工作于它上一层的任务由谁来处理呢?lvs?还是dns?或者是其他?负载均衡器不可能没有极限吧?
    展开

    作者回复: 是LVS,LVS的承载能力要大很多

    共 4 条评论
    12
  • 蓝魔丶
    2019-11-26
    1.nginx类的负载组件只支持静态配置,无法动态感知配置,使用openrestry代替nginx好很多,不仅可以动态配置,而且比nginx提供了更多扩展模块 2.在docker容器的k8s环境中也使用过nginx-controller代理组件,但是这个组件虽然可以实现动态配置参数,但是路由和治理能力都偏弱,可以考虑使用istio等servicemesh网关
    8
  • sdjdd
    2019-11-22
    关闭服务之前,用 503 状态码响应健康检查是不是语义更明确一些。

    作者回复: 是的,我的意思是这样的

    7
  • 古德
    2020-01-06
    以前用阿里云的时候,我们的架构是使用阿里云的SLB,直接转发到微服务网关zuul上,再根据url去分发到对应服务。

    作者回复: 也可以,zuul作为7层负载

    6
  • M
    2019-11-22
    请教下老师,app与服务器之间使用websocket协议连接,如何使用负载均衡呢?

    作者回复: nginx可以支持websocket协议的

    共 3 条评论
    5
  • 如歌
    2020-03-28
    刷第二遍了 从操作系统到网络到这本书

    作者回复: 加油💪

    共 2 条评论
    3
  • Geek_4d0d3e
    2020-12-10
    我们用kong做K8S集群业务的负载均衡
    2
  • い北风
    2020-01-03
    老师,我在之前的面试遇到过一个问题。知道已有功能,如何选取服务器。去购买服务器的配置呢?

    作者回复: 我想面试官想了解的是 你是否可以根据业务特点来选取服务器,比如数据库要选取大内存、大磁盘的存储型的服务器;计算比较多的业务要选取CPU性能比较好的

    共 3 条评论
    3
  • 阿土
    2019-11-28
    优雅关机与启动有案例么?理解了原理,具体怎么实现呢?

    作者回复: 文中提到的nginx的例子,就是一个案例 除此之外,在微服务在重启时,先从注册中心中摘掉节点,然后观察流量没有了之后再重启。 其实,总的一点就是先切流量,再重启

    共 3 条评论
    3
  • longslee
    2019-11-25
    打卡。 涨姿势了。 曾经项目使用一层 IBM WebSphere 简单路由,但是客户端学聪明了,都手动绑定 hosts ,负载不均衡了。所以还是多层不暴露比较好。 提问:老师,您讲到结合注册中心的客户端负载均衡,它又是怎么做到“动态策略”的呢?

    作者回复: 文中有提到,一般是在RPC客户端里面统计对服务端的连接数或者响应时间,这样可以选择连接数最少,或者响应时间最短的服务端

    2
  • 啊啊啊哦哦
    2019-11-24
    nginx 上为什么要阶lvs。 一般dns服务器也可以实现轮训分发到不同的nginx上啊。

    作者回复: LVS能抗更高的并发

    2
  • null
    2020-09-16
    原文:比如从负载均衡端到后端服务的活跃连接数,然后从中选择连接数最少的服务。 怎么知道哪个后端服务的活跃连接数最少?负载均衡服务不是内嵌在客户端的么?各各客户端的负载均衡数据没有共享吧。
    1
  • Luciano李鑫
    2019-11-25
    负载均衡应该分为: 客户端负载均衡 后端负载均衡 DNS负载均衡 IP负载均衡 反向代理负载均衡
    展开
    共 2 条评论
    1
  • 小喵喵
    2019-11-22
    1.Nginx和LVS都可以做负载均衡,这些组件也都可以应用于C/S系统吗? 2.health_check只能检测到服务器和节点指点是否可用,节点可用但是并不能代表内部服务接口是可用的,这个有什么好的方案呢?

    作者回复: 1. 是可以的 2. 是的,这个在于你被探测接口的实现

    1
  • leesir
    2019-11-22
    求教,对于普通web服务,nginx有办法感知新增结点吗?

    作者回复: 可以呀,有一种consul + nginx方案,就是把节点信息写在consul里面,这样当节点变化时,nginx可以得到通知

    2
  • 李明
    2021-12-03
    nginx代理的后端服务,如果某个正在处理请求的后端服务被nginx摘除了,那么被摘除的后端服务还没来的及返回给客户端的响应,还能正常返回不
    共 1 条评论
  • 鸠摩·智
    2021-08-21
    老师,烦劳有空解答一下心中疑惑。 原文:LVS 是在网络栈的四层做请求包的转发,请求包转发之后,由客户端和后端服务直接建立连接,后续的响应包不会再经过 LVS 服务器。 1.如果lvs是工作在四层(传输层),对于tcp协议,感觉lvs是有状态的,需要维护客户端和服务连接的吧?另外,如果lvs只做包转发,由客户端和服务直接建立连接,又感觉lvs是工作在ip网络层。 2.后续的响应包不经过lvs。 老师,由上面1和2,我猜想是不是,lvs确实工作在传输层,但是lvs只维护客户端到服务的单向连接吗? 处理客户端到服务单向连接的意思是,lvs只处理syn,从而保持客户端与服务的对应关系,而不处理传输层其他细节,比如乱序、丢包、拥塞等。这里的资源占用开销很小,所以从lvs实现层面是保持连接,而从lvs使用层面来讲就接近无状态,接近修改目标地址直接转发包,从而说成不处理客户端和服务的连接了。 盼复,谢谢!
    展开
    共 1 条评论
  • 石头
    2021-04-24
    zk实际用的是zab,consul用raft,如果探究一下raft的原理会挺有意思的,大大简化了paxos