SpringCloud初级学习(四)------Ribbon负载均衡
前言
在前面的博文中,我们创建了helloword-cloud
的工程来使用了Eureka的服务注册,现在,我们的服务已经注册到Eureka了,接下来我们来使用消费者访问服务,在这里SpringCloud为我们提供了Ribbon来对客户端的请求进行负载均衡.
正文
1. Ribbon是什么?
SpringCloud Ribbon是基于Netfix Ribbon实现的一套客户端负载均衡的工具.
它的主要功能是提供客户端的负载均衡,简单来说,就是帮助我们基于某种规则,如轮询或者随机,去连接对应的微服务,也可以通过Ribbon实现自定义的负载均衡算法.
所谓负载均衡,就是将用户的请求平摊到多个服务上.
常见的负载均衡有Nginx、F5等,在dubbo中也有对应的负载均衡的实现.
2. Ribbon使用
现在的情况是,我们的微服务helloword-cloud-provider
已经部署成功,并且注册到Eureka上了,但是现在只有一个服务的提供者,我们可以新建一个和helloword-cloud-provider
一样的SpringBoot工程作为第二个微服务提供者,姑且将其命名为:helloword-cloud-provider8002
.后面的数字代表该服务启动的端口号.而之前的provider的端口号是8001.
下面是新建服务提供者8002的步骤
我们新建一个provider,命名为provider8002,启动端口是8002,区别在于,8002连接的是自己的数据库,读数据也是读自己的数据表.下面是SQL脚本:
DROP DATABASE IF EXISTS cloudDB02;
CREATE DATABASE cloudDB02 CHARACTER SET UTF8;
USE cloudDB02;
CREATE TABLE dept
(
deptno BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT,
dname VARCHAR(60),
db_source VARCHAR(60)
);
INSERT INTO dept(dname,db_source) VALUES('开发部',DATABASE());
INSERT INTO dept(dname,db_source) VALUES('人事部',DATABASE());
INSERT INTO dept(dname,db_source) VALUES('财务部',DATABASE());
INSERT INTO dept(dname,db_source) VALUES('市场部',DATABASE());
INSERT INTO dept(dname,db_source) VALUES('运维部',DATABASE());
SELECT * FROM dept;
需要注意的是,我们需要在yml配置文件中定义不同的实例名称,用来区分两个不同的服务提供者.
instance:
instance-id: helloword-cloud-provider(8002) #自定义服务名称
prefer-ip-address: true #访问路径显示IP地址
但是他们对外暴露的服务名必须是一样的,也就是在application.yml中的:
spring:
application:
name: helloword-cloud-provider
上面这个属性,在两个provider中必须是一样的.代表两个不同的实例,提供相同的服务.
接着启动服务提供者8002,首先进行自测,访问url:http://localhost:8002/dept/list
可以看到第二个服务也可以正常调用了:
注意页面显示的数据所属的数据库,是我们新建的clouddb02,也就是和8001不同的库.
启动服务,我们可以看到Eureka Server的Web页面显示了这两个微服务的提供者:
好了,现在我们的服务提供者也有2个了,下面我们来对消费者进行改造,并且引入Ribbon进行负载均衡,访问我们的微服务.
一、Ribbon依赖的引入
首先要使用Ribbon,就要引入其依赖,这也是我们使用众多的开源组件的必须流程
在helloword-cloud-consumer
的POM文件中加入Ribbon的依赖:
<!--Ribbon相关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</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>
</dependency>
可以看到,Ribbon的使用是建立在config和eureka之上的.
二. 修改配置文件application.yml
其次,我们的服务消费者要消费服务,就要知道服务提供者的相关信息,所以需要去Eureka获取,因此要配置Eureka的地址
在application.yml文件中配置:
eureka:
client:
register-with-eureka: false #false表示不向注册中心注册自己
service-url:
defaultZone: http://localhost:7001/eureka/ #设置与eureka server交互的地址,如果有多个,可以用逗号隔开
由于我们的Ribbon是做负载均衡的,而我们这里是使用RestTemplate来进行RPC调用的,因此要在RestTemplate这边开启负载均衡.
@Bean
@LoadBalanced//开启负载均衡
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
三. 开启Eureka client功能
在这里,我们的服务消费者也是作为Eureka的客户端去请求Eureka服务端,获取服务列表的,所以要在主启动类中开启
@SpringBootApplication
@EnableEurekaClient
public class DeptConsumer80 {
public static void main(String[] args) {
SpringApplication.run(DeptConsumer80.class, args);
}
}
四. 修改调用RPC的URL
在之前,我们通过restTemplate来调用,都是通过IP+端口号的方式.
而现在,我们在客户端引入Ribbon,通过负载均衡的方式从Eureka获取服务来调用,所以要更改URL:
//private static final String REST_URL_PREFIX = "http://localhost:8001";
private static final String REST_URL_PREFIX = "http://HELLOWORD-CLOUD-PROVIDER";
这里直接更改为Eureka中的服务名,这样在调用的时候,Ribbon会按照某种负载均衡的方式,在指定的服务中寻找一个服务提供者来调用.
五. 启动服务,调用成功
现在我们启动消费者,在浏览器进行调用,就可以看到调用成功了:
刷新页面,可以看到服务调用成功了.这样我们就不用关心服务的地址和端口号,直接调用服务即可.这也是Ribbon和Eureka给我们带来的便利
3. Ribbon的负载均衡
在上面的步骤中,我们在配置类中加入了@LoadBalanced
注解,这样在消费者进行请求时,会根据Ribbon的默认的负载均衡去调用微服务,我们多刷新几次,可以看到服务的调用时按照轮询的方式来调用的,除了轮询,Ribbon还提供了很多种的负载均衡策略,下面我们来改变Ribbon默认的轮询,改为随机的方式来访问微服务
-
首先我们创建一个负载均衡的配置类
MyRule.java
package com.xiaojian.myrule; import com.netflix.loadbalancer.IRule; import com.netflix.loadbalancer.RandomRule; import com.netflix.loadbalancer.RoundRobinRule; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MyRule { @Bean public IRule initRule() { //return new RoundRobinRule();//轮询 return new RandomRule(); } }
需要注意的是,这个配置类不能和主启动类放在同一个包下,这也是Ribbon官方的要求,因此可以看到上面这个类是在另外一个包里面的.
在这个配置类中,我们给spring容器中注入了一个负载均衡算法,看名字我们就可以知道这个算法是随机算法.
-
下面我们要告诉spring,这个配置文件是Ribbon的配置文件,我们要使用这个配置文件指定的算法来访问微服务
首先我们需要在消费者的主启动类上添加注解
@RibbonClient(name = "HELLOWORD-CLOUD-PROVIDER", configuration = MyRule.class)
注解属性解释:
name: 指定Ribbon要针对哪个服务做负载均衡
configuration: 指定Ribbon要使用的配置类
-
重启消费者,启动以后多调用几次,我们可以看到随机负载的配置生效了
以上方式,我们使用的是Ribbon默认的负载均衡算法,除了这些默认的算法,我们还可以自定义算法
需求: 现在有2个微服务提供者8001和8002,我们要求消费者在进行调用的时候,每个微服务调用5次以后再进行切换?
按照以上需求,我们还对消费者进行配置修改:
第一步: 实现自己的负载算法类
参考github上的RandomRule
类,该类正是我们在容器中注入的负载算法对象类,我们要实现自己的负载算法,可以参考该类,修改对应的代码.
这里我们先看看官方的这个算法类:
//
// 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 edu.umd.cs.findbugs.annotations.SuppressWarnings;
import java.util.List;
import java.util.Random;
public class RandomRule extends AbstractLoadBalancerRule {
Random rand = new Random();
public RandomRule() {
}
@SuppressWarnings({"RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE"})
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;
}
//随机获得要访问的微服务的下标
int index = this.rand.nextInt(serverCount);
//根据下标,从微服务中获取对应的服务
server = (Server)upList.get(index);
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) {
}
}
该算法的主要方法都在choose()
方法中,因此我们可以修改一下,创建我们自己的算法类:
package com.xiaojian.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;
public class MyLoadBalanceRule extends AbstractLoadBalancerRule
{
// total = 0 // 当total==5以后,我们指针才能往下走,
// index = 0 // 当前对外提供服务的服务器地址,
// total需要重新置为零,但是已经达到过一个5次,我们的index = 1
// 分析:我们5次,但是微服务只有8001 8002 因此在数组下标达到2的时候,重置回0
//
private int total = 0; // 总共被调用的次数,目前要求每台被调用5次
private int currentIndex = 0; // 当前提供服务的机器号
public Server choose(ILoadBalancer lb, Object key)
{
if (lb == null) {
return null;
}
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) {
/*
* No servers. End regardless of pass, because subsequent passes only get more
* restrictive.
*/
return null;
}
// int index = rand.nextInt(serverCount);// java.util.Random().nextInt(3);
// server = upList.get(index);
// private int total = 0; // 总共被调用的次数,目前要求每台被调用5次
// private int currentIndex = 0; // 当前提供服务的机器号
if(total < 5)
{
server = upList.get(currentIndex);
total++;
}else {
total = 0;
currentIndex++;
if(currentIndex >= upList.size())
{
currentIndex = 0;
}
}
if (server == null) {
/*
* The only time this should happen is if the server list were somehow trimmed.
* This is a transient condition. Retry after yielding.
*/
Thread.yield();
continue;
}
if (server.isAlive()) {
return (server);
}
// Shouldn't actually happen.. but must be transient or a bug.
server = null;
Thread.yield();
}
return server;
}
@Override
public Server choose(Object key)
{
return choose(getLoadBalancer(), key);
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig)
{
// TODO Auto-generated method stub
}
}
第二部: 注入负载算法类
以上就是我们自己修改的负载算法类,接着我们在负载配置类中配置这个类:
@Bean
public IRule initRule() {
//return new RoundRobinRule();//轮询
//return new RandomRule();
return new MyLoadBalanceRule();
}
重启消费者服务,进行测试:
自行测试后可以看到,每个服务进行5次调用以后,就会调用下一个服务.
至此,我们的自定义负载算法实现了,我们可以根据我们的实际需求来定义负载算法,使我们的微服务发挥出更好的性能.
总结
至此,Ribbon的使用差不多也就到这里了,使用Ribbon可以时我们在调用微服务的时候,脱离IP和端口的限制,只要指定服务名就可以调用服务.
在下一章的博文中,我们将继续讲springcloud中的另一个组件----Feign,进一步学习SpringCloud技术栈.