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-serviceribbon是如何实现将请求http://localhost:8080/order/102转发到其中其中一个user-service上的呢?

Order-service

之前我们已经在order-service中配置了restTemplatbean了,点击这里可以查看->服务发现

    @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类中的,InterceptingClientHttpRequestHttpRequest的一个实现类

server:
  port: 8081
spring:
  application:
    name: userservice # userservice的服务名称

此时originalUri中就存在服务userservice的信息:

image-1655125547938

再通过originalUri.getHost();获取到主机名“userservice”

然后再去调用LoadBalancerClient的实现类RibbonLoadBalancerClientexecute()方法:

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中:

image-1655126702927

此时,所有的服务都被拉下来了,就准备选择到底该选择哪一个服务。

负载均衡

服务已经被存入到了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的继承关系,不难看出,这些就是所谓的负载均衡策略,有轮询、随机…

image-1655132725581

image-1655134036771

配置策略的方式

配置文件方式

在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