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

正文

 

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

  在计算机的世界,这就是大家耳熟能详的负载均衡(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,一经查实,立即删除!

相关文章

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

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

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;离不开各位的帮助和加油。 课程…

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;…

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

主题是拥抱开源 拥抱开源&#xff1a;开放是互联终端不断增长的主旨和核心——使技术世界变得越来越复杂&#xff0c;联系越来越紧密。在微软&#xff0c;我们专注于向客户提供任何信息支持&#xff0c;即使在任何设备或者在多个平台上。 未来是开放的&#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;涉及云计算、数据处理、开发工具、商用软件、物联网、人工智能等前沿科技。 系列活动邀请微软技术专家、一线开发者、成功创业者、企业家等进行技术和经验分享。让您直通微软新…

MyBatis中的RowBounds

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

使用 MQTTnet 快速实现 MQTT 通信

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

git代码合并冲突与撤回提交

查看版本 切回到某一个版本 git log --graph --abbrev-commit --decorate --prettyoneline git reset --hard a07cefe git有一种情况会造成代码被冲掉&#xff1a; 这里有A端和B端&#xff1a; 相同文本基础之上 A端写了大量代码&#xff0c;提交推送 B端拉取&#xff0c;改了代…

Asp.net Core中SignalR Core预览版的一些新特性前瞻,附源码(消息订阅与发送二进制数据)

前言 一晃一个月又过去了,上个月有个比较大的项目要验收上线.所以忙的脚不沾地.现在终于可以忙里偷闲,写一篇关于SignalR Core的文章了. 先介绍一下SignalR吧,如下: ASP.NET SignalR是ASP.NET开发人员的一个库&#xff0c;它简化了向Web应用程序添加即时通讯功能的过程。 它可以…

大二暑假工作三个月后辞职,总体感悟

本人是个大二的学生&#xff0c;因为疫情影响&#xff0c;学校放了很长时间的假。当时对自己的前端技术很自信&#xff0c;大概在五月底的时候决定去上海闯一下&#xff0c;找个工作&#xff0c;不管能不能找到&#xff0c;就是尝试一下&#xff0c;失败了也没关系&#xff0c;…

35年编程史沉淀下来的8条宝贵经

01 1. 时刻提醒自己&#xff1a;学习 学习某件事的第一步是承认你不知道。这听起来很正常&#xff0c;但经验丰富的程序员还记得要真正让自己承认这一点需要花多长时间。很多计算机科学专业的学生毕业的时候&#xff0c;都有一种很傲慢的态度&#xff0c;就是“我知道最好的”&…

基于Emgu CV+百度人脸识别,实现视频动态 人脸抓取与识别

背景 目前AI 处于风口浪尖&#xff0c;作为 公司的CTO&#xff0c;也作为自己的技术专研&#xff0c;开始了AI之旅&#xff0c;在朋友圈中也咨询 一些大牛对于AI 机器学习框架的看法&#xff0c;目前自己的研究方向主要开源的 AI 库&#xff0c;如&#xff1a;Emgu CV、TensorF…

vue-cli2、vue-cli3脚手架详细讲解

转载自 vue-cli2、vue-cli3脚手架详细讲解 前言&#xff1a; vue脚手架指的是vue-cli它是vue官方提供的一个快速构建单页面&#xff08;SPA&#xff09;环境配置的工具&#xff0c;cli 就是(command-line-interface ) 命令行界面 。vue-cli是基于node环境利用webpack对文件进…

微软为.NET程序员带来了最优的跨平台开发体验-WSL

前言 在前几个Visual Studio Code更新中发现有一个重要得特性&#xff0c;就是nodejs可以使用VS Code在WSL中进行Debug了&#xff08;WSL是指Win10中的Linux子系统&#xff09;,之前写过一篇文章是使用SSH对Linux环境进行Debug&#xff0c;此时的想法就是如果可以在WSL中直接对…

article之api文档

查 method:get http://127.0.0.1:8000/article 单条 http://127.0.0.1:8000/article/10 method:get 新增 http://127.0.0.1:8000/article method:post 修改 http://127.0.0.1:8000/article/10 method:put 删除 http://127.0.0.1:8000/article/3 method:delete ## 查询所有数据&…

Redis 性能问题分析

转载自 Redis 性能问题分析 在一些网络服务的系统中&#xff0c;Redis 的性能&#xff0c;可能是比 MySQL 等硬盘数据库的性能更重要的课题。比如微博&#xff0c;把热点微博[1]&#xff0c;最新的用户关系&#xff0c;都存储在 Redis 中&#xff0c;大量的查询击中 Redis&am…

谈谈微服务中的 API 网关(API Gateway)

前言 又是很久没写博客了&#xff0c;最近一段时间换了新工作&#xff0c;比较忙&#xff0c;所以没有抽出来太多的时间写给关注我的粉丝写一些干货了&#xff0c;就有人问我怎么最近没有更新博客了&#xff0c;在这里给大家抱歉。 那么&#xff0c;在本篇文章中&#xff0c;我…