SpringCloud笔记四:Ribbon
什么是Ribbon?
Ribbon是一个客户端的负载均衡。
我举个例子就明白了,我去超市买东西付钱,收银台有3个,一个收银台有10个人排队,一个收银台有5个人排队,一个收银台有2个人排队。只要我不是傻子,我就知道我该去2个人排队的那个收银台。
我是客户,我知道选择人最少的收银台。这就是客户端的负载均衡。这就是Ribbon
提一句,负载均衡有两种方式:
- 集中式:这个就是有一个硬件,比如F5,提供者和消费者之间通过硬件负载均衡,缺点是硬件一般都很贵
- 进程内:这个是软件的形式,把负载均衡的逻辑放到消费方,让消费方根据逻辑去选择一台合适的服务器。
Ribbon的配置
Maven引入
由于Ribbon是客户端的负载均衡,我们在consumer项目里面引入Maven配置
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
开启注解
注解这有两个地方需要标注,我们的consumer是使用restTemplate来访问的,在获取restTemplate的方法上开启负载均衡注解,@LoadBalanced
@Bean
@LoadBalanced
public RestTemplate getRestTemplate()
{
return new RestTemplate();
}
顺便,consumer里面的Controller里面,访问的地址我们原来写的是localhost,既然是微服务,我们肯定使用服务名称了,改一下
// private static final String REST_URL_PREFIX="http://localhost:8001";
private static final String REST_URL_PREFIX="http://PROVIDER-DEPT";
第二个注解就是主方法那里,加一个@EnableEurekaClient
@SpringBootApplication
@EnableEurekaClient
public class Consumer80Application {
public static void main(String[] args) {
SpringApplication.run(Consumer80Application.class, args);
}
}
再次重启启动eureka7001,eureka7002,eureka7003和provider,consumer,你会发现还是完全ok的。
Ribbon负载均衡
上面说了,Ribbon是客户端的负载均衡,所以我们要加在consumer项目上,Ribbon默认的负载均衡方式的轮询。我们可以新建两个provider来测试一下
新建provider8002和8003
新建项目就不多说了,记得把8001的yml,Mybatis,代码啥的复制过去。如下
server:
port: 8002
mybatis:
config-location: classpath:mybatis/mybatis.cfg.xml #mybatis配置文件所在路径
type-aliases-package: com.vae.springcloud.entity #所有Entity别名类所在包
mapper-locations: classpath:mybatis/mapper/**/*.xml #mapper映射文件
spring:
application:
name: provider-dept
datasource:
# type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://localhost:3306/vae?serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123
dbcp2:
min-idle: 5 # 数据库连接池的最小维持连接数
initial-size: 5 # 初始化连接数
max-total: 5 # 最大连接数
max-wait-millis: 200 # 等待连接获取的最大超时时间
eureka:
client: #客户端注册进eureka服务列表内
service-url:
# defaultZone: http://localhost:7001/eureka
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
instance:
instance-id: provider-8002 #这个是修改Eureka界面的Status名称
prefer-ip-address: true #这个是设置鼠标放在status上的时候,出现的提示,设置ip地址显示
info:
app.name: provider-8001
company.name: www.vae.com
build.artifactId: $project.artifactId$
build.version: $project.version$
server:
port: 8003
mybatis:
config-location: classpath:mybatis/mybatis.cfg.xml #mybatis配置文件所在路径
type-aliases-package: com.vae.springcloud.entity #所有Entity别名类所在包
mapper-locations: classpath:mybatis/mapper/**/*.xml #mapper映射文件
spring:
application:
name: provider-dept
datasource:
# type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://localhost:3306/jj?serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123
dbcp2:
min-idle: 5 # 数据库连接池的最小维持连接数
initial-size: 5 # 初始化连接数
max-total: 5 # 最大连接数
max-wait-millis: 200 # 等待连接获取的最大超时时间
eureka:
client: #客户端注册进eureka服务列表内
service-url:
# defaultZone: http://localhost:7001/eureka
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
instance:
instance-id: provider-8003 #这个是修改Eureka界面的Status名称
prefer-ip-address: true #这个是设置鼠标放在status上的时候,出现的提示,设置ip地址显示
info:
app.name: provider-8001
company.name: www.vae.com
build.artifactId: $project.artifactId$
build.version: $project.version$
看到没,yml文件,我只修改了端口号、数据库和instance-id状态id。因为每一个微服务都可以都一个独立的数据库,所以,三个provider的数据库我分别使用的是shuyunquan,vae,jj三个数据库,当然,表结构都是一样的没改
代码直接复制就行,没什么需要改的。
现在我们启动eureka7001,eureka7002,eureka7003,provider8001,provider8002,provider8003和consumer。算一下7个项目了,运行之后,你可以通过consumer访问试试,结果每次刷新都换了数据库,这刚好表明了Ribbon的默认负载均衡方式是轮询。
Ribbon核心组件IRule
Ribbon的核心组件IRule作用是定义以什么样的方式去负载均衡,比如轮询,比如随机。要想使用IRule,我们需要写一个类。
注意:IRule组件的类不能在有@ComponentScan组件的类的包以及子包下存在,主方法的@SpringBootApplication注解里面有@ComponentScan注解,所以,我们不能在主方法的所在包或者子包下新建IRule组件 |
---|
我们在vae包下新建myrule包,包下新建MySelfRule类,代码如下:
package com.vae.myrule;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MySelfRule {
@Bean
public IRule myRule(){
return new RandomRule(); //这个是负载均衡的随机访问
// return new RoundRobinRule(); 这个是负载均衡的默认的轮询访问
}
}
看一下项目目录图
Ribbon自定义
上面讲的都太简单了,完全体现不出技术含量,所以Ribbon负载均衡的方式只有轮询和随机这还不够,我们要学会自定义负载均衡的方式,先看一张图
要想自定义Ribbon负载均衡,我们要自己写个类,首先要继承AbstractLoadBalancerRule这个类,然后剩下的代码嘛,你可以去Ribbon的官方GitHub看看,Ribbon官方随机代码,把这里面的方法复制出来,我们看看里面的内容都是啥,我们只摘取方法
package com.vae.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.concurrent.ThreadLocalRandom;
public class RandomRule extends AbstractLoadBalancerRule {
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
return null;
}
Server server = null;
while (server == null) {
//线程是否中断,中断返回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 = chooseRandomInt(serverCount);
server = upList.get(index);
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;
}
protected int chooseRandomInt(int serverCount) {
return ThreadLocalRandom.current().nextInt(serverCount);
}
@Override
public Server choose(Object key) {
return choose(getLoadBalancer(), key);
}
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
}
可以看到啊,大概就是这样子的,initWithNiwsConfig方法是我们继承了AbstractLoadBalancerRule实现的,上面的方法中,很容易看出choose方法才是真正有内容的,我们现在就可以对这个随机算法进行修改了,比如,我想修改为每个服务访问5次,然后再随机的访问下一个服务。肯定是修改choose方法了
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;
}
可以看到,我就定义了两个变量,自定义算法这个是很主观的,你需要什么样的方式来负载均衡就自己改代码就好了。