SpringCloud中的Ribbon负载均衡
为什么要学这个?你不想面试的时候多装两个逼多拿两千块钱?
本地负载均衡,也叫客户端负载均衡,现在SpringCloud比较流行的是Ribbon和Spring LoadBanlance。关于Spring LoadBanlance的原理可以查看我的另一篇博客链接: Spring Cloud LoadBalancer负载均衡策略中的RestTemplate为什么打上@LoadBalanced注解就能以服务名访问到服务.
这里我们学习一下Ribbon负载均衡的用法
所谓的Ribbon,其实就是负载均衡+RestTemplate的集合调用。RestTemplate负责发请求,Ribbon负责通过负载均衡算法告诉RestTemplate请求发给具体哪一台服务器。Ribbon的核心,IRule接口下面看看源码
public interface IRule {
Server choose(Object var1);
void setLoadBalancer(ILoadBalancer var1);
ILoadBalancer getLoadBalancer();
}
那么对于这个接口,有哪些落地的实现类?接口中的choose方法返回的Server对象可以简单理解为:通过负载均衡算法返回了一个服务实例,用Server对象修饰 等同于 ServiceInstance对象。
上图是Ribbon各种负载均衡机制的实现关系,由一个顶级接口IRule和一个抽象接口AbstractLoadBalanceRule定义。5个默认的具体负载均衡实现类实现类的层级关系。。。。那么五个实现类从左往右的作用依次是:
- 轮询机制(默认):对多个服务进行依次调用 。我们可以看到它还有一个扩展实现类,顾名思义:根据响应时间给服务分配权重,时间越快权重越高,那么该服务被调用的可能就越大
- 随机:随机调用
- 重试:先根据轮训机制进行调用,如果服务获取失败则在指定时间内进行重试,获取可用的服务
- 最佳可利用:会先过滤掉多次访问故障的服务,然后选择一个并发量最小的服务
- 可利用过滤:类似第4中
- 分区:判断服务所在区域和可用性进行服务选择
那么如果给我们的服务消费者主动的选择我们想要的负载均衡机制呢,换句话说,代码该怎么写呢?看我的例子
@Configuration
public class RibbonRules01 { //定义一个配置类
//向Spring容器注入一个Bean,类型是Ribbon的顶级接口IRule
//,那么实现类是什么呢,这里我们选择RandomRule,也就是随机
@Bean
public IRule rule01(){
return new RandomRule();
}
}
//然后修改住启动类
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
/*
加上RibbonClient注解,name属性表示我这个客户端服务使用Ribbon的话是
要去访问那个服务提供者,name就是服务提供者在注册中心中的服务名!configuration 属性
则表示此次使用 Ribbon我是用的那个规则,换句话说我是用的IRule接口下的哪个实现类
在本次例子中,我们使用的是RibbonRules01配置类中返回的RandomRule()随机规则!
*/
@RibbonClient(name = "SPRINGCLOUD-PROVIDER-DEPT",configuration = RibbonRules01.class)
public class DeptConsumer_80 {
public static void main(String[] args) {
SpringApplication.run(DeptConsumer_80.class, args);
}
}
好,然后测试
@RequestMapping("/testRibbon")
public String testRibbon(){
String res = restTemplate.getForObject(REST_URL_PREFIX + "/ribbon", String.class);
return res;
}
以上是简单的Ribbon使用。好!那么他妈的这时候面试官问你为什么这样就实现了负载均衡,怎么办?它的原理是什么?你能否不用它提供的那6中负载均衡规则自己写一个呢?下面我就来分析一下这几个问题!
1.Ribbon负载均衡的原理
我们以默认的轮询方式来解读,首先,Ribbon的轮询方式怎么取计算每次去取不用的服务的呢?
算法:请求次数%服务器集群总数=本次要使用的服务器实例下标。
问题来了,服务器集群总数和服务器实例怎么取? 这个是SpringCloud的基础知识,这里我简单说一下。 SpringBoot启动的时候我们知道会根据@EnableAutoConfiguration注解去加载我们所需要的类!对于SpringCloud来说我们有一个DiscoverClient类(服务注册/发现模型)这个类负责管理所有的已经注册的服务,它会从Eureka中讲读取到的服务实例保存在内存中,以List的形式保存。也提供了一个方法:getInstances(“服务名”),返回的是该服务名对应的服务实例集群。知道了这些东西,我们就知道了上面提到的算法所需要的参数是怎么来的了!
还是以轮询机制为例下面看看RoundRobinRule类的源码(只摘取核心部分):
**没错,核心就是重写的IRule接口的choose()方法!**
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
log.warn("no load balancer");
//如果没有负载均衡策略,返回NULL
return null;
} else {
Server server = null;
int count = 0;
while(true) {
if (server == null && count++ < 10) {
//lb.getReachableServers()返回一个服务实例的List,啥鸡巴意思呢?
//大家伙自己点进去看,意思是选出状态健康的所有服务。
List<Server> reachableServers = lb.getReachableServers();
List<Server> allServers = lb.getAllServers();
int upCount = reachableServers.size();
int serverCount = allServers.size();
//下面就是算法,上面有提过。最后选出一个服务实例Server对象返回!
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;
}
}
}
2.Ribbon我配置了,它是怎么和RestTemplate联系起来的呢?RestTemplate怎么知道我要给那个服务实例发请求呢?
这个问题很有意思,其核心就是RestTemplate对象头上的@LoadBalance注解,@LoadBalance做了什么呢?请看我的文章Spring Cloud负载均衡策略中的RestTemplate为什么打上@LoadBalanced注解就能以服务名访问到服务.
这里不再赘述。
3.自定义负载均衡算法
直接上代码,关键点都在其中的注释中
@Configuration
public class ConfigBean {
@Bean
// @LoadBalanced 1.第一步,注释掉@LoadBalanced注解
// ,表示我们不用Ribbon或SpringLoadBanlance自带的负载均衡,我们用自己写的
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
//定义一个接口IMyOwnRules
public interface IMyOwnRules {
//这个方法表示我要从List<ServiceInstance>中,也就是我要从服务集合中
//通过自定义的算法选出一个服务实例返回!
ServiceInstance getInstance(List<ServiceInstance> serviceInstances);
}
@Component
//接口实现类,编写具体的负载均衡逻辑
public class MyOwnRules implements IMyOwnRules {
//原子类,多线程知识这里不多BB,主要共享变量线程安全问题
private AtomicInteger atomicInteger = new AtomicInteger(0);
//这是在干嘛?这个方法配合上面的原子类保证线程安全,返回服务器访问次数
private final int getAndIncrement(){
int current;
int next;
do {
//得到初始值
current= atomicInteger.get();
//2147483647是整行的最大值,不能大于这个值,next变量代表是第几次访问,对应服务器访问次数
next = current >= 2147483647 ? 0 : current+1;
//CAS知识,不多赘述,如果比较不通过,则自旋
}while (!this.atomicInteger.compareAndSet(current,next));
return next;
}
@Override
//轮询算法
public ServiceInstance getInstance(List<ServiceInstance> serviceInstances) {
int index = getAndIncrement() % serviceInstances.size();
return serviceInstances.get(index);
}
}
好!!!最后是消费者端的代码!写完了自己测试就OK
@RestController
public class DeptConsumerController {
@Autowired
//注入我们刚刚写的接口,等会要调用
private IMyOwnRules myOwnRules;
@Autowired
//注入我们Spring帮我们初始化好的discoveryClient对象。
//我们会通过这个对象获取所有的服务实例
private DiscoveryClient discoveryClient;
@Autowired
private RestTemplate restTemplate;
private static final String REST_URL_PREFIX = "http://SPRINGCLOUD-PROVIDER-DEPT";
@RequestMapping("/testMyOwnRules")
public String testMyOwnRules(){
//通过服务名称获取所有的服务实例(集群)
List<ServiceInstance> list = discoveryClient.getInstances("SPRINGCLOUD-PROVIDER-DEPT");
//调用我们自己写的负载均衡接口,获取最后选定出来的幸运之子
ServiceInstance serviceInstance = myOwnRules.getInstance(list);
//调用就完事了
String res = restTemplate.getForObject(serviceInstance.getUri() + "/ribbon", String.class);
return res;
}
好了 基本已经讲完,欢迎大家评论区指出不足,一起学习进步!
大家看完了点个赞,码字不容易啊,干了一下午。。。