回到首页

输入关键词,多个关键词用空格分隔。

← 云计算课程 · 系列目录

云计算 第 12 课 深入负载均衡器及其使用策略

约 12 分钟
本页目录

上一讲中我们接触负载均衡器,并且通过一个模拟实验进行了基本的体验,这节课我们通过 Azure 来探索一下负载均衡器的工作原理。


学习目标

  1. 了解负载均衡器内部的基本组件
  2. 理解可能影响负载分配策略的各种因素
  3. 理解不同的负载分配策略如何影响应用的服务质量(QoS)
  4. 使用 Round-Robin 策略来实现一个负载均衡器
  5. 学会如何观察和分析实例池中的资源使用情况,并基于这些信息实现一个高效的负载分配策略
  6. 在不丢弃请求的前提下,学会如何监控实例的健康状况,并处理实例挂掉的情况

项目简介

负载均衡器复习

在 web 服务中,负载均衡器是非常重要的,为什么呢?原因有二:

  1. 提高服务质量(QoS),包括吞吐量及延迟等等
  2. 高可用性(HA),也就是保证接近 100% 的可用性(用户在不同时间地点都可以访问)

前面的项目中,我们使用 Amazon ELB 和 Azure 的 Load Balancer 服务来提高服务质量以及减小花费和处理实例失败。

在 Horizontal Scaling 的时候,负载均衡是由 load generator 完成的。每次添加新机器,load generator 会维护一个列表,并把不同的请求发送给不同的 data center。而在 Auto Scaling 中,我们则是使用 ELB 来完成负载均衡,也就使得我们可以不借助客户端(这里 load generator 可以看作是客户端的集合),就能快速增加机器数量(之前是需要通知客户端来发送给不同的机器d而)。换句话说,我们可以在不更新客户端程序的前提下,根据需求灵活调整机器数量。

除了分配流量,负载均衡器还需要负责处理实例失败。只要 ELB 背后还有一个健康的实例,Load Generator 就会继续发送请求。而 ELB 本身是通过一段时间给实例发消息来检测该实例是否存活的。

看起来很美,对不对,但实际上不同的负载分配策略对性能也会有极大的影响,这个我们后面接着讨论。

负载分配策略

Amazon 的 ELB 使用 Round-Robin 策略来分配负载,并不考虑服务器当前的状况(即使不同的服务器在不同的区域,也不管,反正对于所有机器一视同仁)

上一次我们发送的请求,每一个所需要的服务器资源(CPU,内存,磁盘 IO,网络 IO) 都是类似的,所以我们可以方便地通过增加或减少机器数量来进行调整。

可是实际生活中,不同的请求可能需要的资源差异也很大,如果有些请求需要大量的资源怎么办,这个时候 Round-Robin 策略还是不是高效呢?资源的使用率还会不会平衡呢?这又会如何影响整体的服务质量呢?

这一次我们需要自己实现一个负载均衡的策略,同时还要了解如何监控实例的健康状况,确保只发送请求给健康的实例。

最后需要说明的是,这次的项目主要在 Azure 平台上进行,在 AWS 上重复一次可以得到 10% 的加分,但只能使用 Java

镜像列表

Azure

  • Data Center, Standard_A1, https://cc15319619.blob.core.windows.net/system/Microsoft.Compute/Images/vhds/cc15619p22dcv6-osDisk.b0c453f3-f75f-4a2d-bd9c-ae055b830124.vhd
  • Load Generator, Standard_D1, https://cc15319619.blob.core.windows.net/system/Microsoft.Compute/Images/vhds/cc15619p22lgv7-osDisk.c0410b8f-821e-4de3-b725-2a834fd10060.vhd
  • Load Balancer, Standard_D1, https://cc15319619.blob.core.windows.net/system/Microsoft.Compute/Images/vhds/cc15619p22lbv2-osDisk.1cf68388-ac67-4165-bec0-67341257d50a.vhd

AWS

  • Load Generator, m3.medium, ami-0d4e6067
  • Data Center, m3.medium, ami-6f486605
  • Load Balancer, m3.medium / m3.large, ami-f44c629e
  • 标签:Project: 2.2

因为 Azure 的机制,我们需要把镜像先复制到自己的存储账户中,命令如下:

# LG
azure storage blob copy start https://cc15319619.blob.core.windows.net/system/Microsoft.Compute/Images/vhds/cc15619p22lgv7-osDisk.c0410b8f-821e-4de3-b725-2a834fd10060.vhd --dest-account-name wdxstore --dest-account-key Xc6DDXunKq44PQe3Jhey07fRKeFfPqTm8JBU8CFTPNnj4nCzdgXbWEFvAiM+5F16XBSzwgRyLp0c+os24p4W1w== --dest-container p22

# DC
azure storage blob copy start https://cc15319619.blob.core.windows.net/system/Microsoft.Compute/Images/vhds/cc15619p22dcv6-osDisk.b0c453f3-f75f-4a2d-bd9c-ae055b830124.vhd  --dest-account-name wdxstore --dest-account-key Xc6DDXunKq44PQe3Jhey07fRKeFfPqTm8JBU8CFTPNnj4nCzdgXbWEFvAiM+5F16XBSzwgRyLp0c+os24p4W1w== --dest-container p22

# LB
azure storage blob copy start https://cc15319619.blob.core.windows.net/system/Microsoft.Compute/Images/vhds/cc15619p22lbv2-osDisk.1cf68388-ac67-4165-bec0-67341257d50a.vhd --dest-account-name wdxstore --dest-account-key Xc6DDXunKq44PQe3Jhey07fRKeFfPqTm8JBU8CFTPNnj4nCzdgXbWEFvAiM+5F16XBSzwgRyLp0c+os24p4W1w== --dest-container p22

负载均衡器策略

负载模式 - 资源竞争

对于每一台 EC2 实例来说,有如下限制:

  1. 计算能力 - 每个请求的计算复杂度越高,能够处理的请求总数越低
  2. 内存容量 - 每个请求所需要的内存越多,能够处理的请求总数越低
  3. 磁盘 IO - 因为每秒中的读写次数是有上限的,每个请求所需要磁盘读写越多,能够处理的请求总数越低
  4. 网络 I/O - 带宽是有上限的,传输大的数据包会影响到其他数据的传输速度

通常来说,一旦出现资源过载的现象,系统就不得不杀死当前的进程,也就会导致服务质量的下降。那么我们怎么办呢?之前我们的策略是申请更好的实例或者更多的实例,但是随之而来的就是要花更多的钱,即使如此有时候还是不够(有些实例会超载),所以我们需要想办法,让所有的实例一同承受流量冲击

Round Robin

Round Robin 策略很简单,就是轮转着把每个请求分配到不同的实例上去,而不考虑优先级什么的。这个方法的优势在于:超™简单。

这种策略一旦遇到需要特别多资源的请求时,就可能造成某一台实例超载的同时,其他实例都闲着没啥事儿,服务质量降低了不说,钱也没花到刀刃上。

但是当有一个比较大的实例池的时候,所有需要很多资源的请求集中在一个实例上的机会是很小的(但是并不是没有,还是需要具体情况具体分析)

聪明的负载分配策略

相信聪明的同学已经想到可能的解决办法了,我们只需要找到最合适的实例,把请求对应发过去即可,而不是按顺序一个一个发送。那么问题就来了,怎么找到这个『最合适的』呢?

  1. 我们需要了解针对一个请求,是要竞争哪一类资源
  2. 我们需要知道每个 data center 的工作情况
  3. 我们需要根据使用情况来决定下一个请求要发到哪里

于是可以继续问自己,针对上述情况,什么策略是最好的,又需要通过什么样的数据结构来存储这些信息呢?

这个方法一定会比 Round-Robin 好吗?不一定,因为判断『最合适』本身,就需要一定资源,带来一定延迟,除非有很大提高,不然功不抵过呀。

下面是两个可能的策略:

基于请求执行时间的策略

这种策略的难点在于,如何预测一个请求需要执行多长时间。

基于资源使用率的策略

平衡所有 data center 的使用率,维护一个有序列表,把请求发给负载最小的实例。

Round Robin 实战

我们先来实现一个基于 Round-Robin 的负载均衡器并评估它的效率。这里用两种不同的流量模式来进行测试:

  1. 周末流量包(测试 Round-Robin 调度)
  2. 工作日流量包(测试自定义调度)

任务列表

  1. 在 Azure 中启动一台 load balancer 虚拟机
  2. SSH 到 load balancer 上(用户名 ubuntu, 密码 Cloud@123),需要做的内容在 /home/ubuntu/Project22/ 文件夹中
  3. 了解给出的框架代码
    • 可以在 Main.java 找到放置虚拟机 DNS 地址的地方
    • 在 LoadBalancer.java 的 start() 方法中开始写自定义的负载均衡策略
  4. 补充完整 LoadBalancer.java 中的 start() 方法,使其可以用 round robin 的方式给已连接的实例分配负载,完成之后使用 javac *.java 来进行编译
  5. 如果你确定自己的代码是对的,那么开启三个 data center 虚拟机,并在 Main.java 中填写的 http://[your DC DNS] 里填好对应的 DNS 地址
  6. 使用 ./run 来执行你的代码
  7. 可以在浏览器中访问 http://[your load balancer DNS] 看到测试 UI
  8. 确定 load balancer 的 round robin 没有问题后,开启一个 load generator 虚拟机
  9. 提交密码和 andrew id
  10. 开始 round robin 测试,目标是达到平均每秒 59 的 RPS
  11. 访问 http://[your DC DNS]:8080/info/cpu 来查看 data center 的 CPU 使用率

负载是否均衡?在这个测试中,round robin 是一个好的策略吗?

接下来我们就要进入自定义调度算法的测试了。

备注

  • 可以在终端使用提供的 Java/Python 代码来开启所有的虚拟机。但是如果一个虚拟机没能通过健康检查,你需要在 load balancer 的代码中完成虚拟机的启动。
  • 注意代码的整洁和模块化。多写点注释,阐述自己的思路
  • 只能修改 LoadBalancer.java, Main.java 和 DataCenterInstance.java
  • 不能修改 Request.java, RequestHandler.java, Response.java 和 ResponseBuilder.java

解题步骤

先用之前的代码复制镜像,然后开启 1 个 Load Balancer,1 个 Load Generator 和 3 个 Data Center。然后 SSH 到 Load Balancer 上:ssh ubuntu@lbvmwdx.eastus.cloudapp.azure.com 密码是 Cloud@123。然后我们把项目代码拷贝回本地 scp -r ubuntu@yourdns.eastus.cloudapp.azure.com:~/Project22/* ./

接着大概观察一下代码,主要做两件事情:

  1. 把 Data Center 的 DNS 填到 Main.java 中
  2. 补充完全 LoadBalancer.java 中的 start() 方法

这里注意以下几个地方:

  1. LoadBalancer 类在被创建的时候就把 instances 的列表传进来了,直接调用即可
  2. 默认是把所有请求都发给第一个 Data Center 的,只需要改一下这里的索引就好。
  3. 修改完成之后整个文件夹上传回去 scp -r ./* ubuntu@yourdns.eastus.cloudapp.azure.com:~/Project22/
  4. 提交 dns 的时候,不用前面的 http 和最后的 / 之类的,注意下格式

接着就和前面任务列表中的一致了,这里不赘述。

改进负载均衡器策略

下一个任务是开发一个负载均衡的策略,以便通过 自定调度算法测试。

可以通过 http://[data center virtual machine dns]:8080/info/cpu 来了解 data center 的 CPU 使用情况,能否通过这个方法,来通过测试呢?

任务列表

  1. 在 Azure 中启动一台 load balancer 虚拟机
  2. SSH 到 load balancer 上(用户名 ubuntu, 密码 Cloud@123),需要做的内容在 /home/ubuntu/Project22/ 文件夹中
  3. 补充完整 LoadBalancer.java 中的 start() 方法,使其可以通过查询 CPU 使用率的方式给已连接的实例分配负载,完成之后使用 javac *.java 来进行编译
  4. 如果你确定自己的代码是对的,那么开启三个 data center 虚拟机,并在 Main.java 中填写的 http://[your DC DNS] 里填好对应的 DNS 地址
  5. 使用 ./run 来执行你的代码
  6. 可以在浏览器中访问 http://[your load balancer DNS] 看到测试 UI
  7. 确定 load balancer 的自定义调度算法没有问题后,开启一个 load generator 虚拟机
  8. 开始 自定义调度算法 测试,目标是达到平均每秒 41 的 RPS
  9. 访问 http://[your DC DNS]:8080/info/cpu 来查看 data center 的 CPU 使用率

负载是否均衡?在这个测试中,你的算法是一个好的策略吗?

疑难杂症

这里我遇到一个问题,就是 RPS 一直徘徊在 21 左右,死活上不去。我觉得可能是以下几个问题:

  1. Load Generator 发送请求不正常,因为之前强行停止过测试,不知道有没有影响
  2. Load Balancer 判断的时候花费的时间太长,如果是这样的话需要优化代码
  3. 发送请求的间隔数目可能太小,导致检查过于频繁

我觉得很可能是第二个,因为总是能看到选择的 Data Center 的 CPU 使用率为 0,估计是 Load Generator 分发不够快所致。不过我们还是一个一个来试一次

  1. 重启之后发现并没有什么影响,还是处于比较低的 RPS,所以可以先试试看修改间隔数目
  2. 间隔数目改成 20 之后并没有特别大的改变,所以可以确定是 Load Balancer 的问题
  3. 经过问同学和排查,发现了问题所在,需要在选择最空闲的机器之后,继续使用 round robin 的策略

然后问题得以解决。

监控 Data Center 的状态

下一步是让我们的 Load Balancer 能够处理某个 Data Center 挂掉的情况。一旦某个虚拟机挂掉,就开启一个新的,用以代替旧的。

可以通过发送 HTTP 请求来查看 Data Center 的状况,只有返回 200 的时候,才认为它是在工作的。

任务列表

  1. 在 Azure 中启动一台 load balancer 虚拟机
  2. SSH 到 load balancer 上(用户名 ubuntu, 密码 Cloud@123),需要做的内容在 /home/ubuntu/Project22/ 文件夹中
  3. 实现带有健康检查的负载均衡器,也就是说,能检测出机器挂掉并停止向其发送请求,然后开启一个新的虚拟机,直到它能工作时,就用新的代替旧的。
  4. 完成之后使用 javac *.java 来进行编译
  5. 如果你确定自己的代码是对的,那么开启三个 data center 虚拟机,并在 Main.java 中填写的 http://[your DC DNS] 里填好对应的 DNS 地址
  6. 使用 ./run 来执行你的代码
  7. 可以在浏览器中访问 http://[your load balancer DNS] 看到测试 UI
  8. 确定 load balancer 的自定义调度算法以及健康检查没有问题后,开启一个 load generator 虚拟机
  9. 开始 自定义调度算法+健康检查 测试,目标是达到平均每秒 41 的 RPS
  10. 一切完成之后可以进入最后的测试,目标是 30 RPS

所有测试结束之后,关闭除了 load generator 的其他虚拟机,然后上传代码,包括 /home/ubuntu/Project22 下的所有 java 文件和 references 文件。上传完毕之后可以关闭 load generator

提示

可以用 [dns of a virtual machine]/lookup/random 作为健康检查的链接

在 pom.xml 中配置好

<build>
        <sourceDirectory></sourceDirectory>
        <outputDirectory></outputDirectory>
        ....
<build>

就可以编译同一层级的文件了,注意要把 run 文件改为

#!/bin/bash
rm *.class
mvn compile && sudo mvn exec:java -Dexec.mainClass="Main"

性能优化考虑:

  1. 去掉测试用的输出语句,会比较影响性能
  2. Health Check 测试中会挂掉其中两台 DC,其中间隔 6 分钟左右(第 6 分钟,第 12 分钟),注意可能出现的异常情况
  3. 一旦发送请求给挂了的机器,因为需要等待 Timeout,所以会占用很多时间,但是其实很难避免发送请求给已挂的机器,除非增加健康检查的间隔,但是增加间隔本身也会
  4. 每次访问实际上会调用两次 start() 函数,注意这里可能会导致一些问题;网络访问有比较多需要处理的异常,注意保证逻辑的一致性
  5. 选择下一个的时候性能太差,可能的话改动一下,应该一开始的 RPS 有 50 多
  6. 需要开新线程,new Thread(new Runnable).start()
  7. 新开的机器各种不稳定,需要等待一段时间再进行查询

AWS Bonus

在 AWS 上完成前面的三个任务,这里不再重复任务描述。

相关文章