Ribbon如何实现的负载均衡?
现存两个项目:订单模块一个order-service(8080)
服务和 两个用户服务user-service(8081/8082)
目标:查询订单的时候,将订单所属的用户信息也查询出来,此时order-service就要远程调用user-service的接口,获取到用户信息,然后放入订单订单信息中返回结果,如下:
{
"id": 106,
"price": 544900,
"name": "美的(Midea) 新能效 冷静星II ",
"num": 1,
"userId": 6,
"user": {
"id": 6,
"username": "范兵兵",
"address": "山东省青岛市"
}
}
当order-service发起请求http://localhost:8080/order/102
时,会请求到以上信息,那么如果现在存在两个user-service
,ribbon
是如何实现将请求http://localhost:8080/order/102
转发到其中其中一个user-service上的呢?
Order-service
之前我们已经在order-service
中配置了restTemplat
的bean
了,点击这里可以查看->服务发现
@Bean
@LoadBalanced//标记,请求要被ribbon去进行处理了
public RestTemplate restTemplate(){
return new RestTemplate();
}
可以看到,在注册bean
的时候,我们加上了@LaodBalanced
注解,才会在一个叫做LoadBalancerInterceptor
的拦截器中将order-service
发向user-service
的请求拦截下来,然后再其中做服务的拉取以及负载均衡的策略设置。
LoadBalancerInterceptor
服务拉取
这是一个拦截器,可以看出它继承自ClientHttpRequestInterceptor
,在ClientHttpRequestInterceptor
中,可以看到解释说:它是一个客户端Http拦截器,可以拦截一个客户端的http请求,正好,我们之前注册的restTemplat
正好是一个发送http请求的客户端,在此,所以order-service向user-service发送的客户端请求会被拦截下来。
其中它有一个方法intercept
,在它的实现类LoadBalancerInterceptor
中,实现了该方法,实现的方法如下:
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException{
final URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
return this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
}
其中通过request.getURI();
可以获取到user-service
中配置的服务名称“userservice”的服务信息,至于为什么是“userservice”,因为在一开始的请求中,我们在order-service
的服务层的方法这种写的就是“userservice”->服务发现,需要注意的是,这里的getURI()方法是InterceptingClientHttpRequest
类中的,InterceptingClientHttpRequest
是HttpRequest
的一个实现类
server:
port: 8081
spring:
application:
name: userservice # userservice的服务名称
此时originalUri
中就存在服务userservice
的信息:
再通过originalUri.getHost();
获取到主机名“userservice”
然后再去调用LoadBalancerClient
的实现类RibbonLoadBalancerClient
中execute()
方法:
public < T > T execute(String serviceId, LoadBalancerRequest < T > request, Object hint)
throws IOException{
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
Server server = getServer(loadBalancer, hint);
if(server == null)
{
throw new IllegalStateException("No instances available for " + serviceId);
}
RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server, serviceId), serverIntrospector(serviceId).getMetadata(server));
return execute(serviceId, ribbonServer, request);
}
其中的ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
就是去Eureka
中来去所有服务交易userservice
的服务,放到loadBalancer
对象中的一个叫做allServerList
中:
此时,所有的服务都被拉下来了,就准备选择到底该选择哪一个服务。
负载均衡
服务已经被存入到了allServiceList
中,接下来就是通过Server server = getServer(loadBalancer, hint);
,选择一个配置好的负载均衡策略或者默认的策略,去获取到一个服务。
具体的获取过程如下:
首先是调用RibbonLoadBalancerClient
类中的getServer()
方法:
protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
if (loadBalancer == null) {
return null;
}
// Use 'default' on a null hint, or just pass it on?
return loadBalancer.chooseServer(hint != null ? hint : "default");
}
然后是调用ZoneAwareLoadBalancer
类中的chooseServer()
方法:
public Server chooseServer(Object key) {
if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
logger.debug("Zone aware logic disabled or there is only one zone");
return super.chooseServer(key);
}
...........
}
再去调用父类BaseLoadBalancer
类中的getServer()
方法:
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;
}
}
}
然后发现这里是调用了一个IRule
接口的choose(key)
方法,这里的rule
是什么,来源于在项目在配置的是什么,配置的是啥策略,这里的rule
就是啥策略,默认是轮询:private final static IRule DEFAULT_RULE = new RoundRobinRule();
查看接口IRule
的继承关系,不难看出,这些就是所谓的负载均衡策略,有轮询、随机…
配置策略的方式
配置文件方式
在order-service的application.yml文件中配置:
userservice:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
配置类方式
@Configuration
public class MyRuleConfig {
@Bean
public IRule myRule(){
return new RandomRule();
}
}
加载方式
Ribbon默认是采用懒加载,即第一次访问时才会去创建LoadBalanceClient
,请求时间会很长,而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过下面配置开启饥饿加载:
在order-service的application.yml配置文件中:
ribbon:
eager-load:
enabled: true #开启饥饿加载
clients: #执行饥饿加载的服务名称,这里是一个集合,如果只有一个服务需要开启,就直接在后面写就好了,如果有多个,要按照集合的形式来写 - - -
- userservice