Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将NetFlix的中间层服务连接在一起。Ribbon的客 户端组件提供一系列完整的配置项如: 连接超时、重试等等。 简单的说,就是在配置文件中列出LoadBalancer (简称LB:负载均衡)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等等)去连接这些机器。我们也很容易使用Ribbon实现自定义的负载均衡算法!
●LB,即负载均衡(Load Balance) ,在微服务或分布式集群中经常用的一种应用。
●负载均衡简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA (高可用)。
●常见的负载均衡软件有Nginx, Lvs 等等
●dubbo、 SpringCloud中均给我们提供 了负载均衡,SpringCloud的负载均衡算法可以自定义
●负载均衡简单分类:
(1)demo还是之前文章的项目,由于这是ribbon是客户端需要的,所以我们在spring-cloud-consumer-dept项目下导入新依赖:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-ribbonartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-eurekaartifactId>
dependency>
(2)新增配置文件信息
#eureka配置
eureka:
client:
register-with-eureka: false #不向注册中心注册自己,因为这是客户端
service-url: #获取服务的地址
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
(3)在启动类上添加@EnableEurekaClient注解,标识这是eureka客户端
(4)给之前ConfigBean下的bean加上@LoadBalanced注解,进行ribbon负载均衡
(5)修改之前在controller下写死的url:REST_URL_PREFIX,因为我们要进行负载均衡,从不同的注册中心去获取服务,改成服务的名字
(6)启动3个eureka,然后启动provider服务,在启动consumer服务,调用api看看,是否能访问到数据库:访问成功
(1)由于上面的demo只有一个服务提供者,体会不到服务端的负载均衡,所以我们多来两个服务提供者,那么现在我们需要多创建两个数据库,模拟分库分表(因为没有数据库的数据同步,所以现在只能模拟一下):导出springcloud_demo_db1的结构和数据,修改数据库名字等信息,然后运行sql语句,创建出新库
(2)然后再创建两个提供者的服务,指向不同的数据库,配置信息中要注意服务名称不能修改,这是前提,其他的要修改端口等信息
(3)启动eureka(启动一个即可,不然太卡了),再启动三个provider服务,最后启动consumer服务,浏览器访问:
发现是从第二个数据库获取的,也就是从8002端口的服务端获取的;我们再多访问几次看看
自动地从第三个服务去获取了。我们还可以发现,它默认使用的负载均衡是轮询算法,因为每点一次就换下一个服务。
首先我们要了解IRule接口,ribbon的负载均衡算法都来自于这个接口的实现类:
package com.netflix.loadbalancer;
public interface IRule {
Server choose(Object var1);
void setLoadBalancer(ILoadBalancer var1);
ILoadBalancer getLoadBalancer();
}
这是他的实现类:
常用的实现类的负载均衡算法
(1)RoundRobinRule:轮询策略,这是默认的策略
(2)AvailabilityFilteringRule:先过滤掉跳闸,访问故障的服务,再进行轮询可用的服务
(3)RandomRule:随机策略
(4)RetryRule:重试策略,先按照轮询进行服务获取,但获取失败后会在指定时间内重试
还有其他的策略不多介绍
由于默认的是轮询,上面也证实了,现在我们测试一下改成随机策略:我们在ConfigBean类下重新写一个bean,并注册到spring中
重启服务,同样访问http://localhost/consumer/dept/list,发现是随机获取服务的了,不进行轮询了。
上面说到,直接向spring注册即可,那我们自己写一个均衡的策略,然后注册到spring应该也是可以的。
(1)我们先写一个类MyRibbonRule,加上@Configuration注解,标识这是配置类,这个类用于写自定义的策略
在这种情况下,客户端由RibbonClientConfiguration中已经存在的组件与自定义的Configuration中的任何组件组成(后者通常会覆盖前者)。
自定义的Configuration必须是@Configuration(即使用这个注解),但请注意,它不在主应用程序上下文的@ComponentScan中,否则将由所有@RibbonClients共享(注意是@RibbonClients,不是说@RibbonClient)。如果您使用@ComponentScan (或@SpringBootApplication),则需要采取措施避免包含(例如将其放在一个单独的,不重重的包中,或者指定要在@ComponentScan )。
这就说明了我们的MyRibbonRule不能放在启动类的同级目录和子级目录下,因为这样会被@ComponentScan扫描到,我们自定义的这个配置类就会被所有的Ribbon客户端所共享(也就是所有的服务用同一个策略了),也就是说我们达不到特殊化定制的目的了;
这里我们放在了启动类的上一级:
(2)由于更改了的配置,需要在启动类加上@RibbonClient注解,点进这个注解,发现可以配置3个信息:
@Configuration
@Import({RibbonClientConfigurationRegistrar.class})
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RibbonClient {
String value() default "";
String name() default "";//需要进行负载均衡的服务名字,默认可以不写
Class<?>[] configuration() default {};//自定义的策略的类信息,默认用自带的
}
(3)给注解添上自定义的信息:
@RibbonClient(name = "SPRING-CLOUD-PROVIDER-DEPT",configuration = MyRibbonRule.class)
//在spring启动之后,就会加载自定义的类信息了
(4)写MyRuleImpl的内容,我们根据轮询策略来更改一下:每个服务提供3次再进行轮询
private int total=0;
private int currentIndex=0;
@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;
}
if (total<3){//我们要在轮询的基础上,每一个服务都使用3次,
server=upList.get(currentIndex);
total++;
}
else {//已经满足3次,就要轮询下一个
currentIndex=(currentIndex+1)%upList.size();
server=upList.get(currentIndex);
total=1;
}
if (server == null) {
Thread.yield();
} else {
if (server.isAlive()) {
return server;
}
server = null;
Thread.yield();
}
}
return server;
}
}
(5)在MyRibbonRule类中写一个返回MyRuleImpl的bean,并注册到spring
@Configuration
public class MyRibbonRule {
//IRule 随机
@Bean
public IRule randomRule(){
return new MyRuleImpl();
}
}
(6)测试发现是按照代码那样执行,每访问3次,就换了下一个服务
但是其实这并不完全是正确的,因为在实践中就会发现,每次通过lb.getReachableServers()获取可达的服务,返回结果顺序并不是每次都相同的,有可能顺序是[1,2,3],也可能是[1,3,2],但是无伤大雅。