使用Ribbon实现客户端侧负载均衡

Posted shi_zi_183

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用Ribbon实现客户端侧负载均衡相关的知识,希望对你有一定的参考价值。

使用Ribbon实现客户端侧负载均衡

在生成环境中,各个微服务都会部署多个实例。那么服务消费者要如何将请求分摊到多个服务者实例上呢?

Ribbon简介

Ribbon是Netfix发布的负载均衡器,它有助于控制HTTP的TCP客户端的行为。为Ribbon配置服务提供者地址列表后,Ribbon就可基于某种负载均衡算法,自动地帮助服务消费者去请求。Ribbon默认为我们提供了很多的负载均衡算法,例如轮询、随机等。当然,我们也可为Ribbon实现自定义的负载均衡算法。
在Spring Cloud中,当Ribbon与Eureka配合使用时,Ribbon可自动从Eureka Server获取服务提供者地址列表,并基于负载均衡算法,请求其中一个服务提供者实例。

为服务消费者整合Ribbon

1)复制项目microservice-consumer-movie,将ArtifactId修改为microservice-consumer-movie-ribbon。
2)为项目引入Ribbon的依赖,Ribbon的依赖如下:

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </dependency>

由于前文已为电影服务添加了spring-cloud-starter-netflix-eureka-client,该依赖已包含spring-cloud-starter-netflix-eureka-ribbon,因此无须再次引入。
3)为RestTemplate添加@LoadBalanced注解。

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

只需添加注解@LoadBalanced,就可以为RestTemplate整合Ribbon,使其具备负载均衡的能力。
4)对Controller代码进行修改

package com.example.controller;

import com.example.entity.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.List;

@RestController
public class MovieController {
    private static final Logger LOGGER = LoggerFactory.getLogger(MovieController.class);
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private LoadBalancerClient loadBalancerClient;
    @Value("${user.userServiceUrl}")
    private String userServiceUrl;

    @GetMapping("/user/{id}")
    public User findById(@PathVariable Long id){
        return this.restTemplate.getForObject("http://microservice-provider-user/"+id,User.class);
    }

    @GetMapping("/log-user-instance")
    public void logUserInstance(){
        ServiceInstance serviceInstance = this.loadBalancerClient.choose("microservice-provider-user");
        //打印当前选择的是哪个节点
        MovieController.LOGGER.info("{}:{}:{}",serviceInstance.getServiceId(),serviceInstance.getHost(),serviceInstance.getPort());
    }
}

有代码可知,我们将请求的地址改为"http://microservice-provider-user/"。microservice-provider-user是用户微服务的虚拟主机名,当Ribbon和Eureka配合使用时,会自动将虚拟主机名映射成微服务的网络地址。在新增的logUserInstance()方法中可使用LoadBalancerClient的API更加直观地获取当前选择的用户微服务节点。
测试
1)启动microservice-discovery-eureka。
2)启动2个或更多microservice-provider-user实例。
3)启动microservice-consumer-movie-ribbon。
4)访问http://localhost:8761

5)多次访问http://localhost:8010/user/1

同时两个用户微服务实例都会打印类似如下的日志。

6)多次访问http://localhost:8010/log-user-instance

可以看到,此时请求会均匀分布到两个用户微服务节点上,说明已经实现类负载均衡。

  • 虚拟主机名与虚拟IP非常类似,如果大家接触过HAProxy或Heartbeat,理解虚拟主机名就非常容易了,如果无法理解虚拟主机名,可将其简单理解成为提供者的服务名称,因为在默认情况下,虚拟主机名和服务名称是一致的。当然,也可以使用配置属性eureka.instance.virtual-host-name或者eureka.instance.secure-virtual-host-name指定虚拟主机名。
  • 不能将restTemplate.getForObject(…)与loadBalancerClient.choose(…)写在同一个方法中,两者之间会有冲突——因为此时代码中的restTemplate实际上是一个Ribbon客户端,本身已经包含了"choose"的行为。
  • 虚拟主机名中不能包含"_"之类的字符,否则Ribbon在调用时会报异常。

Ribbon配置自定义

很多场景下,可能根据需要自定义Ribbon的配置,例如修改Ribbon的负载均衡规则等Spring Cloud Edgware允许使用Java代码或属性自定义Ribbon的配置,两种方式是等价的。

使用Java代码自定义Ribbon配置

配置指定名称的Ribbon Client

在Spring Cloud中,Ribbon的默认配置如下(格式是BeanType beanName:ClassName):

  • IClientConfig ribbonClientConfig:DefaultClientConfigImpl
  • IRule ribbonRule:ZoneAvoidanceRule
  • IPing ribbonPing:DummyPing
  • ServerList<Server> ribbonServerList:ConfigurationBasedServerList
  • ServerListFilter<Server> ribbonServerListFilter:ZonePreferenceServerListFilter
  • ILoadBalancer ribbonLoadBalancer:ZoneAwareLoadBalancer
  • ServerListUpdater ribbonServerListUpdater:PollingServerListUpdater
//来自org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration
    @Bean
    @ConditionalOnMissingBean
    public IRule ribbonRule(IClientConfig config) {
        if (this.propertiesFactory.isSet(IRule.class, this.name)) {
            return (IRule)this.propertiesFactory.get(IRule.class, config, this.name);
        } else {
            ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
            rule.initWithNiwsConfig(config);
            return rule;
        }
    }

BeanType是IRule,beanName是ribbonRule,ClassName是ZoneAvoidanceRule,这是一种根据服务提供者所在Zone的性能以及服务提供者可用性综合计算,选择提供者节点的负载均衡规则。
在Spring Cloud中,Ribbon默认的配置类是RibbonClientConfiguration。也可使用一个POJO自定义Ribbon的配置(自定义配置会覆盖默认配置)。这种配置是细粒度的,不同名称的Ribbon客户端使用不同配置。
下面来为名为microservice-provider-user的Ribbon客户端自定义配置。
1)复制项目microservice-consumer-movie-ribbon,将ArtifactId修改为microservice-consumer-movie-ribbon-customizing。
2)创建Ribbon的配置类(该类不能放在ComponentScan扫描目录下,否则该服务访问所有的生产者都会使用该配置)

package com.example.config;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RibbonConfiguration {
    @Bean
    public IRule ribbonRule(){
        // 负载均衡规则,改为随机
        return new RandomRule();
    }
}

3)创建一个空类,并在其上添加@Configuration注解和@RibbonClient注解

package com.example.config;

import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.Configuration;

@Configuration
@RibbonClient(name = "microservice-provider-user",configuration = RibbonConfiguration.class)
public class TestConfiguration {
}

由代码可知,使用@RibbonClient注解的configuration属性,即可自定义指定名称Ribbon客户端的配置。
测试
1)启动microservice-discovery-eureka
2)启动2个或更多microservice-provider-user实例。
3)启动microservice-consumer-movie-ribbon-customizing。
4)多次访问http://localhost:8010/log-user-instance

注:必须注意的是,本例中的RibbonConfiguration类不能存放在主应用程序上下文的@ComponentScan所扫描的包中,否则该类中的配置信息将被所有的@RibbonClient共享。
因此,如果只想自定义某一个Ribbon客户端的配置,必须防止@Configuration注解的类所在的包与@ComponentScan扫描的包重叠,或应显示指定@ComponentScan不扫描@Configuration类所在的包。

全局配置

可使用@RibbonClients注解为所有Ribbon Client提供默认配置

package com.ribbon;

import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.ConfigurationBasedServerList;
import com.ribbon.DefaultRibbonConfig;
import org.springframework.cloud.netflix.ribbon.RibbonClients;

@Configuration@RibbonClients(defaultConfiguration = DefaultRibbonConfig.class)
public class RibbonClientDefaultConfigurationTestsConfig {
    public static class BazServiceList extends ConfigurationBasedServerList {
        public BazServiceList(IClientConfig config) {
            super.initWithNiwsConfig(config);
        }
    }
}
package com.example.config;

import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class DefaultRibbonConfig {
    @Bean
    public IRule ribbonRule() {
        return new BestAvailableRule();
    }

    @Bean
    public IPing ribbonPing() {
        return new PingUrl();
    }

    @Bean
    public ServerList<Server> ribbonServerList(IClientConfig config) {
        return new RibbonClientDefaultConfigurationTestsConfig.BazServiceList(config);
    }

    @Bean
    public ServerListSubsetFilter serverListSubsetFilter() {
        ServerListSubsetFilter filter = new ServerListSubsetFilter();
        return filter;
    }
}

使用属性自定义Ribbon配置

从Spring Cloud Netfix 1.2.0开始(即从Spring Cloud Camden RELEASE开始),Ribbo支持使用属性自定义。这种方式比使用Java代码配置的方式更加方便。
支持的属性如下,配置的前缀是<clientName>.ribbon。其中<clientName>是RibbonClient的名称,如果省略,则表示全局配置。

  • NFLoadBalancerClassName:配置IloadBalancer的实现类
  • NFLoadBalancerRuleClassName:配置IRule的实现类
  • NFLoadBalancerPingClassName:配置IPing的实现类
  • NIWSServerListClassName:配置ServerList的实现类
  • NIWSServerListFilterClassName:配置ServerListFilter的实现类
    下面,我们来用属性来修改名为microservice-provider-user的Ribbon Client的负载均衡规则。
    复制项目microservice-consumer-movie-ribbon,将ArtifactId修改为microservice-consumer-movie-ribbon-customizing-properties。在项目的application.yml中添加以下内容即可。
microservice-provider-user:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

这样,就可以将名为microservice-provider-user的Ribbon Client的负载均衡规则设为随机。
若配置成如下形式,则表示对所有Ribbon Client都是用RandomRule;

 ribbon:
   NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

测试
1)启动microservice-discovery-eureka
2)启动2个或更多microservice-provider-user实例。
3)启动microservice-consumer-movie-ribbon-customizing-properties。
4)多次访问http://localhost:8010/log-user-instance

注:属性配置的方式比Java代码配置的方式优先级更高

脱离Eureka使用Ribbon

在前文的实例中,是将Ribbon与Eureka配合使用的,但现实中可能不具备这样的条件,例如一些遗留的微服务,它们可能并没有注册到Eureka Server上,甚至根本不是使用Spring Cloud开发的,此时要想使用Ribbon实现负载均衡,要怎么办?
Ribbon支持脱离Eureka使用。
1)复制项目microservice-consumer-movie-ribbon,将ArtifactId修改为microservice-consumer-movie-without-eureka。在项目的application.yml中添加以下内容即可
2)去掉项目中Eureka依赖,只使用spring-cloud-starter-netflix-ribbon
pom.xml,找到

		<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

修改为

		<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-ribbon</artifactId>
        </dependency>

3)去掉启动类上的@EnableDiscoveryClient(如果有的话)
4)将application.yml改为

server:
  port: 8010
spring:
  application:
    name: microservice-consumer-movie
microservice-provider-user:
  ribbon:
    listOfServers: localhost:8000,localhost:8001,localhost:8002

测试
1)启动2个或更多microservice-provider-user实例。
2)启动microservice-consumer-movie-without-eureka。
3)多次访问http://localhost:8010/log-user-instance

注:当Eureka Client依赖在项目的classpath下时,如果想单独使用Ribbon,而不是使用Eureka的服务发现功能,需添加配置ribbon.eureka.enabled=false。某些场景下,我们可能只想让指定名称的Ribbon Client去使用指定的URL请求,其他Ribbon Client依旧与Eureka配合使用,那么可配置:

<clientName>:
	ribbon:
		NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList
microservice-provider-user:
  ribbon:
    listOfServers: localhost:8000,localhost:8001,localhost:8002

这样,对于名为<clientName>的Ribbon Client,即可从地址列表中选择地址去请求,而其他名称的Ribbon Client依旧可与Eureka配合使用——自动从Eureka Server获得目标服务的地址,并选择一个去请求。

饥饿加载

Spring Cloud会为每个名称的Ribbon Client维护一个子应用程序上下文,这个上下文默认是懒加载的。指定名称的RibbonClient第一次请求时,对应的上下文才会被加载,因此,首次请求往往会比较慢。从Spring Cloud Dalston开始,我们可配置饥饿加载。

ribbon:
eager-load:
	enabled:true
		client: client1,client2

这样,对于名为client1、client2的Ribbon Client,将在启动时就加载对应的子应用程序上下文,从而提高首先请求的访问速度。

以上是关于使用Ribbon实现客户端侧负载均衡的主要内容,如果未能解决你的问题,请参考以下文章

使用Ribbon实现客户端侧负载均衡

springCloud:Ribbon实现客户端侧负载均衡-消费者整合Ribbon

2.Spring Cloud Alibaba实现负载均衡-Ribbon

0403-Ribbon的基本使用

ribbon源码之概述

springcloud-04-自定义ribbon的配置方式