目录

Ribbon负载均衡

流水不争先,争得是滔滔不绝

负载均衡原理

Spring Cloud底层是利用了一个叫Ribbon的组件,来实现负载均衡功能的。流程如下图:

https://s2.loli.net/2022/11/28/rqASJIVz9xbUd2P.png

跟踪源码

那么为什么用RestTemplate发出的请求明明是http://userservice/user/,却变成了http://localhost:8081呢?

很明显是有什么根据服务名称,获取到了服务实例的ip和端口。它就是LoadBalancerInterceptor,这个类会对有@LoadBalanced注解的类发送HTTP请求时进行拦截,然后从Eureka中根据服务id(也就是服务名称)获取服务列表,再利用负载均衡算法得到真实的服务地址信息,用真实的ip和端口替换服务名称。

1.LoadBalancerInterceptor

1
2
3
4
5
6
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
    URI originalUri = request.getURI();
    String serviceName = originalUri.getHost();
    Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
    return (ClientHttpResponse)this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
}

可以看到这里的intercept方法,拦截了用户的HttpRequest请求,然后做了几件事:

  • request.getURI():获取请求uri,比如order-service发送的http://userservice/user/102请求
  • originalUri.getHost():获取uri路径的主机名,也就是服务名userservice
  • this.loadBalancer.execute():处理服务和用户请求

这里的this.loadBalancerLoadBalancerClient类型,我们继续跟入。

2.LoadBalancerClient

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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);
}

代码是这样的:

  • getLoadBalancer(serviceId):根据服务id获取ILoadBalancer,而ILoadBalancer会拿着服务id去eureka中获取服务列表并保存起来。
  • getServer(loadBalancer, hint):利用内置的负载均衡算法,从服务列表中选择一个服务。

3.负载均衡策略IRule

在上面的代码中,可以看到获取服务是通过一个getServer方法来做负载均衡,继续跟入进去:

1
2
3
4
5
6
7
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");
}

继续进入chooseServer方法,发现:

https://s2.loli.net/2022/11/28/VotzyAGl3Knekjf.png

选择服务的是这个rule,而这个rule的默认值是一个RoundRobinRule

https://s2.loli.net/2022/11/28/hVL5nOU67GwPpj1.png

再看这个RoundRobinRule类的介绍:

https://s2.loli.net/2022/11/28/U5M24lYFNsLZejq.png

到这里,整个负载均衡的流程就很清楚了。

4.总结

https://s2.loli.net/2022/11/28/G6XbwEjaI9PYurO.png

基本流程:

  • 拦截带有@LoadBalanced注解RestTemplate的请求http://userservice/user/1
  • RibbonLoadBalancerClient从请求url中获取服务名称,也就是userservice
  • DynamicServerListLoadBalancer根据userservice到eureka拉取服务列表
  • eureka返回列表,localhost:8081、localhost:8082
  • IRule利用内置负载均衡规则,从列表中选择一个,例如localhost:8081
  • RibbonLoadBalancerClient修改请求地址,用localhost:8081替代userservice,得到http://localhost:8081/user/1,发起真实请求

负载均衡策略

Ribbon的负载均衡规则是一个叫做IRule的接口来定义的,每一个子接口都是一种规则

https://s2.loli.net/2022/11/28/FmDgucWM2U47ZHp.png

不同规则的含义如下:

https://s2.loli.net/2023/01/19/bvr5xlkapIeSDRO.png

默认的实现是ZoneAvoidanceRule,一种轮询方案。

1.修改负载均衡策略

通过定义IRule实现可以修改负载均衡规则,有两种方式:

1、代码方式:定义一个新的IRule,这种方法会给所有发送的微服务请求指定负载均衡规则

1
2
3
4
@Bean
public IRule randomRule() {
    return new RandomRule();
}

2、配置文件方式:给某个微服务指定负载均衡规则

1
2
3
userservice: #指定某个微服务配置负载均衡规则
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule  #负载均衡规则
注意
一般用默认的负载均衡规则就好,不用修改

饥饿加载

Ribbon默认是采用懒加载,即第一次访问时才会去创建LoadBalanceClient,请求时间会很长。

而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过下面的配置可以开启饥饿加载:

1
2
3
4
5
#指定单个服务开启饥饿加载
ribbon:
  eager-load:
    enabled: true #开启饥饿加载
    clients: userservice #指定饥饿加载的服务名称
1
2
3
4
5
6
7
#指定多个服务开启饥饿加载
ribbon:
  eager-load:
    enabled: true #开启饥饿加载
    clients: #指定饥饿加载的服务名称
     - 服务名称1
     - 服务名称2