一、ribbon简介
ribbon是什么?负载均衡是什么?它又怎么负载均衡的?甚至跟接下来要讲的Feign负载均衡以及Nginx负载均衡有什么区别?一大问题填满脑海;别着急,跟着我的脚步慢慢来;我会让你弄明白这三者的区别的。我们一个一个问题来向大家解释:
- Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端 负载均衡;注意点就是客户端;而客户端也就是类似这个消费者(Consumer);简单的来说 Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将Netflix的中间层服务连接在一起。而且Ribbon客户端组件提供一系列完善的配置项;比如连接超时;重试等。
- 负载均衡简单的来说就是将用户的请求平摊的分配到多个服务上;从而达到系统的高可用。常见的负载均衡的软件有Nginx、LVS 、硬件F5等。
- 上面我们解释到Ribbon主要功能是提供客户端的软件负载均衡算法;它会自动的帮助你基于某种规则(比如默认的轮询,随机连接…)去连接这些服务。而且我们也可以使用Ribbon实现自定义负载均衡的算法。
二、ribbon初步配置
从上述中就应该了解到Ribbon主要是提供给客户端的;那么在原来的项目中;80端口号是不是就是所谓的客户端啊;那么接下来呢;按照老规矩思想;要加载新的组件或者包;是不是直接到maven中直接写引用坐标就可以了啊;那么按照老手法咋们先修改80消费者(Consumer)的pom文件;添加的内容:
<!--Ribbon相关包-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId> <!--这个eureka的包;有点像数据库的驱动包的存在;我客户端要去访问eureka中心的服务;就必须加载这个-->
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId> <!--此包与eureka包有关联所以加载进来-->
</dependency>
我客户端要去访问eureka中心的服务;是不是就得再yml上配置Eureka啊;所以然后修改咋们的yml文件:
server:
port: 80
#修改所添加的部分
eureka:
client:
register-with-eureka: false #false表示不向注册中心注册自己
service-url:
defaultZone: http://localhost:7001/eureka
好;咋们这个架子;配置都搞好了;接下来就开始整代码;之前咋们一直都是通过REST(restTemplate)这个东西去访问调用啊;restTemplate这个是咋们配置好的一个配置类;现在我要通过Ribbon负载均衡+REST访问调用;那怎么去开启Ribbon呢?就在配置restTemplate的配置类上加上 @LoadBalanced 这么一个注解;就实现了负载均衡了;就这么简单。那同学们肯定有疑问;你这个怎么就体现负载均衡了;好;接下来我通过演示来证明下我从不吹牛皮。
三、Ribbon负载均衡
我们就修改Configbean这个类:
package com.chihiro.microservice.cfgbeans;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration // -->spring 的 applicationContext.xml
public class ConfigBean {
@Bean
@LoadBalanced //负载均衡工具
public RestTemplate getUserSeriver(){
return new RestTemplate();
}
}
我们也整合了Eureka;所以我们也要在主启动类上加上 @EnableEurekaClient 啊:
@SpringBootApplication
@EnableEurekaClient //为了交互
public class DeptConsumer80_App {
public static void main(String[] args) {
SpringApplication.run(DeptConsumer80_App.class,args);
}
}
好;到目前为止;还有一个大问题;就是之前咋们是直接通过REST无脑式的访问接口;现在咋们集成Eureka和Ribbon;是不是就可以真正地通过微服务访问了;所以我们之前的80客户端的DeptController_consumer是不是就得修改了啊;将之前的:
private static final String REST_URL_PREFIX = "http://localhost:8001";
需要修改成:
private static final String REST_URL_PREFIX = "http://MICROSERVICECLOUD-DEPT";
MICROSERVICECLOUD-DEPT是不是对应这个咋们Eureka服务所注册的服务啊
最关键的时候到了;以前咋们是通过REST访问啊;现在我们通过微服务正式的获得访问;测试为了保证通过;咋们先启动EurekaService-7001;然后再启动服务提供者-8001;保证咱们的这个8001服务注册到Eureka中心去;最后启动消费者-80;
测试结果:
证明咋们这个Ribbon访问是不是通过了啊;这还只不过是能够进行访问;但并还没体现Ribbon的自身价值;之前咋们就说了;它有自己的一套访问规则;那怎么能够体现它的规则呢;接下来;咋们将在Eureka上注册多个服务提供者microservicecloud-privider-dept-8002和microservicecloud-privider-dept-8003。当然这里的服务提供者可以是其他;我这里为了方便提供演示;就将8001的代码复制成8002和8003;我之前理论就说所谓的微服务是不是单独一个独立的完整可以运行的服务啊;它也拥有自己的数据库啊;既然如此;8002和8003服务是不是同时拥有自己的数据库;那么接下来创建数据库microservice02和microservice03 导入相同的dept表 ;然后修改其端口号和配置文件;当然这也得首先通过自测;并且都将它们注册进入Eureka中心去 ;不用改变的就是
spring:
application:
name: microservicecloud-dept
应用名称;不需要修改。现在咋们有8001、8002、8003服务提供者;而且这三个微服务做的是同一件事情;就是部门的CURD;假如8001服务在Eureka中心被Donwon机了;也就是所谓的连不上;失去联系。我消费者的请求部门就会报错;甚至影响整个系统;所以防止某个服务的Donwon机;一般会有好几个微服务做得是同样事情;而Ribbon给客户端提供访问规则;好;咋们先把8002和8003服务注册进去;咋们先启动EurekaService-7001;然后足个将8001、8002、8003开启;然后咋们去访问7001端口号:
咋们就会发现多了8002、8003服务;这个时候咋们再去用消费者访问;又会发生什么奇怪的事情呢;那么咋们就开启80消费者端口;然后通过消费者去访问:
没瞎的;应该看到所变化的数据;也就是证明每次所访问的微服务都不是同一个;而且每个微服务轮着来;这个也就是遵循了Ribbon的默认规则—轮询算法;当然它的算法主要来源于IRule组件并且还有其他规则:
- 默认规则----轮询(RoundRobinRule)。
- 随机规则(RandomRule)
- 权重规则(根据响应时间加权)(WeightedResponseTimeRule)
- 如果获取服务失败;则在指定时间内进行重试 (RetryRule)
…等七种自带规则;其上图看出;Ribbon默认加载的是轮询吧;一人一次轮着来。那我又怎么去实现自己自定义的呢;或者说我需要随机规则;那我又怎么去做呢;接下来就来了解下Ribbon的核心组件IRule
四、自定义Ribbon
IRule组件:它是根据特定算法中从服务列表中选取一个要访问的服务。其实它就是一个接口;实现该接口就可以实现自定义。
首先它自带七种规则;与自定义使用肯定是你不一样的;有的就直接使用:
package com.chihiro.microservice.cfgbeans;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration // -->spring 的 applicationContext.xml
public class ConfigBean {
@Bean
@LoadBalanced //负载均衡工具
public RestTemplate getUserSeriver(){
return new RestTemplate();
}
//使用自己重新声明的随机算法替代默认的轮询算法
@Bean
public IRule myIRule(){
return new RandomRule();
}
}
重新声明配置一下即可;那么又如何自定义呢;聪明的同学就会立马想到;不就是实现该接口嘛;只需实现(AbstractLoadBalancerRule接口)我重新定义一个类实现;那么目标已经明确;为了达到特殊化定制的目的;如果我们将该自定义类;写在主启动类某个子包下;然后在ConfigBean 该类重新声明的话;那么所有的服务都将以这个规则去实现;这不是我们的目的;所以我们还得重新写一个专属的配置类但是我们建类的时候需要注意一大细节:在官方网上明确规定这个自定义配置类不能放在 @ComponentScan所扫描的当前包下以及子包下,否则我们的自定义配置类就会被所有的Ribbon客户端所共享,也就是说我们达不到特殊化定制的目的。因为主启动类的@SpringBootApplication注解已经包含了@ComponentScan注解;所以我们就不能够在该包写;必须重新新建与主启动类平级的包;这里特别注意到。那么我们首先就来新建包(myrule);
新建自定义配置类:
package com.chihiro.myrule;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RoundRobinRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyRule {
@Bean
public IRule myRule(){
return new RandomRule_LP(); //使用自定义类
}
}
主启动类也必须指定说明:
package com.chihiro.microservice;
import com.chihiro.myrule.MyRule;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
@SpringBootApplication
@EnableEurekaClient
//指定某个服务使用自定义配置类规则算法
@RibbonClient(name = "MICROSERVICECLOUD-DEPT",configuration = MyRule.class)
public class DeptConsumer80_App {
public static void main(String[] args) {
SpringApplication.run(DeptConsumer80_App.class,args);
}
}
自定义规则类:
package com.chihiro.myrule;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import java.util.List;
import java.util.Random;
public class RandomRule_LP extends AbstractLoadBalancerRule {
//total=0 访问次数
//currentIndex=0 当前服务机器号
private int total = 0;
private int currentIndex = 0;
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
return null;
} else {
Server server = null;
while(server == null) {
if (Thread.interrupted()) {
return null;
}
List<Server> upList = lb.getReachableServers();
List<Server> allList = lb.getAllServers();
int serverCount = allList.size();
if (serverCount == 0) {
return null;
}
//每个服务连续访问五次算法
if(total <5){
server=upList.get(currentIndex);
total++;
}else{
total=0;
currentIndex++;
if (currentIndex>=upList.size()){
currentIndex=0;
}
}
if (server == null) {
Thread.yield();
} else {
if (server.isAlive()) {
return server;
}
server = null;
Thread.yield();
}
}
return server;
}
}
public Server choose(Object key) {
return this.choose(this.getLoadBalancer(), key);
}
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
}
这个算法就是让每个服务轮着来连续访问五次。