负载均衡原理,探究@LoadBalanced注解都做了什么(Ribbon)

Posted 牛哄哄的柯南

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了负载均衡原理,探究@LoadBalanced注解都做了什么(Ribbon)相关的知识,希望对你有一定的参考价值。

负载均衡原理,探究@LoadBalanced注解都做了什么

RPC-百度百科

RPC(Remote Procedure Call Protocol)–远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。

RPC采用客户机/服务器模式。请求程序就是一个客户机,而服务提供程序就是一个服务器。首先,客户机调用进程发送一个有进程参数的调用信息到服务进程,然后等待应答信息。在服务器端,进程保持睡眠状态直到调用信息的到达为止。当一个调用信息到达,服务器获得进程参数,计算结果,发送答复信息,然后等待下一个调用信息,最后,客户端调用进程接收答复信息,获得进程结果,然后调用执行继续进行。

RPC是远程过程调用(Remote Procedure Call)的缩写形式。SAP系统RPC调用的原理其实很简单,有一些类似于三层构架的C/S系统,第三方的客户程序通过接口调用SAP内部的标准或自定义函数,获得函数返回的数据进行处理后显示或打印。

负载均衡原理

定义

负载均衡建立在现有网络结构之上,它提供了一种廉价有效透明的方法扩展网络设备和服务器的带宽、增加吞吐量、加强网络数据处理能力、提高网络的灵活性和可用性。
负载均衡(Load Balance)其意思就是分摊到多个操作单元上进行执行,例如Web服务器、FTP服务器、企业关键应用服务器和其它关键任务服务器等,从而共同完成工作任务。

负载均衡是一个通用的特性,所有的RPC框架都会有这个概念的实现。

平时说负载均衡一般都是指服务端负载均衡,但因为分布式spring cloud分布式框架出现,也出现了客户端负载均衡这一概念

服务端负载均衡

最常见的就是nginx,客户端发送请求,由Nginx服务器接收,根据使用的负载均衡算法,再将请求发送给相应的应用服务器。

由Nginx分配到不同的服务端

客户端负载均衡

客户端的负载均衡是在spring-cloud后出现的,在spring-cloud中有ribbon组件来负责负载均衡。spring的负载均衡需要用到服务注册中心eruka。

服务提供方:将自己注册到服务注册中心eruka

服务消费方:从服务注册中心中获取服务列表,使用服务

客户端的负载均衡流程如下:

服务消费方通过ribbon先从服务注册中心获取服务列表,根据一定的负载均衡算法,分发请求到不同的服务提供方

客户端从服务中心选择一个,去调用

参考自:http://t.csdn.cn/zBP3x

常见的负载均衡算法


当然,我们可以去自定义负载均衡算法。

Ribbon负载均衡组件

Ribbon(spring-cloud-starter-ribbon)

我们都知道使用RestTemplate时,可以直接使用服务名进行服务调用,只需要在定义RestTemplate时加上@LoadBalanced注解就可以了

@LoadBalanced //配置负载均衡实现RestTemplate
@Bean
public RestTemplate getRestTemplate()
    return new RestTemplate();

接下来,我们重点分析下@LoadBalanced这个注解底层都干了什么

@LoadBalanced

在自动配置类里我们可以看到LoadBalancerAutoConfiguration的存在。

LoadBalancerAutoConfiguration

我们先看下这个类的源码:

@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration 
    @LoadBalanced
    @Autowired(
        required = false
    )
    //这里是从ApplicationContext中获取所有被@LoadBalanced修饰的RestTemplate
    private List<RestTemplate> restTemplates = Collections.emptyList();
    @Autowired(
        required = false
    )
    
    private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();

    public LoadBalancerAutoConfiguration() 
    

    @Bean
    public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) 
    //这里的 List<RestTemplateCustomizer> 是 ApplicationContext 存在的 RestTemplateCustomizer集合
        return () -> 
            restTemplateCustomizers.ifAvailable((customizers) -> 
                Iterator var2 = this.restTemplates.iterator();

                while(var2.hasNext()) 
                    RestTemplate restTemplate = (RestTemplate)var2.next();
                    Iterator var4 = customizers.iterator();

                    while(var4.hasNext()) 
                        RestTemplateCustomizer customizer = (RestTemplateCustomizer)var4.next();
                        //遍历restTemplates集合,使用RestTemplateCustomizer给每个restTemplate定制一下
                        customizer.customize(restTemplate);
                    
                

            );
        ;
    
    ...


接着看这个RestTemplateCustomizer定制过程做了啥

RestTemplateCustomizer

RestTemplateCustomizer 是 LoadBalancerAutoConfiguration 的内部类

@Configuration
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig 
    LoadBalancerInterceptorConfig() 
    

	//定义LoadBalancerInterceptor Bean, 这个拦截器继承自 ClientHttpRequestInterceptor ,
	//可以被添加到RestTemplate的拦截器列表中
	//public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor 
    @Bean
    public LoadBalancerInterceptor ribbonInterceptor(LoadBalancerClient loadBalancerClient, LoadBalancerRequestFactory requestFactory) 
        return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
    

    @Bean
    @ConditionalOnMissingBean
    public RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) 
        return (restTemplate) -> 
            List<ClientHttpRequestInterceptor> list = new ArrayList(restTemplate.getInterceptors());
            //在RestTemplate的拦截器列表中加上LoadBalancerInterceptor拦截器
            list.add(loadBalancerInterceptor);
            restTemplate.setInterceptors(list);
        ;
    

这里就是 @LoadBalanced 直接修饰的 RestTemplate,会被机上一个LoadBalancerInterceptor拦截器

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

    public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException 
        // 服务名使用URI中的host信息
        URI originalUri = request.getURI();
        String serviceName = originalUri.getHost();
        Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
        //使用LoadBalancerClient 客户端负载均衡器做真正的服务调用
        return (ClientHttpResponse)this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
    

LoadBalancerClient

LoadBalancerClient (客户端负载均衡器)会根据负载均衡请求和服务名做真正的负载均衡。

public interface LoadBalancerClient extends ServiceInstanceChooser 
	//serviceId就是服务名,request就是请求
    <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;

	// serviceInstance 服务实例
    <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;

	//带有服务名的老的URI
    URI reconstructURI(ServiceInstance instance, URI original);



public interface LoadBalancerRequest<T> 
    T apply(ServiceInstance instance) throws Exception;

public class LoadBalancerRequestFactory 
    private LoadBalancerClient loadBalancer;
    private List<LoadBalancerRequestTransformer> transformers;

    public LoadBalancerRequestFactory(LoadBalancerClient loadBalancer, List<LoadBalancerRequestTransformer> transformers) 
        this.loadBalancer = loadBalancer;
        this.transformers = transformers;
    

    public LoadBalancerRequestFactory(LoadBalancerClient loadBalancer) 
        this.loadBalancer = loadBalancer;
    

    public LoadBalancerRequest<ClientHttpResponse> createRequest(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) 
        return (instance) -> 
            HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance, this.loadBalancer);
            LoadBalancerRequestTransformer transformer;
            if (this.transformers != null) 
                for(Iterator var6 = this.transformers.iterator(); var6.hasNext(); serviceRequest = transformer.transformRequest((HttpRequest)serviceRequest, instance)) 
                    transformer = (LoadBalancerRequestTransformer)var6.next();
                
            

            return execution.execute((HttpRequest)serviceRequest, body);
        ;
    

RibbonLoadBalancerClient

Ribbon 中 LoadBalancerClient 的 默认实现类为 RibbonLoadBalancerClient

我们重点看下这个不带服务实例的execute 是如何 找到合适的 ServiceInstance
有一点可以明确,不带ServiceInstance 最后会去调用 带ServiceInstance的execute,上面也有所体现

public RibbonServer(String serviceId, Server server, boolean secure,
   			Map<String, String> metadata) 
   		this.serviceId = serviceId;
   		this.server = server;
   		this.secure = secure;
   		this.metadata = metadata;
   	
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
			throws IOException 
		//从这里开始的
		//通过serviceId 获取 ILoadBalancer 
		ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
		Server server = getServer(loadBalancer, hint);
		if (server == null) 
			throw new IllegalStateException("No instances available for " + serviceId);
		
		//RibbonServer 是 ServiceInstance  的子类
		//public static class RibbonServer implements ServiceInstance 
		RibbonServer ribbonServer = new RibbonServer(serviceId, server,
				isSecure(server, serviceId),
				serverIntrospector(serviceId).getMetadata(server));

		return execute(serviceId, ribbonServer, request);
	

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

通过serviceId 获取 ILoadBalancer

protected ILoadBalancer getLoadBalancer(String serviceId) 
		return this.clientFactory.getLoadBalancer(serviceId);
	

serviceId 也就是服务名

public ILoadBalancer getLoadBalancer(String name) 
		return getInstance(name, ILoadBalancer.class);
	
@Override
	public <C> C getInstance(String name, Class<C> type) 
		C instance = super.getInstance(name, type);
		if (instance != null) 
			return instance;
		
		IClientConfig config = getInstance(name, IClientConfig.class);
		return instantiateWithConfig(getContext(name), type, config);
	

根据loadBalancer和hint(null)获取Service
因为上面hint为null,这里变成了default

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");
	
public Server chooseServer(Object key);


接着到BaseLoadBalancer

public Server chooseServer(Object key) 
        if (counter == null) 
            counter = createCounter();
        
        counter.increment();
        if (rule == null) 
            return null;
         else 
            try 
                //这里根据key进行选择
                return rule.choose(key);
             catch (Exception e) 
                logger.warn("LoadBalancer []:  Error choosing server for key ", name, key, e);
                return null;
            
        
    
public Server choose(Object key);

跟到这里我们就可以看到熟悉的负载均衡策略了,包括我们自定义的。
下面的就是具体的策略选择不同的服务了。

使用自定义规则

ConfigBean

package com.keafmd.springcloud.config;

import com.keafmd.myrule.MyRandomRule;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import com.netflix.loadbalancer.WeightedResponseTimeRule;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

/**
 * Keafmd
 *
 * @ClassName: ConfigBean
 * @Description:
 * @author: 牛哄哄的柯南
 * @date: 2022-07-06 17:48
 */
@Configuration
public class ConfigBean 

    @LoadBalanced //配置负载均衡实现RestTemplate
    @Bean
    public RestTemplate getRestTemplate()
        return new RestTemplate();
    

    /**
     * IRule:
     * RoundRobinRule 轮询策略
     * RandomRule 随机策略
     * AvailabilityFilteringRule : 会先过滤掉,跳闸,访问故障的服务~,对剩下的进行轮询~
     * RetryRule : 会先按照轮询获取服务~,如果服务获取失败,则会在指定的时间内进行,重试
     */
    @Bean //注释掉 使用自定义规则
    public IRule myRule() 
        return new MyRandomRule();
//        return new RandomRule();//使用随机策略
        //return new RoundRobinRule();//使用轮询策略
        //return new AvailabilityFilteringRule();
        //return new RetryRule();
        //return new WeightedResponseTimeRule();
    


MyRandomRule

继承 AbstractLoadBalancerRule

package com.keafmd.myrule;

import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;

import java.util.List;
import java.util.concurrent.ThreadLocalRandom;

/**
 * Keafmd
 *
 * @ClassName: MyRandomRule
 * @Description:
 * @author: 牛哄哄的柯南
 * @date: 2022-07-07 14:58
 */
public class MyRandomRule extends AbstractLoadBalancerRule 
    /**
     * 每个服务访问5次则换下一个服务(总共3个服务)
     * <p>
     * total=0,默认=0,如果=5,指向下一个服务节点
     * index=0,默认=0,如果total=5,index+1
     */
    private int total = 0;//被调用的次数
    private int currentIndex = 0;//当前是谁在提供服务
    //@edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE")
    public Server choose(ILoadBalancer lb, Object key) 
        if (lb == null) 
            return null;
        
        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) 
                /*
                 * No servers. End regardless of pass, because subsequent passes
                 * only get more restrictive.
                 */
                return null;
            
            //int index = chooseRandomInt(serverCount);//生成区间随机数
            //server = upList.get(index);//从或活着的服务中,随机获取一个
            //=====================自定义代码=========================
            if (total < 5) 
                server = upList.get(currentIndex);
                total++;
             else 
                total = 0;
                currentIndex++;
                if (currentIndex > upList.size()) 
                    currentIndex = 0;
                
                server = upList.get(currentIndex);//从活着的服务中,获取指定的服务来进行操作
            
            //======================================================
            if (server == null) 
                /*
                 * The only time this should happ

以上是关于负载均衡原理,探究@LoadBalanced注解都做了什么(Ribbon)的主要内容,如果未能解决你的问题,请参考以下文章

负载均衡原理,探究@LoadBalanced注解都做了什么(Ribbon)

添加了@LoadBalanced注解,即可实现负载均衡功能,这是什么原理呢?

SpringCloud微服务负载均衡器Ribbon

SpringCloud微服务负载均衡器Ribbon

微服务Ribbon负载均衡

springcloud-自定义实现负载均衡