PART1.负载均衡的一些概念、分类
- 负载均衡:【
负载均衡最主要的作用就是将巨大的请求量平摊到集群中不同的节点上,从而达到系统的高可用HA
】- 当我们的一个服务节点无法支撑现有的访问量时,我们会部署多个节点,组成一个集群,然后通过负载均衡,将请求分发给这个集群下的每个服务节点【咱们的的负载均衡主要还是应用在 Web 服务上,
Web 服务的域名绑定负载均衡的地址,通过负载均衡将用户的请求分发到一个个后端服务上
。】,从而达到多个服务节点共同分担请求压力
的目的。 - 负载均衡的几个角度的分类:
- 负载均衡分为软负载和硬负载
- 软负载就是在一台或多台服务器上安装负载均衡的软件,如 LVS、Nginx 等
- 硬负载就是通过硬件设备来实现的负载均衡,如 F5 服务器等
- 按照客户端或者服务端分类:客户端负载均衡和服务端负载均衡最大的区别在于 服务端地址列表的存储位置,以及负载算法在哪里。
- 基于客户端的负载均衡:服务列表在客户端【我知道我服务端有多少个服务,根据自己的情况做负载。】
- Ribbon就是。
- 服务端的负载均衡:(集中式),在客户端和服务端之间使用中间代理
- 基于客户端的负载均衡:服务列表在客户端【我知道我服务端有多少个服务,根据自己的情况做负载。】
- 按照集中式或者进程式:
- 集中式:
- 在服务消费方和服务提供方之间使用
独立
的LB设备 - Nginx、LVS【LVS:这哥们其实也是来搞负载均衡的,假如这个小区原来的人数用网的时间分为3段,早上A类(老人)人群用,中午下午B类人群(上夜班的人)用,晚上C类人群(夜猫子)用,原来的传统做法是各拉各的网线,然后用就行。LVS出现了后,你们不要单独买网,都从我这里来买,然后我就可以按照时间段,早上把原来3倍的网速给A类用,中午给B类用,晚上给C类用,成本没变,但是你们网速高了很多】
- 在服务消费方和服务提供方之间使用
- 进程式:
将LB逻辑集成在消费方
,消费方从服务注册中心拉去服务地址列表,然后依据LB逻辑从地址列表中选出一个合适的服务器为自己提供服务- Ribbon就是
- 集中式:
- 负载均衡分为软负载和硬负载
- 常见负载均衡的算法主要有随机法、轮询法、最小连接法等。
- 当我们的一个服务节点无法支撑现有的访问量时,我们会部署多个节点,组成一个集群,然后通过负载均衡,将请求分发给这个集群下的每个服务节点【咱们的的负载均衡主要还是应用在 Web 服务上,
PART2.常见负载均衡中间件:
- Ribbon:
- Ribbon 是 Netflix 公司的一个开源的负载均衡项目,是一个客户端/进程内负载均衡器,运行在消费者端【
Ribbon 是运行在消费者端的负载均衡器,工作原理就是 Consumer 端获取到了所有的服务列表之后,在其内部使用负载均衡算法【在 Ribbon 中是先在客户端进行负载均衡才进行请求的。】,进行对多个系统的调用。
】。【Ribbon是Netflix发布的云中间层服务开源项目,主要是用来提供客户端实现负载均衡算法,ribbon就像是一个客户端负载均衡器】- Ribbon工作时分为两步:或者说Ribbon的工作原理或者工作过程:
- 第一步选择Server,它
优先选择在同一个Zone且负载较少
的Server- 拿到服务列表之后,我客户端A怎么知道众多服务中我该调用哪一个呢?所以得通过负载均衡得到服务列表【IP+Port】,就要开始调用了。发起服务调用:Netflix Feign。【至于拿啥,你翻一下源码,看他返回的实体类中含哪些属性不就行了,想拿啥拿啥】
- 第二步再根据用户指定的策略,再从Server取到的服务注册列表中选择一个地址
- 一般像Ribbon、Dubbo这种内涵注册与发现机制的,都会提供几种负载均衡策略并且也会有自己的默认负载均衡机制【Ribbon的默认机制是轮询】
- 第一步选择Server,它
- Ribbon内的负载均衡算法或者负载均衡策略:不管 Nginx 还是 Ribbon 都需要其算法的支持,
Nginx 使用的是 轮询和加权轮询算法
。而在 Ribbon 中有更多的负载均衡调度算法,其默认是使用的 RoundRobinRule 轮询策略
。Ribbon 的几种负载均衡算法:- Ribbon一共提供了以下7种负载均衡策略:
//Ribbon中的负载均衡使用:加一个@LoadBalane,同时在application.xml中加上这些配置代码 ... springcloud-nacos-provider: #nacos中的服务id ribbon: NFLoadBalancerRuleClassName: com.aiminhu.loadbalancer.XxxXxxxx #设置负载均衡,后面的XxxXxxxx是对应的七种负载均衡的大驼峰英文名字,用哪个配置中写哪个就行 ...
- RoundRobinRule:
轮询策略【默认】
。Ribbon 默认采用的策略。若经过一轮轮询没有找到可用的 provider,其最多轮询 10 轮。若最终还没有找到,则返回 null
。- 这次调这个服务,下次调那个,轮流调用
- Ribbon中轮询的源码,其实不可怕【从某个节点中拿服务出来:ServiceInstance instance = xx.choose(“xxx”);给客户端返回对象:restTemplate.getForObject();】
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package com.netflix.loadbalancer; import com.netflix.client.config.IClientConfig; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class RoundRobinRule extends AbstractLoadBalancerRule { private AtomicInteger nextServerCyclicCounter; private static final boolean AVAILABLE_ONLY_SERVERS = true; private static final boolean ALL_SERVERS = false; private static Logger log = LoggerFactory.getLogger(RoundRobinRule.class); public RoundRobinRule() { this.nextServerCyclicCounter = new AtomicInteger(0); } public RoundRobinRule(ILoadBalancer lb) { this(); this.setLoadBalancer(lb); } public Server choose(ILoadBalancer lb, Object key) { if (lb == null) { log.warn("no load balancer"); return null; } else { Server server = null; int count = 0; while(true) { if (server == null && count++ < 10) { List<Server> reachableServers = lb.getReachableServers(); List<Server> allServers = lb.getAllServers(); int upCount = reachableServers.size(); int serverCount = allServers.size(); if (upCount != 0 && serverCount != 0) { int nextServerIndex = this.incrementAndGetModulo(serverCount); server = (Server)allServers.get(nextServerIndex); if (server == null) { Thread.yield(); } else { if (server.isAlive() && server.isReadyToServe()) { return server; } server = null; } continue; } log.warn("No up servers available from load balancer: " + lb); return null; } if (count >= 10) { log.warn("No available alive servers after 10 tries from load balancer: " + lb); } return server; } } } private int incrementAndGetModulo(int modulo) { int current; int next; do { current = this.nextServerCyclicCounter.get(); next = (current + 1) % modulo; } while(!this.nextServerCyclicCounter.compareAndSet(current, next)); return next; } public Server choose(Object key) { return this.choose(this.getLoadBalancer(), key); } public void initWithNiwsConfig(IClientConfig clientConfig) { } }
- 按权重调用策略:WeightedResponseTimeRule,会根据每个服务提供者的响应时间给分配一个权重【你响应时间越长证明你这个服务提供者越菜,那你的 权重就越小,被选中的可能性就越小】,实现原理就是使用默认的轮询策略时开启一个计时器,每隔一段时间收集一次所有服务提供者的平均响应时间,然后再给每个服务者附上一个权重,以供后续选择
- RandomRule: 随机策略,瞎选一个用,
从所有可用的 provider 中随机选择一个
。 - 最小连接数(并发数)策略:BestAvailableRule,遍历服务提供者列表找连接数最小的一个服务实例,连接数相同则轮询
- RetryRule: 重试策略。
先按照 RoundRobinRule 策略获取 provider,若获取失败,则在指定的时限内重试。默认的时限为 500 毫秒
。- 重试策略:RetryRule,先轮询,如果得到的服务实例为null或已经失效,在指定时间内不断重试来获取服务,超时还没得到服务实例返回null
- 可用性敏感策略:AvaliabilityFilteringRule,先过滤掉非健康的服务实例再选择连接数最小的服务实例
- Ribbon还会帮咱们过滤掉已经down的服务或者说节点【有的服务调用不了了,调了几次之后就不跟着坏了的较劲了,得依靠客户端的负载均衡Ribbon【我知道我服务端有多少个服务】先过滤一下,通过Ribbon发现调你你不好使,那下次就不调用你了】
- 区域敏感策略:ZoneAvoidanceRule,根据服务所在的区域的性能和服务的可用性来选择服务实例,不存在区域时和轮询差不多
- RoundRobinRule:
- 自己实现:【在 Ribbon 中只需要实现 IRule 接口,然后修改配置文件或者自定义 Java Config 类,就可以自己定义负载均衡算法】
- 这个IRule有很多实现类,不就对应上面七种嘛
- 比如咱们就自定义一个,先拿到所有服务,然后取第一个来用这种策略
public class MyBalanceRule extends AbstractLoadBalancerRule { public MyBalanceRule() { } @Override public void initWithNiwsConfig(IClientConfig clientConfig) { } /* 在该方法里实现负载均衡 */ @Override public Server choose(Object key) { List<Server> allServers = getLoadBalancer().getAllServers(); // 简单粗暴的负载均衡策略 return allServers.get(0); } }
- 这个IRule有很多实现类,不就对应上面七种嘛
- Ribbon一共提供了以下7种负载均衡策略:
- Ribbon工作时分为两步:或者说Ribbon的工作原理或者工作过程:
- Ribbon 是 Netflix 公司的一个开源的负载均衡项目,是一个客户端/进程内负载均衡器,运行在消费者端【
- Nginx:
- 是一种集中式【
Nginx 是接收了所有的请求【在 Nginx 中请求是先进入负载均衡器】,将所有请求都集中起来,然后再进行负载均衡
】的负载均衡器。
- 是一种集中式【
- Feign【restTemplate面向URL编程,要自己拼接处URL,Feigh是面向接口编程,好懂而已】
- Ribbon中玩的是RestTemplate拼接大法,RestTemplate每次用的时候都要 return restTemplate.postForObject(url, request, Boolean.class);返回url、请求、返回类型的。这样很不方便,所以我们就用映射,
就像域名和 IP 地址的映射。我们可以将被调用的服务代码映射到消费者端
。OpenFeign 也是运行在消费者端的,使用 Ribbon 进行负载均衡,所以 OpenFeign 直接内置了 Ribbon。【Feign本身算是对RestTemplate的一层包装,搞出API,让我们更方便使用RestTemplate。】
- Feign也是一个 Http 请求调用的轻量级框架,可以
以 Java 接口注解【只需要创建一个接口并添加一个注解即可使用Feign,,类似于之前咱们给Dao层接口上标注Mapper注解,而现在是一个微服务接口上标注一个Feign注解即可】的方式调用 Http 请求实现调用远程服务就像调用本地服务一样简单,而不用像 Java 中通过封装 HTTP 请求报文的方式直接调用
。通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的请求,这种请求相对而言比较直观。Feign 封装 了HTTP 调用流程,面向接口编程 - Ribbon那里,咱们一般是用微服务的名字,借助RestTemplate这个类的getForObject这些方法(传入咱们拼接而成的URL等参数)去通过服务名或者URL访问
- Feign也是一个 Http 请求调用的轻量级框架,可以
- 在导入了 Open Feign 之后我们就可以进行愉快编写 Consumer 端代码了,然后我们在 Controller 就可以像原来调用 Service 层代码一样调用它了。
- Ribbon中玩的是RestTemplate拼接大法,RestTemplate每次用的时候都要 return restTemplate.postForObject(url, request, Boolean.class);返回url、请求、返回类型的。这样很不方便,所以我们就用映射,
巨人的肩膀:
凤凰架构~大佬的书,跟深入理解JVM一样值得多次翻阅
B站的各位大佬
JavaGuide
SpringCloud官方文档\中文文档【https://docs.gitcode.net/spring/guide/spring-cloud/spring-cloud-openfeign.html】
Dubbo官方文档