微服务架构spring cloud - 客户端负载均衡 Ribbon(三)

1.什么是Ribbion

Ribbion基于Http和TCP的客户端负载均衡,也是基于Netfix ribbion封装而来。微服务间的调用、API网关的请求转发等时实际上都是通过RIbbion来实现的。还有Feign也是基于Ribbion实现的工具,ribbion是一个工具类框架。

2.客户端负载均衡和服务端负载均衡的区别

最大的区别就是服务清单的存储位置。客户端负载均衡是自己维护一份服务清单,并且通过心跳去保持服务清单的正确性

3.Ribbion使用流程

(1)服务提供者启动多个服务实例并注册到注册中心上

(2)服务消费者通过调用@LoadBalance注解修饰过的RestTemplate来实现面向服务的接口调用

3.学习RestTemplate

下面分成get、post、put、delete四个method分别讲解

GET请求

(1) getForEntity函数,大致包含三种重载方法,方法返回的是ResponseEntity,该对象是对http请求响应的封装

 public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables) throws RestClientException {
        RequestCallback requestCallback = this.acceptHeaderRequestCallback(responseType);
        ResponseExtractor<ResponseEntity<T>> responseExtractor = this.responseEntityExtractor(responseType);
        return (ResponseEntity)nonNull(this.execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables));
    }

    public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {
        RequestCallback requestCallback = this.acceptHeaderRequestCallback(responseType);
        ResponseExtractor<ResponseEntity<T>> responseExtractor = this.responseEntityExtractor(responseType);
        return (ResponseEntity)nonNull(this.execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables));
    }

    public <T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType) throws RestClientException {
        RequestCallback requestCallback = this.acceptHeaderRequestCallback(responseType);
        ResponseExtractor<ResponseEntity<T>> responseExtractor = this.responseEntityExtractor(responseType);
        return (ResponseEntity)nonNull(this.execute(url, HttpMethod.GET, requestCallback, responseExtractor));
    }

首先是使用第一种重载放方法,可以传入任何泛型作为返回的数据类型,第三个参数需要按照url拼接的顺序进行输入 

RestTemplate restTemplate=new RestTemplate();
        ResponseEntity<String> responseEntity=restTemplate.getForEntity("http://HELLO-SERVICE/user?name={1}",String.class,"llg");
        String body=responseEntity.getBody();

        ResponseEntity<Double> responseEntitys=restTemplate.getForEntity("http://HELLO-SERVICE/user?name={1}",Double.class,"llg");
        Double bodys=responseEntitys.getBody();

紧接着是 传入map类型的重载方法

Map map=new HashMap();
        map.put("name","llg");
        ResponseEntity<Double> responseEntityss=restTemplate.getForEntity("http://HELLO-SERVICE/user?name={name}",Double.class,map);
        Double bodyss=responseEntityss.getBody();

还有是uri,不做说明,不常用

(2) getForObject 函数

可以理解为对getForEntity做了进一步的封装,直接返回了需要的对象例如pojo对象

@Nullable
    <T> T getForObject(String var1, Class<T> var2, Object... var3) throws RestClientException;

    @Nullable
    <T> T getForObject(String var1, Class<T> var2, Map<String, ?> var3) throws RestClientException;

    @Nullable
    <T> T getForObject(URI var1, Class<T> var2) throws RestClientException;

POST请求

第一种 postForEntity 函数,方法与getForEntity类似,返回ResponseEntity对象

大致参数与getForEntity参数类似,重点讲解Object request参数,传入一个普通对象后会被转化成HttpEntity对象

   public <T> ResponseEntity<T> postForEntity(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables) throws RestClientException {
        RequestCallback requestCallback = this.httpEntityCallback(request, responseType);
        ResponseExtractor<ResponseEntity<T>> responseExtractor = this.responseEntityExtractor(responseType);
        return (ResponseEntity)nonNull(this.execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables));
    }

    public <T> ResponseEntity<T> postForEntity(String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {
        RequestCallback requestCallback = this.httpEntityCallback(request, responseType);
        ResponseExtractor<ResponseEntity<T>> responseExtractor = this.responseEntityExtractor(responseType);
        return (ResponseEntity)nonNull(this.execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables));
    }

    public <T> ResponseEntity<T> postForEntity(URI url, @Nullable Object request, Class<T> responseType) throws RestClientException {
        RequestCallback requestCallback = this.httpEntityCallback(request, responseType);
        ResponseExtractor<ResponseEntity<T>> responseExtractor = this.responseEntityExtractor(responseType);
        return (ResponseEntity)nonNull(this.execute(url, HttpMethod.POST, requestCallback, responseExtractor));
    }

第二种函数 postForObject 函数,就是返回的对象直接变成传入的响应类型,不再描述

第三种函数 postForLoaction函数, 返回的是一个uri对象

@Nullable
    public URI postForLocation(String url, @Nullable Object request, Object... uriVariables) throws RestClientException {
        RequestCallback requestCallback = this.httpEntityCallback(request);
        HttpHeaders headers = (HttpHeaders)this.execute(url, HttpMethod.POST, requestCallback, this.headersExtractor(), uriVariables);
        return headers != null ? headers.getLocation() : null;
    }

    @Nullable
    public URI postForLocation(String url, @Nullable Object request, Map<String, ?> uriVariables) throws RestClientException {
        RequestCallback requestCallback = this.httpEntityCallback(request);
        HttpHeaders headers = (HttpHeaders)this.execute(url, HttpMethod.POST, requestCallback, this.headersExtractor(), uriVariables);
        return headers != null ? headers.getLocation() : null;
    }

    @Nullable
    public URI postForLocation(URI url, @Nullable Object request) throws RestClientException {
        RequestCallback requestCallback = this.httpEntityCallback(request);
        HttpHeaders headers = (HttpHeaders)this.execute(url, HttpMethod.POST, requestCallback, this.headersExtractor());
        return headers != null ? headers.getLocation() : null;
    }

PUT 请求

由于是用在修改的请求上,所以返回类型为void,用法大致也与上述类似,不重复说明。

RestTemplate restTemplate=new RestTemplate();
        restTemplate.put("http://HELLO-SERVICE/hello/user/{1}",String.class,"23");
public void put(String url, @Nullable Object request, Object... uriVariables) throws RestClientException {
        RequestCallback requestCallback = this.httpEntityCallback(request);
        this.execute(url, HttpMethod.PUT, requestCallback, (ResponseExtractor)null, (Object[])uriVariables);
    }

    public void put(String url, @Nullable Object request, Map<String, ?> uriVariables) throws RestClientException {
        RequestCallback requestCallback = this.httpEntityCallback(request);
        this.execute(url, HttpMethod.PUT, requestCallback, (ResponseExtractor)null, (Map)uriVariables);
    }

    public void put(URI url, @Nullable Object request) throws RestClientException {
        RequestCallback requestCallback = this.httpEntityCallback(request);
        this.execute(url, HttpMethod.PUT, requestCallback, (ResponseExtractor)null);
    }

DELETE请求

情况与put请求类似,所以不再重复描述

RestTemplate restTemplate=new RestTemplate();
        restTemplate.delete("http://HELLO-SERVICE/hello/user/{1}",String.class,"23");
​
public void delete(String url, Object... uriVariables) throws RestClientException {
        this.execute(url, HttpMethod.DELETE, (RequestCallback)null, (ResponseExtractor)null, (Object[])uriVariables);
    }

    public void delete(String url, Map<String, ?> uriVariables) throws RestClientException {
        this.execute(url, HttpMethod.DELETE, (RequestCallback)null, (ResponseExtractor)null, (Map)uriVariables);
    }

    public void delete(URI url) throws RestClientException {
        this.execute(url, HttpMethod.DELETE, (RequestCallback)null, (ResponseExtractor)null);
    }

​

 4.源码解析

首先从代码入手可以看出负载均衡是从一个注解开始的,作为标记以使用负载均衡的客户端(LoadBalanceClient)来配置它

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

从LoadBalanceClinet这个借口可以看出大概功能是重写一个host:port的url,挑选服务中的某一个实例并进行执行请求

public interface LoadBalancerClient extends ServiceInstanceChooser {
    <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;

    <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;

    URI reconstructURI(ServiceInstance instance, URI original);
}

紧接着我们从LoadBalancerAutoConfiguration实现客户端负载均衡器的自动化配置类入手

从Condition来看,这个自动配置类的启动条件是要有RestTemplate和LoadBalancerClient这两个类

@Configuration
@ConditionalOnClass({RestTemplate.class})
@ConditionalOnBean({LoadBalancerClient.class})
@EnableConfigurationProperties({LoadBalancerRetryProperties.class})
public class LoadBalancerAutoConfiguration {

创建一个客户端发起请求的拦截器

================================================================================================

 @Bean
        public LoadBalancerInterceptor ribbonInterceptor(LoadBalancerClient loadBalancerClient, LoadBalancerRequestFactory requestFactory) {
            return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
        }

此处插播拦截器知识(帮助理解)

拦截器是spring mvc 所持有的,功能类似于filter,主要继承HandleInterceptor接口或者HandleInterceptorAdapter类实现自定义拦截器,通过重写webMvcConfigurerAdapter的addInterceptors方法来注册自定义的拦截器

public class MyInterceptor extends HandlerInterceptorAdapter {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return super.preHandle(request, response, handler);//true  拦截通过返回true,类似chain.dofilter
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        super.afterCompletion(request, response, handler, ex);
    }

    @Override
    public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        super.afterConcurrentHandlingStarted(request, response, handler);
    }
}
public class Config extends WebMvcConfigurationSupport {
    @Bean
    public MyInterceptor myInterceptor(){
        return new MyInterceptor();
    }
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        super.addInterceptors(registry);
        registry.addInterceptor(myInterceptor()).addPathPatterns("/hello/**");
    }
}

================================================================================================

创建一个RestTemplateCustomizer的Bean,用于添加拦截器,在这里是对所有增加注解的resttemplate都进行拦截器的添加

@Bean
        @ConditionalOnMissingBean
        public RestTemplateCustomizer restTemplateCustomizer(final RetryLoadBalancerInterceptor loadBalancerInterceptor) {
            return (restTemplate) -> {
                List<ClientHttpRequestInterceptor> list = new ArrayList(restTemplate.getInterceptors());
                list.add(loadBalancerInterceptor);
                restTemplate.setInterceptors(list);
            };
        }

特此说明虽然同样是拦截器,但是这个拦截器与spring mvc的拦截器不同

接下来看看拦截器是如何操作使RestTemplate变成负载均衡

从下面的拦截方法可以看出,主要是调用了LoadBalancer负载均衡客户端的execute方法,将服务名传入了方法体内

request.getURI()返回的就是一个真实的ip地址,由异步调用获取,改变地址后并且发起了http请求

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));
    }

接着我们去查看execute函数,发现首要的获取服务信息的就是getServer函数

public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
        ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId);
        Server server = this.getServer(loadBalancer);
        if (server == null) {
            throw new IllegalStateException("No instances available for " + serviceId);
        } else {
            RibbonLoadBalancerClient.RibbonServer ribbonServer = new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server));
            return this.execute(serviceId, ribbonServer, request);
        }
    }

 接着我们进入getServer函数,发现了chooseServer方法

protected Server getServer(ILoadBalancer loadBalancer) {
        return loadBalancer == null ? null : loadBalancer.chooseServer("default");
    }

接着我们进入chooseServer方法里面,发现了是一个接口,接口的实现有三种,第一种是BaseLoadBalancer实现了基础的负载均衡,而DynamicServerListLoadBalancer和ZoneAwareLoadBalance 在负载均衡的策略上做了一些功能的扩展

public interface ILoadBalancer {
    void addServers(List<Server> var1);

    Server chooseServer(Object var1);

    void markServerDown(Server var1);

    /** @deprecated */
    @Deprecated
    List<Server> getServerList(boolean var1);

    List<Server> getReachableServers();

    List<Server> getAllServers();
}

查看配置类RibbonClientConfiguration可以看出来,默认的负载均衡实现类是ZoneAwareLoadBalance

@Bean
    @ConditionalOnMissingBean
    public ILoadBalancer ribbonLoadBalancer(IClientConfig config, 
ServerList<Server> serverList, ServerListFilter<Server> serverListFilter, IRule 
rule, IPing ping, ServerListUpdater serverListUpdater) {
        return (ILoadBalancer)(this.propertiesFactory.isSet(ILoadBalancer.class, 
this.name) ? (ILoadBalancer)this.propertiesFactory.get(ILoadBalancer.class, config, 
this.name) : new ZoneAwareLoadBalancer(config, rule, ping, serverList, 
serverListFilter, serverListUpdater));
    }

通过chooseServer返回了一个ServicInstance对象,通过服务名返回的对象,包括了服务实例信息,数据信息等等

public ServiceInstance choose(String serviceId) {
        Server server = this.getServer(serviceId);
        return server == null ? null : new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server));
    }
​
public interface ServiceInstance {
    String getServiceId();

    String getHost();

    int getPort();

    boolean isSecure();

    URI getUri();

    Map<String, String> getMetadata();

    default String getScheme() {
        return null;
    }
}

​
public class AsyncLoadBalancerInterceptor implements AsyncClientHttpRequestInterceptor {
    private LoadBalancerClient loadBalancer;

    public AsyncLoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
        this.loadBalancer = loadBalancer;
    }

    public ListenableFuture<ClientHttpResponse> intercept(final HttpRequest request, final byte[] body, final AsyncClientHttpRequestExecution execution) throws IOException {
        URI originalUri = request.getURI();
        String serviceName = originalUri.getHost();
        return (ListenableFuture)this.loadBalancer.execute(serviceName, new LoadBalancerRequest<ListenableFuture<ClientHttpResponse>>() {
            public ListenableFuture<ClientHttpResponse> apply(final ServiceInstance instance) throws Exception {
                HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance, AsyncLoadBalancerInterceptor.this.loadBalancer);
                return execution.executeAsync(serviceRequest, body);
            }
        });
    }
}

从return那里可以看到调用了execute的方法然后下面是apply方法,是在获取真实ip的时候所调用的方法

public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException {
        Server server = null;
        if (serviceInstance instanceof RibbonLoadBalancerClient.RibbonServer) {
            server = ((RibbonLoadBalancerClient.RibbonServer)serviceInstance).getServer();
        }

        if (server == null) {
            throw new IllegalStateException("No instances available for " + serviceId);
        } else {
            RibbonLoadBalancerContext context = this.clientFactory.getLoadBalancerContext(serviceId);
            RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);

            try {
                T returnVal = request.apply(serviceInstance);
                statsRecorder.recordStats(returnVal);
                return returnVal;
            } catch (IOException var8) {
                statsRecorder.recordStats(var8);
                throw var8;
            } catch (Exception var9) {
                statsRecorder.recordStats(var9);
                ReflectionUtils.rethrowRuntimeException(var9);
                return null;
            }
        }
    }
public ListenableFuture<ClientHttpResponse> intercept(final HttpRequest request, final byte[] body, final AsyncClientHttpRequestExecution execution) throws IOException {
        URI originalUri = request.getURI();
        String serviceName = originalUri.getHost();
        return (ListenableFuture)this.loadBalancer.execute(serviceName, new LoadBalancerRequest<ListenableFuture<ClientHttpResponse>>() {
            public ListenableFuture<ClientHttpResponse> apply(final ServiceInstance instance) throws Exception {
                HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance, AsyncLoadBalancerInterceptor.this.loadBalancer);
                return execution.executeAsync(serviceRequest, body);
            }
        });
    }

然后通过getURI方法执行了reconstrutURI函数,至此URL由服务名构成的地址被替换成真实ip


public class ServiceRequestWrapper extends HttpRequestWrapper {
    private final ServiceInstance instance;
    private final LoadBalancerClient loadBalancer;

    public ServiceRequestWrapper(HttpRequest request, ServiceInstance instance, LoadBalancerClient loadBalancer) {
        super(request);
        this.instance = instance;
        this.loadBalancer = loadBalancer;
    }

    public URI getURI() {
        URI uri = this.loadBalancer.reconstructURI(this.instance, this.getRequest().getURI());
        return uri;
    }
}

重点讨论负载均衡

spring cloud中的LoadBalancerClient 作为负载均衡器的通用接口,实现了RibbionLoadBalanceClient ,但是具体实现负载均衡又是ILoadBalancerClient,所以接下来讨论ILoadBalancerClient的各种实现类

AbstractLoadBalancer

getLoadBalancerStats() 存储各实例当前的属性和统计信息,这些信息是用来指定负载均衡策略的重要依据

public abstract class AbstractLoadBalancer implements ILoadBalancer {
    public AbstractLoadBalancer() {
    }

    public Server chooseServer() {
        return this.chooseServer((Object)null);
    }

    public abstract List<Server> getServerList(AbstractLoadBalancer.ServerGroup var1);

    public abstract LoadBalancerStats getLoadBalancerStats();

    public static enum ServerGroup {
        ALL,
        STATUS_UP,
        STATUS_NOT_UP;

        private ServerGroup() {
        }
    }
}

BaseLoadBalancer

是抽象类的基础实现,定义了许多的基础方法。比如存储了服务列表,利用ping检查服务是否正常,定义了如何负载均衡的处理规则,下面我们只看处理规则源码

public Server chooseServer(Object key) {
        if (this.counter == null) {
            this.counter = this.createCounter();
        }

        this.counter.increment();
        if (this.rule == null) {
            return null;
        } else {
            try {
                return this.rule.choose(key);
            } catch (Exception var3) {
                logger.warn("LoadBalancer [{}]:  Error choosing server for key {}", new Object[]{this.name, key, var3});
                return null;
            }
        }
    }

 

DynameicServerListLoadBalance

继承 BaseLoadBalancer ,对负载均衡器的扩展,实现了动态更新服务清单和对服务清单过滤的功能。在rabbion和eureka的结合包可以找到主要是依靠EurekaClient获取的服务实例InstanceInfo列表,通过计时器进行定时刷服务列表,通过过滤条件例如status 、zone等进行过滤

ZoneAwareLoadBalancer

继承自DynameicServerListLoadBalance,主要是重写了chooseServer的方法,由原来的线性选择策略变成zone策略,只有当zone的个数大于一的时候才会执行重写的方法,否则仍然执行父类的方法,如果个数大于1则通过一系列判断和计算获得zone区域为可用区域,之后再从这个zone区域中选择一个服务实例

负载均衡策略

从顶级接口IRule开始分析

AbstractLoadBalancerRule,里面定义了ILoadBalance变量,为了便于拿到负载均衡器维护的数据

public abstract class AbstractLoadBalancerRule implements IRule, IClientConfigAware {
    private ILoadBalancer lb;

    public AbstractLoadBalancerRule() {
    }

    public void setLoadBalancer(ILoadBalancer lb) {
        this.lb = lb;
    }

    public ILoadBalancer getLoadBalancer() {
        return this.lb;
    }
}

紧接着开始查看继承了AbstractLoadBalancerRule的各种子类

RandomRule

随机生成一个数,然后从数组中返回一个实例

@SuppressWarnings({"RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE"})
    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            return null;
        } else {
            Server server = null;

            while(server == null) {
                if (Thread.interrupted()) {
                    return null;
                }

                List<Server> upList = lb.getReachableServers();
                List<Server> allList = lb.getAllServers();
                int serverCount = allList.size();
                if (serverCount == 0) {
                    return null;
                }

                int index = this.rand.nextInt(serverCount);
                server = (Server)upList.get(index);
                if (server == null) {
                    Thread.yield();
                } else {
                    if (server.isAlive()) {
                        return server;
                    }

                    server = null;
                    Thread.yield();
                }
            }

            return server;
        }
    }

RoundRobinRule

线性轮询,也就是一个接着一个的找,找到有就返回

public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            log.warn("no load balancer");
            return null;
        } else {
            Server server = null;
            int count = 0;

            while(true) {
                if (server == null && count++ < 10) {
                    List<Server> reachableServers = lb.getReachableServers();
                    List<Server> allServers = lb.getAllServers();
                    int upCount = reachableServers.size();
                    int serverCount = allServers.size();
                    if (upCount != 0 && serverCount != 0) {
                        int nextServerIndex = this.incrementAndGetModulo(serverCount);
                        server = (Server)allServers.get(nextServerIndex);
                        if (server == null) {
                            Thread.yield();
                        } else {
                            if (server.isAlive() && server.isReadyToServe()) {
                                return server;
                            }

                            server = null;
                        }
                        continue;
                    }

                    log.warn("No up servers available from load balancer: " + lb);
                    return null;
                }

                if (count >= 10) {
                    log.warn("No available alive servers after 10 tries from load balancer: " + lb);
                }

                return server;
            }
        }
    }

RetryRule

顾名思义,就是不断重试,由源码可以看出通过一定的时间范围内,调用RoundRobinRule的线性轮询方法

,在这里说明一下此处的InterruptTask定时器,是在一定时间后执行里面的run方法,也就是中断while循环。

public Server choose(ILoadBalancer lb, Object key) {
        long requestTime = System.currentTimeMillis();
        long deadline = requestTime + this.maxRetryMillis;
        Server answer = null;
        answer = this.subRule.choose(key);
        if ((answer == null || !answer.isAlive()) && System.currentTimeMillis() < deadline) {
            InterruptTask task = new InterruptTask(deadline - System.currentTimeMillis());

            while(!Thread.interrupted()) {
                answer = this.subRule.choose(key);
                if (answer != null && answer.isAlive() || System.currentTimeMillis() >= deadline) {
                    break;
                }

                Thread.yield();
            }

            task.cancel();
        }

        return answer != null && answer.isAlive() ? answer : null;
    }
public void run() {
        if (this.target != null && this.target.isAlive()) {
            this.target.interrupt();
        }

    }

WeightedResponseTimeRule

对RoundRobinRule的扩展,根据实例运行情况来计算权重,并根据权重挑选实例

首先是初始化启动一个定时任务,用来计算权重,每隔30秒执行一次

void initialize(ILoadBalancer lb) {
        if (this.serverWeightTimer != null) {
            this.serverWeightTimer.cancel();
        }

        this.serverWeightTimer = new Timer("NFLoadBalancer-serverWeightTimer-" + this.name, true);
        this.serverWeightTimer.schedule(new WeightedResponseTimeRule.DynamicServerWeightTask(), 0L, (long)this.serverWeightTaskTimerInterval);
        WeightedResponseTimeRule.ServerWeight sw = new WeightedResponseTimeRule.ServerWeight();
        sw.maintainWeights();
        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
            public void run() {
                WeightedResponseTimeRule.logger.info("Stopping NFLoadBalancer-serverWeightTimer-" + WeightedResponseTimeRule.this.name);
                WeightedResponseTimeRule.this.serverWeightTimer.cancel();
            }
        }));
    }
class DynamicServerWeightTask extends TimerTask {
        DynamicServerWeightTask() {
        }

        public void run() {
            WeightedResponseTimeRule.ServerWeight serverWeight = WeightedResponseTimeRule.this.new ServerWeight();

            try {
                serverWeight.maintainWeights();
            } catch (Exception var3) {
                WeightedResponseTimeRule.logger.error("Error running DynamicServerWeightTask for {}", WeightedResponseTimeRule.this.name, var3);
            }

        }
    }

接着我们来看是如何计算权重的

(1)根据LoadBalanceStats记录实例的统计信息,获取总的平均响应时间

(2)计算权重:weightSoFar+totalResponseTIme-实例的平均响应时间

切记每计算一个权重都需要累加到weightSoFar上面以供后面的实例计算权重

通过计算所有权重后,其实并不是根据权重大小来判断选择实例,而是用过各个权重所组成的区间,随机生成一个随机数,当某个权重大于等于这个随机数的时候,把这个权重的索引传回去用于获取实例。

不难发现,每个区间的宽度实际上是总的响应时间-实例的平均响应时间,所以实例的平均响应时间越短,权重区间的宽度越大,那么被选中的概率就越大。

class ServerWeight {
        ServerWeight() {
        }

        public void maintainWeights() {
            ILoadBalancer lb = WeightedResponseTimeRule.this.getLoadBalancer();
            if (lb != null) {
                if (WeightedResponseTimeRule.this.serverWeightAssignmentInProgress.compareAndSet(false, true)) {
                    try {
                        WeightedResponseTimeRule.logger.info("Weight adjusting job started");
                        AbstractLoadBalancer nlb = (AbstractLoadBalancer)lb;
                        LoadBalancerStats stats = nlb.getLoadBalancerStats();
                        if (stats != null) {
                            double totalResponseTime = 0.0D;

                            ServerStats ss;
                            for(Iterator var6 = nlb.getAllServers().iterator(); var6.hasNext(); totalResponseTime += ss.getResponseTimeAvg()) {
                                Server server = (Server)var6.next();
                                ss = stats.getSingleServerStat(server);
                            }

                            Double weightSoFar = 0.0D;
                            List<Double> finalWeights = new ArrayList();
                            Iterator var20 = nlb.getAllServers().iterator();

                            while(var20.hasNext()) {
                                Server serverx = (Server)var20.next();
                                ServerStats ssx = stats.getSingleServerStat(serverx);
                                double weight = totalResponseTime - ssx.getResponseTimeAvg();
                                weightSoFar = weightSoFar + weight;
                                finalWeights.add(weightSoFar);
                            }

                            WeightedResponseTimeRule.this.setWeights(finalWeights);
                            return;
                        }
                    } catch (Exception var16) {
                        WeightedResponseTimeRule.logger.error("Error calculating server weights", var16);
                        return;
                    } finally {
                        WeightedResponseTimeRule.this.serverWeightAssignmentInProgress.set(false);
                    }

                }
            }
        }
    }

ClientConfigEnabledRoundRobinRule

功能上是使用线性轮询的方法,此类通常被用于子类继承,当子类的choose无法使用的时候就使用线性轮询

public Server choose(Object key) {
        if (this.roundRobinRule != null) {
            return this.roundRobinRule.choose(key);
        } else {
            throw new IllegalArgumentException("This class has not been initialized with the RoundRobinRule class");
        }
    }

BestAvailableRule

该策略继承自ClientConfigEnabledRoundRobinRule,从源码可以看出通过ServerStars拿到请求数量,挑选并发请求数量最小的一个进行负载均衡。由于LoadBalanceStats为空时,不会执行此方法,而是执行父类的线性轮询方法

public Server choose(Object key) {
        if (this.loadBalancerStats == null) {
            return super.choose(key);
        } else {
            List<Server> serverList = this.getLoadBalancer().getAllServers();
            int minimalConcurrentConnections = 2147483647;
            long currentTime = System.currentTimeMillis();
            Server chosen = null;
            Iterator var7 = serverList.iterator();

            while(var7.hasNext()) {
                Server server = (Server)var7.next();
                ServerStats serverStats = this.loadBalancerStats.getSingleServerStat(server);
                if (!serverStats.isCircuitBreakerTripped(currentTime)) {
                    int concurrentConnections = serverStats.getActiveRequestsCount(currentTime);
                    if (concurrentConnections < minimalConcurrentConnections) {
                        minimalConcurrentConnections = concurrentConnections;
                        chosen = server;
                    }
                }
            }

            if (chosen == null) {
                return super.choose(key);
            } else {
                return chosen;
            }
        }
    }

PredicateBasedRule

通过getPredicate()过滤了一部分实例,之后根据线性轮询挑选实例,其他特点与上述类似。

过滤的话是实现了google提供的Predicate接口,通过调用apply方法来判断

public abstract class PredicateBasedRule extends ClientConfigEnabledRoundRobinRule {
    public PredicateBasedRule() {
    }

    public abstract AbstractServerPredicate getPredicate();

    public Server choose(Object key) {
        ILoadBalancer lb = this.getLoadBalancer();
        Optional<Server> server = this.getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
        return server.isPresent() ? (Server)server.get() : null;
    }
}
public List<Server> getEligibleServers(List<Server> servers, Object loadBalancerKey) {
        if (loadBalancerKey == null) {
            return ImmutableList.copyOf(Iterables.filter(servers, this.getServerOnlyPredicate()));
        } else {
            List<Server> results = Lists.newArrayList();
            Iterator var4 = servers.iterator();

            while(var4.hasNext()) {
                Server server = (Server)var4.next();
                if (this.apply(new PredicateKey(loadBalancerKey, server))) {
                    results.add(server);
                }
            }

            return results;
        }
    }

AvailabilityFilteringRule

继承的是PredicateBasedRule,过滤条件使用了AvailablityPredicate类

使用shouldSkipServer方法过滤掉故障的实例或者并发请求数大于阈值的实例,二者取其一即可

public class AvailabilityPredicate extends AbstractServerPredicate {
  

    public boolean apply(@Nullable PredicateKey input) {
        LoadBalancerStats stats = this.getLBStats();
        if (stats == null) {
            return true;
        } else {
            return !this.shouldSkipServer(stats.getSingleServerStat(input.getServer()));
        }
    }

    private boolean shouldSkipServer(ServerStats stats) {
        return CIRCUIT_BREAKER_FILTERING.get() && stats.isCircuitBreakerTripped() || stats.getActiveRequestsCount() >= (Integer)this.activeConnectionsLimit.get();
    }
}

 choose方法首先是以线性方式选择一个实例,接着用过滤条件判断该实例是否符合要求,如果符合直接返回实例。这比父类中通过过滤所有集合然后线性选取更好,开销更小

public class AvailabilityFilteringRule extends PredicateBasedRule {
   
    public Server choose(Object key) {
        int count = 0;

        for(Server server = this.roundRobinRule.choose(key); count++ <= 10; server = this.roundRobinRule.choose(key)) {
            if (this.predicate.apply(new PredicateKey(server))) {
                return server;
            }
        }

        return super.choose(key);
    }

}

ZoneAvoidanceRule

使用主过滤器条件对所有实例过滤并返回过滤后的实例清单 zone

依次使用次过滤条件列表中的过滤条件对主过滤条件的结果进行过滤 Avaliabitypredicate

每次过滤之后(主和从),都需要判断下面两件,有一条件成立即可返回

           过滤后的实例总数>=最小过滤实例数,默认为1

           过滤后的实例比例> 最小过滤百分比 默认为0

5.配置详解

配置主要分成有没有与eureka整合的两大类来说,默认负载均衡使用Zone策略

没有eureka的时候,服务清单由Ribbion自己维护,所以在springboot中需要通过

client.ribbion.xx=xx

hello-service.ribbion.NFLoadBalancerPingClassName=com.netfix.loadBalancer.PingUrl

如果是全局配置,则将client删掉即可

ribbion.NFLoadBalancerPingClassName=com.netfix.loadBalancer.PingUrl

如果没有服务治理框架,我们需要手动输入实例

hello-service.ribbion.listofServer=localhost:8000,localhost:8001,localhost:8002

与eureka结合的时候

服务交给了eureka维护,所以在配置文件上不需要加上client了

由于ribbio默认是zone策略,所以我们在配置文件上配置zone即可

把region理解为地区,把zone理解为机房,一个机房可能是一个完备的单实例集合,例如一个注册中心一个消费者一个服务提供者

eureka.instance.metadataMap.zone=shanghai  (无效)

eureka.client.availabilityZones.beijing=myzone # beijing是region

eureka.client.region=beijing  

6.重试机制

增加故障的容错机制,多次重复访请求

spring.cloud.loadbalancer.retry.enabled=true 开启重试机制

hystrix.command.default.execution.isloation.thread.timeoutInMilliseconds=10000 断路器的超时时间要大于Ribbion的超时时间,不然不会触发重试

hello-service.ribbion.ConnectTimeout=250 请求连接超时间

hello-service.ribbion.ReadTimeout=1000 请求处理超时时间

heelo-service.ribbion.OkToRetryAllOperations=true 对所有请求都进行重试

heelo-service.ribbion.MaxAutoRetriesNextServer=2  切换实例的重试次数

hello-service.ribbion.MaxAutoRetries=1 对当前实例的重试次数

根据上述配置,如果访问实例不行,则换一个,再不行再换,直到等于MaxAutoRetriesNextServer还不行就返回失败信息

 

 

 

 

 

 

 

 

 

展开阅读全文

没有更多推荐了,返回首页