loadbalancer自定义重试机制

背景

  接着上篇文章讲的@FeignClient上层实现自定义重试机制,同一个接口同一个请求在一个节点请求失败,并不会自动切换到另一个节点,虽然Feign的地层robbin默认采用轮询进行负载均衡,打断点也会切换,但是压测发现可能同一个请求刚好都落到不可用的服务上,直到重试的次数耗尽,还是可能出现丢数据的情况。下面介绍一种通过loadbalancer进行自定义重试。

实现

1、配置文件

spring:
  cloud:
    loadbalancer:
      retry:
        enabled: true
        initial-interval: 1000 # 重试的初始间隔时间(毫秒)
        max-interval: 2000 # 最大重试间隔时间(毫秒)
        multiplier: 1.5 # 间隔增加的乘数
        max-attempts: 2 # 最大重试次数
        retry-on-all-operations: true
        max-retries-on-next-service-instance: 1 #切换实例的重试次数,即当请求一个服务实例失败时,会尝试切换到另一个服务实例并重试的次数。不包括首次调用所选的实例
        max-retries-on-same-service-instance: 0 #对当前实例重试的次数,即当请求一个服务实例失败时,会再次尝试向该实例发送请求的次数。不包括首次调用
        retryable-status-codes: 505

2、在项目下创建org.springframework.cloud.openfeign.loadbalancer包LoadBalancerUtils类 ,请求失败根据自定义的code重试

package org.springframework.cloud.openfeign.loadbalancer;
import java.io.IOException;
import java.net.ConnectException;
import java.net.URI;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Set;
import feign.Client;
import feign.Request;
import feign.Response;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.CompletionContext;
import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycle;
import org.springframework.cloud.client.loadbalancer.RequestData;
import org.springframework.cloud.client.loadbalancer.ResponseData;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;

/**
 * @author Olga Maciaszek-Sharma
 *
 * A utility class for handling {@link LoadBalancerLifecycle} calls.
 */
@SuppressWarnings({ "unchecked", "rawtypes" })
final class LoadBalancerUtils {
    private LoadBalancerUtils() {
        throw new IllegalStateException("Can't instantiate a utility class");
    }
    static Response executeWithLoadBalancerLifecycleProcessing(Client feignClient, Request.Options options,Request feignRequest, org.springframework.cloud.client.loadbalancer.Request lbRequest,org.springframework.cloud.client.loadbalancer.Response<ServiceInstance> lbResponse,                                   Set<LoadBalancerLifecycle> supportedLifecycleProcessors, boolean loadBalanced, boolean useRawStatusCodes)
            throws IOException {
        supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStartRequest(lbRequest, lbResponse));
        try {
            Response response = feignClient.execute(feignRequest, options);
            if (loadBalanced) {
                supportedLifecycleProcessors.forEach(
                        lifecycle -> lifecycle.onComplete(new CompletionContext<>(CompletionContext.Status.SUCCESS,
                                lbRequest, lbResponse, buildResponseData(response, useRawStatusCodes))));
            }
            return response;
        }
        catch (Exception exception) {
            if (loadBalanced) {
                supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onComplete(
                        new CompletionContext<>(CompletionContext.Status.FAILED, exception, lbRequest, lbResponse)));
            }
            //get方法抛异常都重试
            if(HttpMethod.GET.matches(feignRequest.method())){
                throw exception;
            }
            //其他方法
            if(exception instanceof ConnectException) {
                if (exception.getMessage().contains("timeout") || exception.getCause().getMessage().contains("Read timed out")) {
                    return feign.Response.builder()
                            .status(504)
                            .reason(exception.getMessage())
                            .request(feignRequest)
                            .build();
                } else {
                    return feign.Response.builder()
                            .status(505)
                            .reason(exception.getMessage())
                            .request(feignRequest)
                            .build();
                }
            }
            throw exception;
        }
    }

    static ResponseData buildResponseData(Response response, boolean useRawStatusCodes) {
        HttpHeaders responseHeaders = new HttpHeaders();
        response.headers().forEach((key, value) -> responseHeaders.put(key, new ArrayList<>(value)));
        if (useRawStatusCodes) {
            return new ResponseData(responseHeaders, null, buildRequestData(response.request()), response.status());
        }
        return new ResponseData(HttpStatus.resolve(response.status()), responseHeaders, null,
                buildRequestData(response.request()));
    }

    static RequestData buildRequestData(Request request) {
        HttpHeaders requestHeaders = new HttpHeaders();
        request.headers().forEach((key, value) -> requestHeaders.put(key, new ArrayList<>(value)));
        return new RequestData(HttpMethod.resolve(request.httpMethod().name()), URI.create(request.url()),
                requestHeaders, null, new HashMap<>());
    }

    static Response executeWithLoadBalancerLifecycleProcessing(Client feignClient, Request.Options options,
                                                               Request feignRequest, org.springframework.cloud.client.loadbalancer.Request lbRequest,
                                                               org.springframework.cloud.client.loadbalancer.Response<ServiceInstance> lbResponse,
                                                               Set<LoadBalancerLifecycle> supportedLifecycleProcessors, boolean useRawStatusCodes) throws IOException {
        return executeWithLoadBalancerLifecycleProcessing(feignClient, options, feignRequest, lbRequest, lbResponse,
                supportedLifecycleProcessors, true, useRawStatusCodes);
    }
}

3、以上代码的核心处理逻辑是executeWithLoadBalancerLifecycleProcessing方法的catch块,通过返回505错误码,然后根据配置的retryable-status-codes进行重试。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值