关于负载均衡的一切:总结与思考

正文

 

  古人云,不患寡而患不均。

  在计算机的世界,这就是大家耳熟能详的负载均衡(load balancing),所谓负载均衡,就是说如果一组计算机节点(或者一组进程)提供相同的(同质的)服务,那么对服务的请求就应该均匀的分摊到这些节点上。负载均衡的前提一定是“provide a single Internet service from multiple servers”, 这些提供服务的节点被称之为server farm、server pool或者backend servers。

  这里的服务是广义的,可以是简单的计算,也可能是数据的读取或者存储。负载均衡也不是新事物,这种思想在多核CPU时代就有了,只不过在分布式系统中,负载均衡更是无处不在,这是分布式系统的天然特性决定的,分布式就是利用大量计算机节点完成单个计算机无法完成的计算、存储服务,既然有大量计算机节点,那么均衡的调度就非常重要。

  负载均衡的意义在于,让所有节点以最小的代价、最好的状态对外提供服务,这样系统吞吐量最大,性能更高,对于用户而言请求的时间也更小。而且,负载均衡增强了系统的可靠性,最大化降低了单个节点过载、甚至crash的概率。不难想象,如果一个系统绝大部分请求都落在同一个节点上,那么这些请求响应时间都很慢,而且万一节点降级或者崩溃,那么所有请求又会转移到下一个节点,造成雪崩。

  事实上,网上有很多文章介绍负载均衡的算法,大多都是大同小异。本文更多的是自己对这些算法的总结与思考。

 


一分钟了解负载均衡的一切


  本章节的标题和内容都来自一分钟了解负载均衡的一切这一篇文章。当然,原文的标题是夸张了点,不过文中列出了在一个大型web网站中各层是如何用到负载均衡的,一目了然。

  

  常见互联网分布式架构如上,分为客户端层、反向代理nginx层、站点层、服务层、数据层。可以看到,每一个下游都有多个上游调用,只需要做到,每一个上游都均匀访问每一个下游,就能实现“将请求/数据【均匀】分摊到多个操作单元上执行”。

  (1)【客户端层】到【反向代理层】的负载均衡,是通过“DNS轮询”实现的
  (2)【反向代理层】到【站点层】的负载均衡,是通过“nginx”实现的
  (3)【站点层】到【服务层】的负载均衡,是通过“服务连接池”实现的
  (4)【数据层】的负载均衡,要考虑“数据的均衡”与“请求的均衡”两个点,常见的方式有“按照范围水平切分”与“hash水平切分”。

  数据层的负载均衡,在我之前的《带着问题学习分布式系统之数据分片》中有详细介绍。


算法衡量

  在我看来,当我们提到一个负载均衡算法,或者具体的应用场景时,应该考虑以下问题

  第一,是否意识到不同节点的服务能力是不一样的,比如CPU、内存、网络、地理位置

  第二,是否意识到节点的服务能力是动态变化的,高配的机器也有可能由于一些突发原因导致处理速度变得很慢

  第三,是否考虑将同一个客户端,或者说同样的请求分发到同一个处理节点,这对于“有状态”的服务非常重要,比如session,比如分布式存储

  第四,谁来负责负载均衡,即谁充当负载均衡器(load balancer),balancer本身是否会成为瓶颈

  下面会结合具体的算法来考虑这些问题



负载均衡算法



轮询算法(round-robin)

  思想很简单,就是提供同质服务的节点逐个对外提供服务,这样能做到绝对的均衡。Python示例代码如下


 1 SERVER_LIST = [
2 '10.246.10.1',
3 '10.246.10.2',
4 '10.246.10.3',
5 ]
6 def round_robin(server_lst, cur = [0]):
7 length = len(server_lst)
8 ret = server_lst[cur[0] % length]
9 cur[0] = (cur[0] + 1) % length
10 return ret


  可以看到,所有的节点都是以同样的概率提供服务,即没有考虑到节点的差异,也许同样数目的请求,高配的机器CPU才20%,低配的机器CPU已经80%了


加权轮询算法(weight round-robin)

  加权轮训算法就是在轮训算法的基础上,考虑到机器的差异性,分配给机器不同的权重,能者多劳。注意,这个权重的分配依赖于请求的类型,比如计算密集型,那就考虑CPU、内存;如果是IO密集型,那就考虑磁盘性能。Python示例代码如下


 1 WEIGHT_SERVER_LIST = { 
2 '10.246.10.1': 1,
3 '10.246.10.2': 3,
4 '10.246.10.3': 2,
5 }
6
7 def weight_round_robin(servers, cur = [0]):
8 weighted_list = []
9 for k, v in servers.iteritems():
10 weighted_list.extend([k] * v)
11
12 length = len(weighted_list)
13 ret = weighted_list[cur[0] % length]
14 cur[0] = (cur[0] + 1) % length
15 return ret


 


随机算法(random)

  这个就更好理解了,随机选择一个节点服务,按照概率,只要请求数量足够多,那么也能达到绝对均衡的效果。而且实现简单很多

1 def random_choose(server_lst):
2 import random
3    random.seed()
4 return random.choice(server_lst)

 


加权随机算法(random)

  如同加权轮训算法至于轮训算法一样,也是在随机的时候引入不同节点的权重,实现也很类似。


def weight_random_choose(servers):   
 import randomrandom.seed()weighted_list = []  
   for k, v in servers.iteritems():weighted_list.extend([k] * v)  
   return random.choice(weighted_list)


 

  当然,如果节点列表以及权重变化不大,那么也可以对所有节点归一化,然后按概率区间选择


 1 def normalize_servers(servers): 
2 normalized_servers = {}
3 total = sum(servers.values())
4 cur_sum = 0
5 for k, v in servers.iteritems():
6 normalized_servers[k] = 1.0 * (cur_sum + v) / total
7 cur_sum += v
8 return normalized_servers
9
10 def weight_random_choose_ex(normalized_servers):
11 import random, operator
12    random.seed()
13 rand = random.random()
14 for k, v in sorted(normalized_servers.iteritems(), key = operator.itemgetter(1)):
15 if v >= rand:
16 return k
17 else:
18 assert False, 'Error normalized_servers with rand %s ' % rand


 


哈希法(hash)

  根据客户端的IP,或者请求的“Key”,计算出一个hash值,然后对节点数目取模。好处就是,同一个请求能够分配到同样的服务节点,这对于“有状态”的服务很有必要

1 def hash_choose(request_info, server_lst):
2 hashed_request_info = hash(request_info)
3 return server_lst[hashed_request_info % len(server_lst)]

  只要hash结果足够分散,也是能做到绝对均衡的。


一致性哈希

  哈希算法的缺陷也很明显,当节点的数目发生变化的时候,请求会大概率分配到其他的节点,引发到一系列问题,比如sticky session。而且在某些情况,比如分布式存储,是绝对的不允许的。

  为了解决这个哈希算法的问题,又引入了一致性哈希算法,简单来说,一个物理节点与多个虚拟节点映射,在hash的时候,使用虚拟节点数目而不是物理节点数目。当物理节点变化的时候,虚拟节点的数目无需变化,只涉及到虚拟节点的重新分配。而且,调整每个物理节点对应的虚拟节点数目,也就相当于每个物理节点有不同的权重


最少连接算法(least connection)

  以上的诸多算法,要么没有考虑到节点间的差异(轮训、随机、哈希),要么节点间的权重是静态分配的(加权轮训、加权随机、一致性hash)。

  考虑这么一种情况,某台机器出现故障,无法及时处理请求,但新的请求还是会以一定的概率源源不断的分配到这个节点,造成请求的积压。因此,根据节点的真实负载,动态地调整节点的权重就非常重要。当然,要获得接节点的真实负载也不是一概而论的事情,如何定义负载,负载的收集是否及时,这都是需要考虑的问题。

  每个节点当前的连接数目是一个非常容易收集的指标,因此lease connection是最常被人提到的算法。也有一些侧重不同或者更复杂、更客观的指标,比如最小响应时间(least response time)、最小活跃数(least active)等等。


一点思考


有状态的请求  

  首先来看看“算法衡量”中提到的第三个问题:同一个请求是否分发到同样的服务节点,同一个请求指的是同一个用户或者同样的唯一标示。什么时候同一请求最好(必须)分发到同样的服务节点呢?那就是有状态 -- 请求依赖某些存在于内存或者磁盘的数据,比如web请求的session,比如分布式存储。怎么实现呢,有以下几种办法:

  (1)请求分发的时候,保证同一个请求分发到同样的服务节点。

  这个依赖于负载均衡算法,比如简单的轮训,随机肯定是不行的,哈希法在节点增删的时候也会失效。可行的是一致性hash,以及分布式存储中的按范围分段(即记录哪些请求由哪个服务节点提供服务),代价是需要在load balancer中维护额外的数据。

  (2)状态数据在backend servers之间共享

  保证同一个请求分发到同样的服务节点,这个只是手段,目的是请求能使用到对应的状态数据。如果状态数据能够在服务节点之间共享,那么也能达到这个目的。比如服务节点连接到共享数据库,或者内存数据库如memcached

  (3)状态数据维护在客户端

  这个在web请求中也有使用,即cookie,不过要考虑安全性,需要加密。

 


 关于load balancer

  接下来回答第四个问题:关于load balancer,其实就是说,在哪里做负载均衡,是客户端还是服务端,是请求的发起者还是请求的3。具体而言,要么是在客户端,根据服务节点的信息自行选择,然后将请求直接发送到选中的服务节点;要么是在服务节点集群之前放一个集中式代理(proxy),由代理负责请求求分发。不管哪一种,至少都需要知道当前的服务节点列表这一基础信息。

  如果在客户端实现负载均衡,客户端首先得知道服务器列表,要么是静态配置,要么有简单接口查询,但backend server的详细负载信息,就不适用通过客户端来查询。因此,客户端的负载均衡算法要么是比较简单的,比如轮训(加权轮训)、随机(加权随机)、哈希这几种算法,只要每个客户端足够随机,按照大数定理,服务节点的负载也是均衡的。要在客户端使用较为复杂的算法,比如根据backend的实际负载,那么就需要去额外的负载均衡服务(external load balancing service)查询到这些信息,在grpc中,就是使用的这种办法

  

  可以看到,load balancer与grpc server通信,获得grpc server的负载等具体详细,然后grpc client从load balancer获取这些信息,最终grpc client直连到被选择的grpc server。

  而基于Proxy的方式是更为常见的,比如7层的Nginx,四层的F5、LVS,既有硬件路由,也有软件分发。集中式的特点在于方便控制,而且能容易实现一些更精密,更复杂的算法。但缺点也很明显,一来负载均衡器本身可能成为性能瓶颈;二来可能引入额外的延迟,请求一定先发到达负载均衡器,然后到达真正的服务节点。

  load balance proxy对于请求的响应(response),要么不经过proxy,如LVS;要么经过Proxy,如Nginx。下图是LVS示意图(来源见水印)

  

  而如果response也是走load balancer proxy的话,那么整个服务过程对客户端而言就是完全透明的,也防止了客户端去尝试连接后台服务器,提供了一层安全保障!

  值得注意的是,load balancer proxy不能成为单点故障(single point of failure),因此一般会设计为高可用的主从结构


 其他

  在这篇文章中提到,负载均衡是一种推模型,一定会选出一个服务节点,然后把请求推送过来。而换一种思路,使用消息队列,就变成了拉模型:空闲的服务节点主动去拉取请求进行处理,各个节点的负载自然也是均衡的。消息队列相比负载均衡好处在于,服务节点不会被大量请求冲垮,同时增加服务节点更加容易;缺点也很明显,请求不是事实处理的。

 

  想到另外一个例子,比如在gunicorn这种pre-fork模型中,master(gunicorn 中Arbiter)会fork出指定数量的worker进程,worker进程在同样的端口上监听,谁先监听到网络连接请求,谁就提供服务,这也是worker进程之间的负载均衡。


原文:http://www.cnblogs.com/xybaby/p/7867735.html


.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/322698.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Vue父组件使用子组件时,需要携带参数,函数内如何获取子组件给的值

在参数中 通过$event获取&#xff1a; 子组件 this.$emit(visible-change, val)父组件 <en-category-picker visible-change"changeVisiable($event,scope.$index, scope.row)" />changeVisiable(val, _index, _row) {if (val false) {this.confirmChangeS…

Spark入门(五)Spark SQL shell启动方式(元数据存储在derby)

一、spark-sql shell介绍 Spark sql是以hive SQL提交spark任务到spark集群执行。 由于spark是计算框架没有存储功能&#xff0c;所有spark sql数据表映射关系存储在运行shell的当前目录下metastore_db目录里面&#xff08;spark默认使用derby数据库创建的本地存储&#xff0c…

jzoj4743-积木【状压dp】

正题 题目大意 有长方体积木nnn块&#xff0c;求能搭成的最高高度。 解题思路 考虑状态压缩&#xff0c;设fs,i,jf_{s,i,j}fs,i,j​表示积木使用状态为sss&#xff0c;最下面的是第iii块&#xff0c;状态为jjj&#xff1a; 状态&#xff1a;对于一个长方体a,b,ca,b,ca,b,c a…

Spark入门(六)Spark SQL shell启动方式(元数据存储在mysql)

一、hive配置文件 在spak/conf目录添加hive-site.xml配置&#xff0c;设置mysql作为元数据存储的数据库 <?xml version"1.0" encoding"UTF-8" standalone"no"?> <?xml-stylesheet type"text/xsl" href"configurat…

Asp.NET Core2.0 项目实战入门视频课程_完整版

END OR START? 看到这个标题&#xff0c;你开不开心&#xff0c;激不激动呢&#xff1f; 没错&#xff0c;.net core的入门课程已经完毕了。52ABP.School项目从11月19日&#xff0c;第一章视频的试录制&#xff0c;到今天完整版出炉&#xff0c;离不开各位的帮助和加油。 课程…

Js中等于号使用

判断必须用 多等于 赋值用 切记 意思是值相等 是值和类型都相等

jzoj4745-看电影【期望概率,数学】

正题 题目大意 一个环&#xff0c;nnn个人。 每次随机选一个位置&#xff0c;把顺时针第一个踢掉。直到只剩下kkk个人&#xff0c;求第ididid个人留下的期望概率。 解题思路 首先我们考虑一个环&#xff0c;若第xxx的概率比第yyy号的概率打&#xff0c;那么将环旋转到使得yyy…

VS Tools for AI全攻略(2)低配置虚拟机也能玩转深度学习,无需NC/NV系列

接着上文VS Tools for AI全攻略&#xff0c;我们来讨论如何使用Azure资源来训练我们的tensorflow项目。Azure云我个人用得很多&#xff0c;主要是因为微软爸爸批了150刀每月的额度&#xff0c;我可以愉快地玩耍。 那么针对Azure&#xff0c;有成套的两个方案解决问题。 方案一&…

Spark入门(七)Spark SQL thriftserver/beeline启动方式

一、启动thrift服务 启动thriftServer&#xff0c;默认端口为10000,。 --jars 添加worker类库 --driver-class-path 驱动类库 --master spark集群地址 --total-executor-cores 启动的核数&#xff0c;默认是所有核数 --executor-memory 每个work分配的内存&#xff0c;…

array关于map,reduce,filter的用法

1、.map直接还原一个数组中多个对象里面的单个属性 不需要任何push操作 let arr [{a:10,b:50},{a:20,b:60},{a:30,b:80}]; let result arr.map((cb)>{return cb.a}); console.log(result); // [10, 20, 30]2、.reduce用于数组中的元素需要做递归操作&#xff0c;比如累加…

欢乐纪中B组周六赛【2019.3.9】

前言 AKAKAK了(虽然有一堆AKAKAK的) 成绩 RankRankRank是有算别人的 今天LRZLRZLRZ不在 RankRankRankPersonPersonPersonScoreScoreScoreAAABBBCCC1112017myself2017myself2017myself3003003001001001001001001001001001002929292017zyc2017zyc2017zyc210210210100100100101010…

【青岛】12月16日.NETCore与AI技术交流会-等你来哦!!

主题是拥抱开源 拥抱开源&#xff1a;开放是互联终端不断增长的主旨和核心——使技术世界变得越来越复杂&#xff0c;联系越来越紧密。在微软&#xff0c;我们专注于向客户提供任何信息支持&#xff0c;即使在任何设备或者在多个平台上。 未来是开放的&#xff0c;未来的云是开…

MyBatis Generator分页插件RowBoundsPlugin坑

一、Mybatis Generator设置分页 Mybatis Generator没有设置分页&#xff0c;但自带了个RowBoundsPlugin分页插件&#xff0c;故而配置 <plugin type"org.mybatis.generator.plugins.RowBoundsPlugin"/> 在mysql数据库进行查询的表分页10条&#xff0c;查很久…

对于自绝对父相的理解

1、如果对子元素施加绝对定位&#xff0c;然后设置一个bottom为20 则&#xff1a; html <div class"k1"><div class"k2">1111</div></div>.k1 {margin: 0 auto;height: 500px;width: 500px;background: green;}.k2 {height: 100…

【北京】微软技术直通车(第二期) 之 SQL Server 2017饕餮

微软技术直通车 本系列活动密切关注微软及周边相关技术。以微软云计算和相关产品为依托&#xff0c;涉及云计算、数据处理、开发工具、商用软件、物联网、人工智能等前沿科技。 系列活动邀请微软技术专家、一线开发者、成功创业者、企业家等进行技术和经验分享。让您直通微软新…

P1993-小K的农场【差分约束,SPFA】

正题 题目链接:https://www.luogu.org/problemnew/show/P1993 题目大意 有若干个条件 Waw>WbW_aw>W_bWa​w>Wb​Waw<WbW_aw<W_bWa​w<Wb​WaWbW_aW_bWa​Wb​ 解题思路 差分约束不解释 codecodecode #include<cstdio> #include<queue> #incl…

MyBatis中的RowBounds

转载自 MyBatis中的RowBounds 一、如何分页查询 Mybatis如何分页查询&#xff1f;Mysql中可以使用limit语句&#xff0c;但limit并不是标准SQL中的&#xff0c;如果是其它的数据库&#xff0c;则需要使用其它语句。MyBatis提供了RowBounds类&#xff0c;用于实现分页查询。R…

vscode插件以及配置

插件&#xff1a; Auto Rename Tag indent-rainbow 好看多彩的缩进空格 Dracula Officila 吸血鬼主题 Rainbow Brackets 彩虹括号&#xff0c;强烈推荐 快捷键&#xff1a; 代码折叠&#xff1a; 1、Ctrl键 Shift键 [键 折叠 2、Ctrl键 Shift键 ]键 展开 多行选中 &#x…

使用 MQTTnet 快速实现 MQTT 通信

1 什么是 MQTT &#xff1f; MQTT&#xff08;Message Queuing Telemetry Transport&#xff0c;消息队列遥测传输&#xff09;是 IBM 开发的一个即时通讯协议&#xff0c;有可能成为物联网的重要组成部分。MQTT 是基于二进制消息的发布/订阅编程模式的消息协议&#xff0c;如今…

P1726-上白泽慧音【tarjan,图论】

正题 题目链接:https://www.luogu.org/problemnew/show/P1726 题目大意 求最大的强联通分量和包含的点 解题思路 tarjantarjantarjan模板不解释。 codecodecode #include<cstdio> #include<algorithm> #include<stack> using namespace std; const int N5…