集群高性能:负载均衡
1. 前言
单服务器无论如何优化,无论采用多好的硬件,总会有一个性能天花板,当单服务器的性能无法满足业务需求时,就需要设计高性能集群来提升系统整体的处理性能。高性能集群的本质很简单,通过增加更多的服务器来提升系统整体的计算能力。
由于计算本身存在一个特点:同样的输入数据和逻辑,无论在哪台服务器上执行,都应该得到相同的输出。因此高性能集群设计的复杂度主要体现在任务分配这部分,需要设计合理的任务分配策略,将计算任务分配到多台服务器上执行。
高性能集群的复杂性主要体现在需要增加一个任务分配器,以及为任务选择一个合适的任务分配算法。对于任务分配器,现在更流行的通用叫法是“负载均衡器”。但这个名称有一定的误导性,会让人潜意识里认为任务分配的目的是要保持各个计算单元的负载达到均衡状态。而实际上任务分配并不只是考虑计算单元的负载均衡,不同的任务分配算法目标是不一样的,有的基于负载考虑,有的基于性能(吞吐量、响应时间)考虑,有的基于业务考虑。考虑到“负载均衡”已经成为了事实上的标准术语,这里我也用“负载均衡”来代替“任务分配”,但请你时刻记住,负载均衡不只是为了计算单元的负载达到均衡状态。
2. 负载均衡分类
常见的负载均衡系统包括 3 种:DNS 负载均衡、硬件负载均衡和软件负载均衡。
1. DNS负载均衡
DNS 是最简单也是最常见的负载均衡方式,一般用来实现地理级别的均衡。例如,北方的用户访问北京的机房,南方的用户访问深圳的机房。DNS 负载均衡的本质是 DNS 解析同一个域名可以返回不同的 IP 地址。例如,同样是 www.baidu.com,北方用户解析后获取的地址是 61.135.165.224(这是北京机房的 IP),南方用户解析后获取的地址是 14.215.177.38(这是深圳机房的 IP)
DNS 负载均衡实现简单、成本低,但也存在粒度太粗、负载均衡算法少等缺点。仔细分析一下优缺点,
● 其优点有:
○ 简单、成本低:负载均衡工作交给 DNS 服务器处理,无须自己开发或者维护负载均衡设备。
○ 就近访问,提升访问速度:DNS 解析时可以根据请求来源 IP,解析成距离用户最近的服务器地址,可 以加快访问速度,改善性能。
● 缺点有:
○ 更新不及时:DNS 缓存的时间比较长,修改 DNS 配置后,由于缓存的原因,还是有很多用户会继续访问修改前的 IP,这样的访问会失败,达不到负载均衡的目的,并且也影响用户正常使用业务。
○ 扩展性差:DNS 负载均衡的控制权在域名商那里,无法根据业务特点针对其做更多的定制化功能和扩展特性。
○ 分配策略比较简单:DNS 负载均衡支持的算法少;不能区分服务器的差异(不能根据系统与服务的状态来判断负载);也无法感知后端服务器的状态。
针对 DNS 负载均衡的一些缺点,对于时延和故障敏感的业务,有一些公司自己实现了 HTTP-DNS 的功能,即使用 HTTP 协议实现一个私有的 DNS 系统。这样的方案和通用的 DNS 优缺点正好相反。
2. 硬件负载均衡
硬件负载均衡是通过单独的硬件设备来实现负载均衡功能,这类设备和路由器、交换机类似,可以理解为一个用于负载均衡的基础网络设备。目前业界典型的硬件负载均衡设备有两款:F5 和 A10。这类设备性能强劲、功能强大,但价格都不便宜,一般只有“土豪”公司才会考虑使用此类设备。普通业务量级的公司一是负担不起,二是业务量没那么大,用这些设备也是浪费。
● 硬件负载均衡的优点是:
○ 功能强大:全面支持各层级的负载均衡,支持全面的负载均衡算法,支持全局负载均衡。
○ 性能强大:对比一下,软件负载均衡支持到 10 万级并发已经很厉害了,硬件负载均衡可以支持 100 万以上的并发。
○ 稳定性高:商用硬件负载均衡,经过了良好的严格测试,经过大规模使用,稳定性高。
○ 支持安全防护:硬件均衡设备除具备负载均衡功能外,还具备防火墙、防 DDoS 攻击等安全功能。
● 硬件负载均衡的缺点是:
○ 价格昂贵:最普通的一台 F5 就是一台“马 6”,好一点的就是“Q7”了。
○ 扩展能力差:硬件设备,可以根据业务进行配置,但无法进行扩展和定制。
3. 软件负载均衡
软件负载均衡通过负载均衡软件来实现负载均衡功能,常见的有 Nginx , Nginx 是软件的 7 层负载均衡。
-
软件负载均衡的优点:
- 简单:无论是部署还是维护都比较简单。
- 便宜:只要买个 Linux 服务器,装上软件即可。
- 灵活:4 层和 7 层负载均衡可以根据业务进行选择;也可以根据业务进行比较方便的扩展,例如,可以通过 Nginx 的插件来实现业务的定制化功能。
-
其实下面的缺点都是和硬件负载均衡相比的,并不是说软件负载均衡没法用。
- 性能一般:一个 Nginx 大约能支撑 5 万并发。功能没有硬件负载均衡那么强大。一般不具备防火墙和防 DDoS 攻击等安全功能。
3.负载均衡典型架构
前面我们介绍了 3 种常见的负载均衡机制:DNS 负载均衡、硬件负载均衡、软件负载均衡,每种方式都有一些优缺点,但并不意味着在实际应用中只能基于它们的优缺点进行非此即彼的选择,反而是基于它们的优缺点进行组合使用。* *具体来说,组合的基本原则为:DNS 负载均衡用于实现地理级别的负载均衡;硬件负载均衡用于实现集群级别的负载均衡;软件负载均衡用于实现机器级别的负载均衡。**我以一个假想的实例来说明一下这种组合方式,如下图所示。
整个系统的负载均衡分为三层。
- 地理级别负载均衡:www.xxx.com 部署在北京、广州、上海三个机房,当用户访问时,DNS 会根据用户的地理位置来决定返回哪个机房的 IP,图中返回了广州机房的 IP 地址,这样用户就访问到广州机房了。
- 集群级别负载均衡:广州机房的负载均衡用的是 F5 设备,F5 收到用户请求后,进行集群级别的负载均衡,将用户请求发给 3 个本地集群中的一个,我们假设 F5 将用户请求发给了“广州集群 2”。
- 机器级别的负载均衡:广州集群 2 的负载均衡用的是 Nginx,Nginx 收到用户请求后,将用户请求发送给集群里面的某台服务器,服务器处理用户的业务请求并返回业务响应。需要注意的是,上图只是一个示例,一般在大型业务场景下才会这样用,如果业务量没这么大,则没有必要严格照搬这套架构。例如,一个大学的论坛,完全可以不需要 DNS 负载均衡,也不需要 F5 设备,只需要用 Nginx 作为一个简单的负载均衡就足够了。
需要注意的是,上图只是一个示例,一般在大型业务场景下才会这样用,如果业务量没这么大,则没有必要严格照搬这套架构。例如,一个大学的论坛,完全可以不需要 DNS 负载均衡,也不需要 F5 设备,只需要用 Nginx 作为一个简单的负载均衡就足够了。
4.场景设计题
假设你来设计一个日活跃用户 1000 万的论坛的负载均衡集群,你的方案是什么?设计理由是什么?
1、首先,流量评估。
1000万DAU,换算成秒级,平均约等于116。
考虑每个用户操作次数,假定10,换算成平均QPS=1160。
考虑峰值是均值倍数,假定10,换算成峰值QPS=11600。
考虑静态资源、图片资源、服务拆分等,流量放大效应,假定10,QPS*10=116000。
2、其次,容量规划。
考虑高可用、异地多活,QPS*2=232000。
考虑未来半年增长,QPS*1.5=348000。
3、最后,方案设计。
三级导流。
第一级,DNS,确定机房,以目前量级,可以不考虑。
第二级,确定集群,扩展优先,则选Haproxy/LVS,稳定优先则选F5。
第三级,Nginx+KeepAlived,确定实例。
5.负载均衡算法
负载均衡算法数量较多,而且可以根据一些业务特性进行定制开发,抛开细节上的差异,根据算法期望达到的目的,大体上可以分为下面几类。
-
任务平分类:负载均衡系统将收到的任务平均分配给服务器进行处理,这里的“平均”可以是绝对数量的平均,也可以是比例或者权重上的平均。
-
负载均衡类:负载均衡系统根据服务器的负载来进行分配,这里的负载并不一定是通常意义上我们说的“CPU 负载”,而是系统当前的压力,可以用 CPU 负载来衡量,也可以用连接数、I/O 使用率、网卡吞吐量等来衡量系统的压力。
-
性能最优类:负载均衡系统根据服务器的响应时间来进行任务分配,优先将新任务分配给响应最快的服务器。
-
Hash 类:负载均衡系统根据任务中的某些关键信息进行 Hash 运算,将相同 Hash 值的请求分配到同一台服务器上。常见的有源地址 Hash、目标地址 Hash、session id hash、用户 ID Hash 等。
接下来我介绍一下负载均衡算法以及它们的优缺点。
1.轮询
负载均衡系统收到请求后,按照顺序轮流分配到服务器上。
轮询是最简单的一个策略,无须关注服务器本身的状态,例如:
- 某个服务器当前因为触发了程序 bug 进入了死循环导致 CPU 负载很高,负载均衡系统是不感知的,还是会继续将请求源源不断地发送给它。
- 集群中有新的机器是 32 核的,老的机器是 16 核的,负载均衡系统也是不关注的,新老机器分配的任务数是一样的。
需要注意的是负载均衡系统无须关注“服务器本身状态”,这里的关键词是“本身”。也就是说,只要服务器在运行,运行状态是不关注的。但如果服务器直接宕机了,或者服务器和负载均衡系统断连了,这时负载均衡系统是能够感知的,也需要做出相应的处理。例如,将服务器从可分配服务器列表中删除,否则就会出现服务器都宕机了,任务还不断地分配给它,这明显是不合理的。总而言之,“简单”是轮询算法的优点,也是它的缺点。
2.加权轮询
负载均衡系统根据服务器权重进行任务分配,这里的权重一般是根据硬件配置进行静态配置的,采用动态的方式计算会更加契合业务,但复杂度也会更高。
加权轮询是轮询的一种特殊形式,其主要目的就是为了解决不同服务器处理能力有差异的问题。例如,集群中有新的机器是 32 核的,老的机器是 16 核的,那么理论上我们可以假设新机器的处理能力是老机器的 2 倍,负载均衡系统就可以按照 2:1 的比例分配更多的任务给新机器,从而充分利用新机器的性能。
加权轮询解决了轮询算法中无法根据服务器的配置差异进行任务分配的问题,但同样存在无法根据服务器的状态差异进行任务分配的问题。
3.一致性Hash
负载均衡系统根据任务中的某些关键信息进行 Hash 运算,将相同 Hash 值的请求分配到同一台服务器上,这样做的目的主要是为了满足特定的业务需求。例如:
-
源地址 Hash
根据请求的来源IP地址进行散列计算,确保来自同一来源的请求被分配到同一台服务器。
将来源于同一个源 IP 地址的任务分配给同一个服务器进行处理,适合于存在事务、会话的业务。例如,当我们通过浏览器登录网上银行时,会生成一个会话信息,这个会话是临时的,关闭浏览器后就失效。网上银行后台无须持久化会话信息,只需要在某台服务器上临时保存这个会话就可以了,但需要保证用户在会话存在期间,每次都能访问到同一个服务器,这种业务场景就可以用源地址 Hash 来实现。
-
ID Hash
将某个 ID 标识的业务分配到同一个服务器中进行处理,这里的 ID 一般是临时性数据的 ID(如 session id),从而保证数据的一致性和缓存的有效性。例如,在分布式缓存集群中,为了保证缓存的高命中率,通常需要将相同ID的数据存储在同一节点上。
6.微信抢红包的高并发架构,应该采取什么样的负载均衡算法?
有三个基本动作:发红包,抢红包,拆红包。发红包就是在数据库插一条红包库存数据,抢红包就是查询红包库存数据,拆红包就是插入一条秒杀流水并更新库存数据。
有三个难点:一是海量的并发,二是资金安全,三是良好的用户体验。资金安全决定了交易只能直接建立在数据库上,不能建立在缓存上。良好的用户体验就是要求不能出现不公平的现象,保证先抢先得,先抢的不能失败。
解决方案是:
1.分而治之,分成很多个并行的服务和数据库。相同的红包id总是分到相同的服务和数据库。所以负载均衡算法应该是hash算法
2.相同红包id的所有请求放在一个先进先出队列。然后分配一个独立的线程/进程处理。杜绝抢锁。
3.分离过期数据,减少单表数据量