Spring Cloud Ribbon(负载均衡器)

1. 介绍

目前主流的负载方案分为以下两种:

  • 集中式负载均衡,在消费者和服务提供方中间使用独立的代理方式进行负载,有硬件的(比如 F5),也有软件的(比如 Nginx)。
  • 客户端自己做负载均衡,根据自己的请求情况做负载,Ribbon 就属于客户端自己做负载。

Spring Cloud Ribbon 是一个基于 HTTP 和 TCP 的客户端负载均衡工具,它基于 Netflix Ribbon 实现。通过 Spring Cloud 的封装,可以让我们轻松地将面向服务的 REST 模版请求自动转换成客户端负载均衡的服务调用。

Spring Cloud Ribbon 虽然只是一个工具类框架,它不像服务注册中心、配置中心、API 网关那样需要独立部署,但是它几乎存在于每一个 Spring Cloud 构建的微服务和基础设施中。因为微服务间的调用,API 网关的请求转发等内容,实际上都是通过 Ribbon 来实现的(https://github.com/Netflix/ribbon)。

2. Ribbon 模块

Ribbon 模块介绍如下表所示。
image

3. Ribbon 使用

我们使用 Ribbon 来实现一个最简单的负载均衡调用功能,接口就用《使用Eureka编写服务提供者》提供的 /user/hello 接口,需要启动两个服务,一个是 8081 的端口,一个是 8083 的端口。

然后创建一个新的 Maven 项目 ribbon-native-demo,在项目中集成 Ribbon,在 pom.xml 中添加如下代码所示的依赖。

<dependency>
    <groupId>com.netflix.ribbon</groupId>
    <artifactId>ribbon</artifactId>
    <version>2.2.2</version>
</dependency>
<dependency>
    <groupId>com.netflix.ribbon</groupId>
    <artifactId>ribbon-core</artifactId>
    <version>2.2.2</version>
</dependency>
<dependency>
    <groupId>com.netflix.ribbon</groupId>
    <artifactId>ribbon-loadbalancer</artifactId>
    <version>2.2.2</version>
</dependency>
<dependency>
    <groupId>io.reactivex</groupId>
    <artifactId>rxjava</artifactId>
    <version>1.0.10</version>
</dependency>

接下来我们编写一个客户端来调用接口,代码如下所示。

import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.LoadBalancerBuilder;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.reactive.LoadBalancerCommand;
import com.netflix.loadbalancer.reactive.ServerOperation;
import javafx.application.Application;
import org.assertj.core.util.Lists;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import rx.Observable;

import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;

/**
 * @USER:
 * @DATE: 2021-06-17
 * @description:功能描述
 */

public class TestRibbon {

    @Test
    public void test(){
        //服务列表
        List<Server> serverList = Lists.newArrayList(new Server("127.0.0.1",8081),new Server("127.0.0.1",8083));
        //构建负载实例
        ILoadBalancer loadBalancer = LoadBalancerBuilder.newBuilder().buildFixedServerListLoadBalancer(serverList);
        //调用5次来测试效果
        for(int i = 0 ;i<5;i++){
            String result = LoadBalancerCommand.<String>builder().withLoadBalancer(loadBalancer).build()
                    .submit(new ServerOperation<String>() {
                        @Override
                        public Observable<String> call(Server server) {
                            try {
                                String addr = "http://"+server.getHost()+":"+server.getPort()+"/user/hello";
                                System.out.println("调用地址:"+addr);
                                URL url = new URL(addr);
                                HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                                connection.setRequestMethod("GET");
                                connection.connect();
                                InputStream in = connection.getInputStream();
                                byte[] data = new byte[in.available()];
                                in.read(data);
                                return Observable.just(new String(data));
                            }catch (Exception e){
                                return Observable.error(e);
                            }

                        }
                    }).toBlocking().first();
            System.out.println("调用结果:"+result);

        }
    }
}

4. Spring Cloud Ribbon结合RestTemplate实现负载均衡

Spring 提供了一种简单便捷的模板类来进行 API 的调用,那就是 RestTemplate。

4.1 使用 RestTemplate

首先我们来看看 GET 请求的使用方式:创建一个新的项目 spring-rest-template,配置好 RestTemplate:

package com.yxkj.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

/**
 * @USER: 文 俊
 * @DATE: 2021-06-17
 * @description:功能描述
 */
@Configuration
public class BeanConfiguration {

    @Bean
    public RestTemplate getResetTemplate(){
        return new RestTemplate();
    }
}

新建一个 HouseController,并增加两个接口,一个通过 @RequestParam 来传递参数,返回一个对象信息;另一个通过 @PathVariable 来传递参数,返回一个字符串。请尽量通过两个接口组装不同的形式,具体代码如下所示。

@GetMapping("/house/data")
public HouseInfo getData(@RequestParam("name") String name) {
    return new HouseInfo(1L, "上海" "虹口" "东体小区");
}
@GetMapping("/house/data/{name}")
public String getData2(@PathVariable("name") String name) {
    return name;
}

新建一个 HouseClientController 用于测试,使用 RestTemplate 来调用我们刚刚定义的两个接口,代码如下所示。

@GetMapping("/call/data")
public HouseInfo getData(@RequestParam("name") String name) {
    return restTemplate.getForObject( "http://localhost:8081/house/data?name="+ name, HouseInfo.class);
}
@GetMapping("/call/data/{name}")
public String getData2(@PathVariable("name") String name) {
    return restTemplate.getForObject( "http://localhost:8081/house/data/{name}", String.class, name);
}

获取数据结果可通过 RestTemplate 的 getForObject 方法(如下代码所示)来实现,此方法有三个重载的实现:

  • url:请求的 API 地址,有两种方式,其中一种是字符串,另一种是 URI 形式。
  • responseType:返回值的类型。
  • uriVariables:PathVariable 参数,有两种方式,其中一种是可变参数,另一种是 Map 形式。
public <T> T getForObject(String url, Class<T> responseType,
                          Object... uriVariables);
public <T> T getForObject(String url, Class<T> responseType,
                          Map<String, ?> uriVariables);
public <T> T getForObject(URI url, Class<T> responseType);

了 getForObject,我们还可以使用 getForEntity 来获取数据,代码如下所示。

@GetMapping("/call/dataEntity")
public HouseInfo getData(@RequestParam("name") String name) {
    ResponseEntity<HouseInfo> responseEntity = restTemplate
            .getForEntity("http://localhost:8081/house/data?name=" + name, HouseInfo.class);
    if (responseEntity.getStatusCodeValue() == 200) {
        return responseEntity.getBody();
    }
    return null;
}

getForEntity 中可以获取返回的状态码、请求头等信息,通过 getBody 获取响应的内容。其余的和 getForObject 一样,也是有 3 个重载的实现。

接下来看看怎么使用 POST 方式调用接口。在 HouseController 中增加一个 save 方法用来接收 HouseInfo 数据,代码如下所示。

@PostMapping("/house/save")
public Long addData(@RequestBody HouseInfo houseInfo) {
    System.out.println(houseInfo.getName());
    return 1001L;
}

接着写调用代码,用 postForObject 来调用,代码如下所示。

@GetMapping("/call/save")
public Long add() {
    HouseInfo houseInfo = new HouseInfo();
    houseInfo.setCity("上海");
    houseInfo.setRegion("虹口");
    houseInfo.setName("×××");
    Long id = restTemplate.postForObject("http://localhost:8081/house/save", houseInfo, Long.class);
    return id;
}

postForObject 同样有 3 个重载的实现。除了 postForObject 还可以使用 postForEntity 方法,用法都一样,代码如下所示。

public <T> T postForObject(String url, Object request,
                           Class<T> responseType, Object... uriVariables);

public <T> T postForObject(String url, Object request,
                           Class<T> responseType, Map<String, ?> uriVariables);

public <T> T postForObject(URI url, Object request, Class<T> responseType);

除了 get 和 post 对应的方法之外,RestTemplate 还提供了 put、delete 等操作方法,还有一个比较实用的就是 exchange 方法。exchange 可以执行 get、post、put、delete 这 4 种请求方式。更多地使用方式大家可以自行学习。

4.2 整合 Ribbon

在 Spring Cloud 项目中集成 Ribbon 只需要在 pom.xml 中加入下面的依赖即可,其实也可以不用配置,因为 Eureka 中已经引用了 Ribbon,代码如下所示。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
4.3 RestTemplate 负载均衡示例

前面我们调用接口都是通过具体的接口地址来进行调用,RestTemplate 可以结合 Eureka 来动态发现服务并进行负载均衡的调用。
修改 RestTemplate 的配置,增加能够让 RestTemplate 具备负载均衡能力的注解 @LoadBalanced。代码如下所示。

@Configuration
public class BeanConfiguration {
    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
}

修改接口调用的代码,将 IP+PORT 改成服务名称,也就是注册到 Eureka 中的名称,代码如下所示。

  @GetMapping(value = "/call/data")
    public HouseInfo getData(@RequestParam("name") String name){
        return restTemplate.getForObject("http://RESTTEMPLATE/house/data?name="+name,HouseInfo.class);
    }

接口调用的时候,框架内部会将服务名称替换成具体的服务 IP 信息,然后进行调用。

4.4 @LoadBalanced 注解原理

相信大家一定有一个疑问:为什么在 RestTemplate 上加了一个 @LoadBalanced 之后,RestTemplate 就能够跟 Eureka 结合了,不但可以使用服务名称去调用接口,还可以负载均衡?

应该归功于 Spring Cloud 给我们做了大量的底层工作,因为它将这些都封装好了,我们用起来才会那么简单。框架就是为了简化代码,提高效率而产生的。

这里主要的逻辑就是给 RestTemplate 增加拦截器,在请求之前对请求的地址进行替换,或者根据具体的负载策略选择服务地址,然后再去调用,这就是 @LoadBalanced 的原理。

下面我们来实现一个简单的拦截器,看看在调用接口之前会不会进入这个拦截器。我们不做任何操作,就输出一句话,证明能进来就行了。具体代码如下所示

package com.yxkj.interceptor;

import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequestFactory;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.util.Assert;

import java.io.IOException;
import java.net.URI;
import java.net.URL;

/**
 * @USER: 
 * @DATE: 2021-06-17
 * @description:功能描述
 */
public class MyLoadBalancerInterceptor implements ClientHttpRequestInterceptor {

    private LoadBalancerClient loadBalancerClient;
    private LoadBalancerRequestFactory requestFactory;

    public MyLoadBalancerInterceptor(LoadBalancerClient loadBalancer,LoadBalancerRequestFactory requestFactory){
        this.loadBalancerClient = loadBalancer;
        this.requestFactory = requestFactory;
    }
    public MyLoadBalancerInterceptor(LoadBalancerClient loadBalancer){
        this(loadBalancer,new LoadBalancerRequestFactory(loadBalancer));
    }

    public MyLoadBalancerInterceptor() {

    }

    @Override
    public ClientHttpResponse intercept( HttpRequest request, byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
         URI url = request.getURI();
         String serviceName = url.getHost();
        System.out.println("进入自定义的请求拦截器:"+serviceName);
        Assert.state(serviceName!=null,"Request URI does not contain a valid hostname:"+url);
        return this.loadBalancerClient.execute(serviceName,requestFactory.createRequest(request,bytes,clientHttpRequestExecution));
    }
}

拦截器设置好了之后,我们再定义一个注解,并复制 @LoadBalanced 的代码,改个名称就可以了,代码如下所示。

@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface MyLoadBalanced {
}

然后定义一个配置类,给 RestTemplate 注入拦截器,代码如下所示。

@Configuration
public class MyLoadBalancerAutoConfiguration {
    @MyLoadBalanced
    @Autowired(required = false)
    private List<RestTemplate> restTemplates = Collections.emptyList();
    @Bean
    public MyLoadBalancerInterceptor myLoadBalancerInterceptor() {
        return new MyLoadBalancerInterceptor();
    }
    @Bean
    public SmartInitializingSingleton myLoadBalancedRestTemplateInitializer() {
        return new SmartInitializingSingleton() {
          @Override
          public void afterSingletonsInstantiated() {
            for (RestTemplate restTemplate : MyLoadBalancerAutoConfiguration.this.restTemplates){
                List<ClientHttpRequestInterceptor> list = new ArrayList<>(restTemplate.getInterceptors());
                list.add(myLoad BalancerInterceptor());
                restTemplate.setInterceptors(list);
            }
          }
        };
    }
}

维护一个 @MyLoadBalanced 的 RestTemplate 列表,在 SmartInitializingSingleton 中对 RestTemplate 进行拦截器设置。
然后改造我们之前的 RestTemplate 配置,将 @LoadBalanced 改成我们自定义的 @MyLoadBalanced,代码如下所示。

@Bean
//@LoadBalanced
@MyLoadBalanced
public RestTemplate getRestTemplate() {
    return new RestTemplate();
}

重启服务,访问服务中的接口就可以看到控制台的输出了,这证明在接口调用的时候会进入该拦截器,输出如下:

进入自定义的请求拦截器中 ribbon-eureka-demo

通过这个小案例我们就能够清楚地知道 @LoadBalanced 的工作原理。接下来我们来看看源码中是怎样的一个逻辑。

首先看配置类,如何为 RestTemplate 设置拦截器,代码在 spring-cloud-commons.jar 中的 org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration 类里面通过查看 LoadBalancerAutoConfiguration 的源码,可以看到这里也是维护了一个 @LoadBalanced 的 RestTemplate 列表,代码如下所示。

@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializer(final List<RestTemplateCustomizer> customizers) {
    return new SmartInitializingSingleton() {
        @Override
        public void afterSingletonsInstantiated() {
            for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
                for (RestTemplateCustomizer customizer : customizers) {
                    customizer.customize(restTemplate);
                }
            }
        }
    };
}

通过查看拦截器的配置可以知道,拦截器用的是 LoadBalancerInterceptor,RestTemplate Customizer 用来添加拦截器,代码如下所示。

@Configuration
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {
    @Bean
    public LoadBalancerInterceptor ribbonInterceptor(LoadBalancerClient loadBalancerClient,
            LoadBalancerRequestFactory requestFactory) {
        return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
    }
    @Bean
    @ConditionalOnMissingBean
    public RestTemplateCustomizer restTemplateCustomizer(
            final LoadBalancerInterceptor loadBalancerInterceptor) {
        return new RestTemplateCustomizer() {
            @Override
            public void customize(RestTemplate restTemplate) {
                List<ClientHttpRequestInterceptor> list = new ArrayList<>(
                  restTemplate.getInterceptors());
                list.add(loadBalancerInterceptor);
                restTemplate.setInterceptors(list);
            }
        };
    }
}

拦截器的代码在 org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor 中,代码如下所示。

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
    private LoadBalancerClient loadBalancer;
    private LoadBalancerRequestFactory requestFactory;
    public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) {
        this.loadBalancer = loadBalancer;
        this.requestFactory = requestFactory;
    }
    public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
        this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
    }
    @Override
    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, requestFactory.createRequest(request, body, execution));
    }
}

主要的逻辑在 intercept 中,执行交给了 LoadBalancerClient 来处理,通过 LoadBalancer RequestFactory 来构建一个 LoadBalancerRequest 对象,代码如下所示。

public LoadBalancerRequest<ClientHttpResponse> createRequest(final HttpRequest request, final byte[] body,
        final ClientHttpRequestExecution execution) {
    return new LoadBalancerRequest<ClientHttpResponse>() {
        @Override
        public ClientHttpResponse apply(final ServiceInstance instance) throws Exception {
            HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance, loadBalancer);
            if (transformers != null) {
                for (LoadBalancerRequestTransformer transformer : transformers) {
                    serviceRequest = transformer.transformRequest(serviceRequest,instance);
                }
            }
            return execution.execute(serviceRequest, body);
        }
    };
}

createRequest 中通过 ServiceRequestWrapper 来执行替换 URI 的逻辑,ServiceRequest Wrapper 中将 URI 的获取交给了 org.springframework.cloud.client.loadbalancer.LoadBalancer Client#reconstructURI 方法。

以上就是整个 RestTemplate 结合 @LoadBalanced 的执行流程,至于具体的实现大家可以自己去研究,这里只介绍原理及整个流程。

4.5 Ribbon API 使用

当你有一些特殊的需求,想通过 Ribbon 获取对应的服务信息时,可以使用 Load-Balancer Client 来获取,比如你想获取一个 ribbon-eureka-demo 服务的服务地址,可以通过 LoadBalancerClient 的 choose 方法来选择一个:

  @Autowired
    private LoadBalancerClient loadBalancerClient;

    @GetMapping("/choose")
    public Object cho0seUrl(){
        ServiceInstance instance = loadBalancerClient.choose("RESTTEMPLATE");
        return  instance;
    }

访问接口,可以看到返回的信息如下:

{
    "serviceId":"RESTTEMPLATE",
    "server":{
        "host":"192.168.227.150",
        "port":8085,
        "scheme":null,
        "id":"192.168.227.150:8085",
        "zone":"defaultZone",
        "readyToServe":true,
        "instanceInfo":{
            "instanceId":"resttemplate:127.0.0.1:8085",
            "app":"RESTTEMPLATE",
            "appGroupName":null,
            "ipAddr":"192.168.227.150",
            "sid":"na",
            "homePageUrl":"http://192.168.227.150:8085/",
            "statusPageUrl":"http://192.168.227.150:8085/actuator/info",
            "healthCheckUrl":"http://192.168.227.150:8085/actuator/health",
            "secureHealthCheckUrl":null,
            "vipAddress":"resttemplate",
            "secureVipAddress":"resttemplate",
            "countryId":1,
            "dataCenterInfo":{
                "@class":"com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo",
                "name":"MyOwn"
            },
            "hostName":"192.168.227.150",
            "status":"UP",
            "overriddenStatus":"UNKNOWN",
            "leaseInfo":{
                "renewalIntervalInSecs":90,
                "durationInSecs":90,
                "registrationTimestamp":1623911515196,
                "lastRenewalTimestamp":1623914305339,
                "evictionTimestamp":0,
                "serviceUpTimestamp":1623908454192
            },
            "isCoordinatingDiscoveryServer":false,
            "metadata":{
                "management.port":"8085"
            },
            "lastUpdatedTimestamp":1623911515196,
            "lastDirtyTimestamp":1623911515136,
            "actionType":"ADDED",
            "asgName":null
        },
        "metaInfo":{
            "instanceId":"resttemplate:127.0.0.1:8085",
            "serverGroup":null,
            "appName":"RESTTEMPLATE",
            "serviceIdForDiscovery":"resttemplate"
        },
        "alive":true,
        "hostPort":"192.168.227.150:8085"
    },
    "secure":false,
    "metadata":{
        "management.port":"8085"
    },
    "scheme":null,
    "host":"192.168.227.150",
    "port":8085,
    "uri":"http://192.168.227.150:8085"
}

4.6 Ribbon 饥饿加载

笔者从网上看到很多博客中都提到过的一种情况:在进行服务调用的时候,如果网络情况不好,第一次调用会超时。有很多大神对此提出了解决方案,比如把超时时间改长一点、禁用超时等。

超时的问题也是一样,Ribbon 的客户端是在第一次请求的时候初始化的,如果超时时间比较短的话,初始化 Client 的时间再加上请求接口的时间,就会导致第一次请求超时。

本教程是基于 Finchley.RELEAS 撰写的,这个版本已经提供了一种针对上述问题的解决方法,那就是 eager-load 方式。通过配置 eager-load 来提前初始化客户端就可以解决这个问题。

#Ribbon 饥饿加载
      ribbon:
        eager-load:
          enabled: true
          clients: RESTTEMPLATE

5. Spring Cloud Ribbon配置详解

常用配置

5.1 禁用 Eureka

当我们在 RestTemplate 上添加 @LoadBalanced 注解后,就可以用服务名称来调用接口了,当有多个服务的时候,还能做负载均衡。
这是因为 Eureka 中的服务信息已经被拉取到了客户端本地,如果我们不想和 Eureka 集成,可以通过下面的配置方法将其禁用。

# 禁用 Eureka
ribbon.eureka.enabled=false

当我们禁用了 Eureka 之后,就不能使用服务名称去调用接口了,必须指定服务地址。

5.2 2 配置接口地址列表

上面我们讲了可以禁用 Eureka,禁用之后就需要手动配置调用的服务地址了,配置如下:

5.2.3 禁用 Eureka 后手动配置服务地址

ribbon-config-demo.ribbon.listOfServers=localhost:8081,localhost:8083
这个配置是针对具体服务的,前缀就是服务名称,配置完之后就可以和之前一样使用服务名称来调用接口了。

5.2.4 配置负载均衡策略

Ribbon 默认的策略是轮询,从我们前面讲解的例子输出的结果就可以看出来,Ribbon 中提供了很多的策略,这个在后面会进行讲解。我们通过配置可以指定服务使用哪种策略来进行负载操作。

5.2.5 超时时间

Ribbon 中有两种和时间相关的设置,分别是请求连接的超时时间和请求处理的超时时间,设置规则如下:

# 请求连接的超时时间
ribbon.ConnectTimeout=2000
# 请求处理的超时时间
ribbon.ReadTimeout=5000

也可以为每个Ribbon客户端设置不同的超时时间, 通过服务名称进行指定:
ribbon-config-demo.ribbon.ConnectTimeout=2000
ribbon-config-demo.ribbon.ReadTimeout=5000
5.2.5 并发参数
# 最大连接数
ribbon.MaxTotalConnections=500
# 每个host最大连接数
ribbon.MaxConnectionsPerHost=500

6 代码配置 Ribbon

配置 Ribbon 最简单的方式就是通过配置文件实现。当然我们也可以通过代码的方式来配置。

通过代码方式来配置之前自定义的负载策略,首先需要创建一个配置类,初始化自定义的策略,代码如下所示。

@Configuration
public class BeanConfiguration {
    @Bean
    public MyRule rule() {
        return new MyRule();
    }
}

创建一个 Ribbon 客户端的配置类,关联 BeanConfiguration,用 name 来指定调用的服务名称,代码如下所示。

@RibbonClient(name = "ribbon-config-demo", configuration = BeanConfiguration.class)
public class RibbonClientConfig {
}

可以去掉之前配置文件中的策略配置,然后重启服务,访问接口即可看到和之前一样的效果。

7 配置文件方式配置 Ribbon

除了使用代码进行 Ribbon 的配置,我们还可以通过配置文件的方式来为 Ribbon 指定对应的配置:

<clientName>.ribbon.NFLoadBalancerClassName: Should implement ILoadBalancer(负载均衡器操作接口)
<clientName>.ribbon.NFLoadBalancerRuleClassName: Should implement IRule(负载均衡算法)
<clientName>.ribbon.NFLoadBalancerPingClassName: Should implement IPing(服务可用性检查)
<clientName>.ribbon.NIWSServerListClassName: Should implement ServerList(服务列表获取)
<clientName>.ribbon.NIWSServerListFilterClassName: Should implement ServerList­Filter(服务列表的过滤)

8 重试机制

8.1 RetryRule 重试

解决上述问题,最简单的方法就是利用 Ribbon 自带的重试策略进行重试,此时只需要指定某个服务的负载策略为重试策略即可:

ribbon-config-demo.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RetryRule
8.2 Spring Retry 重试

除了使用 Ribbon 自带的重试策略,我们还可以通过集成 Spring Retry 来进行重试操作。

在 pom.xml 中添加 Spring Retry 的依赖,代码如下所示。

<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
</dependency>

配置重试次数等信息:

# 对当前实例的重试次数
ribbon.maxAutoRetries=1
# 切换实例的重试次数
ribbon.maxAutoRetriesNextServer=3
# 对所有操作请求都进行重试
ribbon.okToRetryOnAllOperations=true
# 对Http响应码进行重试
ribbon.retryableStatusCodes=500,404,502
posted @ 2021-06-17 10:16  风飘落叶  阅读(219)  评论(0编辑  收藏  举报