我们来了解一下负载均衡的源码,是怎么实现的,它是怎么实现的,开始我们说了第三种方式只是用了一个注解,
实质上是和第二种方式是一模一样的,那么为了方便观察呢,这里使用第二种方式,来查看源码
ServiceInstance serviceInstance = this.loadBalancerClient.choose("PRODUCT");
这里调用了choose这种方法,我们点进来可以看到他的一个接口
/**
* Implemented by classes which use a load balancer to choose a server to
* send a request to.
*
* @author Ryan Baxter
*/
public interface ServiceInstanceChooser {
/**
* Choose a ServiceInstance from the LoadBalancer for the specified service
* @param serviceId the service id to look up the LoadBalancer
* @return a ServiceInstance that matches the serviceId
*/
ServiceInstance choose(String serviceId);
}
按进来跳到他的实现,看一下他的实现类
public class RibbonLoadBalancerClient implements LoadBalancerClient {
可以看到他实现了一个接口,LoadBalancerClient,LoadBalancerClient又继承了一个ServiceInstanceChooser
/**
* Represents a client side load balancer
* @author Spencer Gibb
*/
public interface LoadBalancerClient extends ServiceInstanceChooser {
注意我们看源码的时候呢,可以这样子来看,比如我已经进入到这个类里面了,你想看他类之间的关系的话,
回到我们要观察的方法
@Override
public ServiceInstance choose(String serviceId) {
Server server = getServer(serviceId);
if (server == null) {
return null;
}
return new RibbonServer(serviceId, server, isSecure(server, serviceId),
serverIntrospector(serviceId).getMetadata(server));
}
我们知道他第一步要把服务列表要给找出来,你看到这个方法
protected Server getServer(String serviceId) {
return getServer(getLoadBalancer(serviceId));
}
然后又继续往下找
protected Server getServer(ILoadBalancer loadBalancer) {
if (loadBalancer == null) {
return null;
}
return loadBalancer.chooseServer("default"); // TODO: better handling of key
}
可以看到他这里用了哪个去找,用的是一个ILoadBalancer,看一下这个
/**
* Interface that defines the operations for a software loadbalancer. A typical
* loadbalancer minimally need a set of servers to loadbalance for, a method to
* mark a particular server to be out of rotation and a call that will choose a
* server from the existing list of server.
*
* @author stonse
*
*/
public interface ILoadBalancer {
他用的这个组件去找,这个组件就是属于Ribbo下面的
com.netflix.loadbalancer.ILoadBalancer
所以说他一旦用了负载均衡的技术,都是Ribbon,我们可以看这个方法
protected Server getServer(ILoadBalancer loadBalancer) {
if (loadBalancer == null) {
return null;
}
return loadBalancer.chooseServer("default"); // TODO: better handling of key
}
这里有几个选择,我们就选择com.netflix.loadbalancer.BaseLoadBalancer.chooseServer(Object)
/*
* Get the alive server dedicated to key
*
* @return the dedicated server
*/
public Server chooseServer(Object key) {
if (counter == null) {
counter = createCounter();
}
counter.increment();
if (rule == null) {
return null;
} else {
try {
return rule.choose(key);
} catch (Exception e) {
logger.warn("LoadBalancer [{}]: Error choosing server for key {}", name, key, e);
return null;
}
}
}
进来之后同样的可以观察这个类之间的关系,这个就比上一个复杂一些了
public class BaseLoadBalancer extends AbstractLoadBalancer
public abstract class AbstractLoadBalancer implements ILoadBalancer {
看一下ILoadBalancer接口,这里有一个getServerList
/**
* @deprecated 2016-01-20 This method is deprecated in favor of the
* cleaner {@link #getReachableServers} (equivalent to availableOnly=true)
* and {@link #getAllServers} API (equivalent to availableOnly=false).
*
* Get the current list of servers.
*
* @param availableOnly if true, only live and available servers should be returned
*/
@Deprecated
public List getServerList(boolean availableOnly);
这个已经标记废弃了,getAllServers这个还在用
/**
* @return All known servers, both reachable and unreachable.
*/
public List getAllServers();
所以我们断定获取所有服务列表,肯定是在这个方法
com.netflix.loadbalancer.BaseLoadBalancer.getAllServers()
@Override
public List getAllServers() {
return Collections.unmodifiableList(allServerList);
}
这里我们要获取列表,所以我们启动两个实例,由于服务刚刚启动完成,还没来得及获取服务列表,
你就来访问就来刷新,所以会遇到这个问题,你看这个日志刚刚报了个错之后,DiscoveryClient打印出的
日志,他去获取所有已经注册了的服务,这个时候才去获取,这个时候我们再来访问,此时能够获取服务列表了,
就是把这个list作为不能再修改的List
localhost:8081/getProductMsg
http://10.40.8.144:8080/msg
这是第一步获取服务列表,我们再来看一下他的负载均衡策略,
/*
* Get the alive server dedicated to key
*
* @return the dedicated server
*/
public Server chooseServer(Object key) {
if (counter == null) {
counter = createCounter();
}
counter.increment();
if (rule == null) {
return null;
} else {
try {
return rule.choose(key);
} catch (Exception e) {
logger.warn("LoadBalancer [{}]: Error choosing server for key {}", name, key, e);
return null;
}
}
}
我们关心的是规则了
protected IRule rule = DEFAULT_RULE;
有个默认的规则
private final static IRule DEFAULT_RULE = new RoundRobinRule();
默认的规则是什么,通过名字也能够看出来吧,就是轮询的方式,那我们可以来测试一下,看到底是不是轮询,把Product
两个都给启动了,为了以示区别
@RestController
public class ClientController {
// @Autowired
// private RestTemplate restTemplate;
@Autowired
private LoadBalancerClient loadBalancerClient;
@GetMapping("/getProductMsg")
public String getProductMsg() {
// 1.第一种方式(直接使用RestTemplate,url写死)
RestTemplate restTemplate = new RestTemplate();
// String response = restTemplate.getForObject("http://localhost:8080/msg", String.class);
// System.out.println(response);
// 第二种方式(利用loadBalancerClient通过应用名获取url,然后再使用restTemplate 注意这里不能用第三种的restTemplate)
ServiceInstance serviceInstance = this.loadBalancerClient.choose("product");
String url = String.format("http://%s:%s", serviceInstance.getHost(),serviceInstance.getPort()+"/msg");
System.out.println(url);
return restTemplate.getForObject(url, String.class);
// 3.第三种方式(利用@LoadBalanced,可以在restTemplate里使用应用名字)
// String response = restTemplate.getForObject("http://PRODUCT/msg", String.class);
// System.out.println(response);
// return response;
}
}
一个是8080,一个是9080
localhost:8080/msg
localhost:9080/msg
可以看到他时轮询出现的,这里有三个负载均衡类,他用的到底是哪一个呢,我们可以重启一下,日志里面也会打印出来,
public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping,
ServerList serverList, ServerListFilter filter,
ServerListUpdater serverListUpdater) {
super(clientConfig, rule, ping);
this.serverListImpl = serverList;
this.filter = filter;
this.serverListUpdater = serverListUpdater;
if (filter instanceof AbstractServerListFilter) {
((AbstractServerListFilter) filter).setLoadBalancerStats(getLoadBalancerStats());
}
restOfInit(clientConfig);
}
就是我们刚刚说到的轮询,那假如我们想改变负载均衡的规则,其实一般情况下我们不需要改变,你基本上用这个
轮询就可以了,假如需要自己定义的时候,直接在配置里面加一个配置就行了,这个配置很长,不建议大家去记,
https://cloud.spring.io/spring-cloud-static/Finchley.SR4/multi/multi_spring-cloud.html
我们搜索Ribbon关键字,看目录里面,最好在目录里面搜到,我们直接通过配置文件就可以配置了
https://cloud.spring.io/spring-cloud-static/Finchley.SR4/multi/multi_spring-cloud-ribbon.html#
_customizing_the_default_for_all_ribbon_clients
16.4 Customizing the Ribbon Client by Setting Properties
users:
ribbon:
NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule
这个就是表示我们要配置的ClassName,
order.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RoundRobinRule
ZoneAvoidanceRule