目录
1、基础使用
2、自带策略
2.1、RandomRule 随机
2.2、RoundRobinRule:轮询
2.3、RetryRule:重试
2.4、ZoneAvoidanceRule:默认的线性轮询
3、自定义策略
3.1、自定义选择
3.2、自定义策略
Ribbon主要负责解决在集群部署时客户端选择连接到哪一个服务的问题,选择唯一一个连接地址的功能就叫负载均衡。
负载均衡有一些算法,比如:
在上一篇文章的基础上,先构建一个服务注册中心Eureka Server,之后建立三个服务提供方Provider Service和一个服务调用方Provider Consumer。
服务提供方Provider Service的服务名称需要相同,端口号不同。
@RestController
public class HelloController {
@GetMapping("/hello")
public String sayHello() {
System.out.println("service 03");
return "hello lily";
}
}
服务调用方Provider Consumer
@RestController
public class TestController {
@Autowired
private RestTemplate restTemplate;
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
@GetMapping("test")
public String sayHello() {
return restTemplate.getForObject("http://spring-cloud-provider-service/hello",String.class);
}
}
在其中,起到负载均衡作用的注解就是@LoadBalanced。
这样,在调用http://localhost:8086/test,就会自动负载均衡,决定调用哪一个服务。
在Ribbon组件中,IRule的接口主要负责对负载均衡策略的生成,它具有一系列的实现类:
核心代码是根据服务个数servercount获取到一个数值,启动时获取的服务列表会根据这个数值去拿出里面的一个服务,这个逻辑就是非常简单的。
int index = this.chooseRandomInt(serverCount);
server = (Server)upList.get(index);
if (server == null) {
Thread.yield();
} else {
if (server.isAlive()) {
return server;
}
server = null;
Thread.yield();
}
chooseRandomInt方法也是非常的简单,用的就是ThreadLocalRandom直接获取到的一个随机数,虽然不知道ThreadLocalRandom是什么,但是直到ThreadLocal是什么,可以推断出来,就是在线程内安全的一个随机数,根据服务个数来取值。
protected int chooseRandomInt(int serverCount) {
return ThreadLocalRandom.current().nextInt(serverCount);
}
这个类有两个实现子类:ResponseTimeWeightedRule和WeightedResponseTimeRule,但是ResponseTimeWeightedRule现在已经过时。
WeightedResponseTimeRule主要作用是根据服务器响应时间来增加权重。
轮询的核心代码,在服务为空的情况下,将循环10次,超过10次将抛出错误。十次以内,在有服务的情况下,根据函数incrementAndGetModulo获取下一个服务在全部服务allserver的列表中的位置,取出这个服务。
while(true) {
if (server == null && count++ < 10) {
List reachableServers = lb.getReachableServers();
List allServers = lb.getAllServers();
int upCount = reachableServers.size();
int serverCount = allServers.size();
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;
}
incrementAndGetModulo方法就是根据当前位置获取下一个服务位置。
private int incrementAndGetModulo(int modulo) {
int current;
int next;
do {
current = this.nextServerCyclicCounter.get();
next = (current + 1) % modulo;
} while(!this.nextServerCyclicCounter.compareAndSet(current, next));
return next;
}
}
在重试中,有一个subRule,这个属性是:
IRule subRule = new RoundRobinRule();
可以看出来,这是一个轮询,也就是说,在尝试用轮询获取到一个服务,如果成功就返回,如果失败,就会进入重试,重试的机制也是在线程没有中断的情况下,继续从轮询中获取到服务,如果失败超过了deadline,就取消任务,返回null
public Server choose(ILoadBalancer lb, Object key) {
long requestTime = System.currentTimeMillis();
long deadline = requestTime + this.maxRetryMillis;
Server answer = null;
answer = this.subRule.choose(key);
if ((answer == null || !answer.isAlive()) && System.currentTimeMillis() < deadline) {
InterruptTask task = new InterruptTask(deadline - System.currentTimeMillis());
while(!Thread.interrupted()) {
answer = this.subRule.choose(key);
if (answer != null && answer.isAlive() || System.currentTimeMillis() >= deadline) {
break;
}
Thread.yield();
}
task.cancel();
}
return answer != null && answer.isAlive() ? answer : null;
}
ZoneAvoidanceRule是默认的负载均衡策略,使用的是两个过滤器来达到线性轮询的效果。
public ZoneAvoidanceRule() {
ZoneAvoidancePredicate zonePredicate = new ZoneAvoidancePredicate(this);
AvailabilityPredicate availabilityPredicate = new AvailabilityPredicate(this);
this.compositePredicate = this.createCompositePredicate(zonePredicate, availabilityPredicate);
}
在配置文件中,可以指定某个服务所使用的负载均衡策略:
举例:
spring-cloud-provider-service.ribbon.NfLoadBalancerClass=com.netflix.loadbalancer.RandomRule
这样spring-cloud-provider-service服务的负载均衡策略就会变成随机。
除了指定服务的负载均衡,也可以自己实现一个,就是通过继承AbstractLoadBalancerRule
重写choose方法,先是判断负载均衡是否启用,然后获取可用服务和全部服务,
public class MyDefineIpHashRule extends AbstractLoadBalancerRule {
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
@Override
public Server choose(Object o) {
return choose(getLoadBalancer(), o);
}
public Server choose(ILoadBalancer loadBalancer, Object o) {
if (loadBalancer == null) {
return null;
}
//获取当前可用服务
List upServers = loadBalancer.getReachableServers();
//获取全部注册服务
List allServers = loadBalancer.getAllServers();
int serverCount = allServers.size();
if (serverCount == 0) {
return null;
}
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
//获取到请求地址
String remoteAddr = servletRequestAttributes.getRequest().getRemoteAddr();
//对请求地址获取hash
int hashCode = Math.abs(remoteAddr.hashCode());
//对服务长度取模
int code = hashCode%serverCount;
//返回服务
return upServers.get(code);
}
}