SpringCloud组件之Ribbon使用及源码分析


本文仅是学习记录,如有错误,还请大佬们不吝指出.

SpringCloud组件之Ribbon使用及源码分析

一、什么是Ribbon?

Ribbon 是一个具有内置软件负载平衡器的进程间通信(远程过程调用)库。简单来说Ribbon就是一个客户端负载均衡器,其作用就是解析目标服务的可调用的服务列表,基于负载均衡算法来实现请求的分发。

二、Ribbon的基本使用

1.测试代码编写

编写一个简单的user服务和一个order服务来测试,通过user服务来远程调用order服务获取用户订单信息

order服务

@RestController
public class OrderService {

    @Value("${server.port}")
    private String port;
    
    @GetMapping("/orders")
    public String getAllOrder(){
        System.err.println("order service port : " + port);
        return "return all order..";
    }
}

order服务配置文件

server:
  port: 9000
spring:
  application:
    name: order-service

user服务

@RestController
public class UserController{

    @Autowired
    private RestTemplate restTemplate;

    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate(RestTemplateBuilder restTemplateBuilder){
        return restTemplateBuilder.build();
    }

    @GetMapping("/user/{userId}")
    public String getAllOrder(@PathVariable("userId") String userId){
        //调用订单服务查询用户订单信息
        String result = restTemplate.getForObject("http://order-service/orders", String.class);
        return "userId: " + userId + "  order info : "  + result;
    }

}

user服务配置文件

server:
  port: 8000
spring:
  application:
    name: user-service
# 配置order服务的提供者的地址列表,这里就相当于是注册中心中的生产者的信息
order-service:
  ribbon:
    listOfServers: localhost:9000,localhost:9100

2.结果测试

访问用户服务接口测试
访问User服务测试
这里我调用了两次,可以看出在user服务调用order服务时,是负载均衡调用的
在这里插入图片描述

三、Ribbon源码分析

1.@LoadBalanced注解

为什么仅仅加了一个LoadBalanced注解就实现了负载均衡,来看一下LoadBalanced注解

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

从LoadBalanced中可以看出,除了元注解信息,就只有一个Qualifier注解,这个Qualifier就相当于是一个标记,用来标识当前的RestTemplate,在LoadBalancerAutoConfiguration自动装配类中会去解析LoadBalanced注解,在这里面会获取到所有加了LoadBalanced注解的RestTemplate的Bean对象

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {

	//获取所有加了LoadBalanced注解的RestTemplate对象
	@LoadBalanced
	@Autowired(required = false)
	private List<RestTemplate> restTemplates = Collections.emptyList();

2.LoadBalancerAutoConfiguration自动装配

那么获取到这些加了**@LoadBalanced**注解的RestTemplate有什么作用?在LoadBalancerAutoConfiguration自动装配类中,获取到restTemplates后,对每个restTemplate做了一个包装,增加了一个拦截器

	@Bean
	public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
			final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
		return () -> restTemplateCustomizers.ifAvailable(customizers -> {
			//遍历restTemplates,对每个restTemplate进行包装,其实就是对每个restTemplate都增加一个拦截器
			for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
				for (RestTemplateCustomizer customizer : customizers) {
					customizer.customize(restTemplate);
				}
			}
		});
	}
	
	@Bean
	@ConditionalOnMissingBean
	public RestTemplateCustomizer restTemplateCustomizer(final RetryLoadBalancerInterceptor loadBalancerInterceptor) {
		//先获取默认的拦截器,再将loadBalancerInterceptor添加进拦截器链
		return restTemplate -> {
			List<ClientHttpRequestInterceptor> list = new ArrayList<>(
					restTemplate.getInterceptors());
			list.add(loadBalancerInterceptor);
			restTemplate.setInterceptors(list);
		};
	}

现在已经将所有添加了LoadBalanced注解的RestTemplate添加了一个拦截器,所有当user服务在调用order服务的时候一定会进入拦截器

restTemplate.getForObject("http://order-service/orders", String.class);

3. LoadBalancerInterceptor拦截器

那么进入拦截器之后,拦截器又做了什么? 我们进到LoadBalancerInterceptor
这里LoadBalancerInterceptor将请求拦截到之后,就委托给了LoadBalancerClient去执行,LoadBalancerClient是一个接口,最终由RibbonLoadBalancerClient实现类执行

	private LoadBalancerClient 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,this.requestFactory.createRequest(request, body, execution));
	}

4.ILoadBalancer负载均衡器

我们接着看RibbonLoadBalancerClient.execute()方法,这里面做了两个操作,一个就是根据服务名称获取负载均衡器,第二个就是根据负载均衡器获得某个Server实例

	public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
			throws IOException {
		//获取负载均衡器
		ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
		//根据负载均衡器获得一个Server实例,这里的Server是从集群中获得的某个Server实例
		Server server = getServer(loadBalancer, hint);
		if (server == null) {
			throw new IllegalStateException("No instances available for " + serviceId);
		}
		RibbonServer ribbonServer = new RibbonServer(serviceId, server,
				isSecure(server, serviceId),
				serverIntrospector(serviceId).getMetadata(server));
		return execute(serviceId, ribbonServer, request);
	}

我们先看获取负载均衡器是如何操作的,跟进方法可以看到在getInstance()实例化过程中,基于工厂模式通过服务名称,获得一个上下文环境,然后再通过type(ILoadBalancer.class)从上下文环境中得到一个ILoadBalancer 的实例,这里getInstance()的时候还做了一个操作就是基于SpringClientFactory初始化了RibbonClientConfiguration类

	public SpringClientFactory() {
		super(RibbonClientConfiguration.class, NAMESPACE, "ribbon.client.name");
	}
	
	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);
	}

    public <T> T getInstance(String name, Class<T> type) {
        AnnotationConfigApplicationContext context = this.getContext(name);
        return BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, type).length > 0 ? context.getBean(type) : null;
    }

ILoadBalancer 的类继承实现关系图
在这里插入图片描述
这里BEBUG可以看出,就构造出了一个实例ZoneAwareLoadBalancer在这里插入图片描述

5.服务列表更新与获取

拿到了负载均衡器后,紧接着看第二个操作,getServer(loadBalancer, hint),其中chooseServer()就是基于负载均衡算法选择集群中的某个服务节点

	protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
		if (loadBalancer == null) {
			return null;
		}
		// 这里 default 就是采用默认的负载均衡算法,
		return loadBalancer.chooseServer(hint != null ? hint : "default");
	}
		
	//截取ZoneAwareLoadBalancer类chooseServer方法中部分代码
	if (server != null) {
         return server;
     } else {
         logger.debug("Zone avoidance logic is not invoked.");
         //这里调用了父类的chooseServer方法
         return super.chooseServer(key);
     }

    protected IRule rule = DEFAULT_RULE;
	//根据不同key选择相应的负载均衡规则,再选择出服务节点
    public Server chooseServer(Object key) {
        if (this.counter == null) {
            this.counter = this.createCounter();
        }
        this.counter.increment();
        if (this.rule == null) {
            return null;
        } else {
            try {
            	//调用PredicateBasedRule中的choose(key)方法
                return this.rule.choose(key);
            } catch (Exception var3) {
                logger.warn("LoadBalancer [{}]:  Error choosing server for key {}", new Object[]{this.name, key, var3});
                return null;
            }
        }
    }
    
    public Server choose(Object key) {
        ILoadBalancer lb = getLoadBalancer();
        //这里lb.getAllServers()获取了所有服务列表信息,再对集群中的order-servier服务进行轮训筛选
        Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
        if (server.isPresent()) {
            return server.get();
        } else {
            return null;
        }       
    }

从上面choose方法中可以看到调用了lb.getAllServers() 方法来获取所有的服务列表信息,那么这里到底是如何获得服务列表信息的嘞? 我们进入到getAllServers()方法中

    /**
     * @return All known servers, both reachable and unreachable.
     */
     // ILoadBalancer 接口类中的一个方法
	public List<Server> getAllServers();
	
    @Monitor(name = PREFIX + "AllServerList", type = DataSourceType.INFORMATIONAL)
    protected volatile List<Server> allServerList = Collections.synchronizedList(new ArrayList<Server>());
    @Monitor(name = PREFIX + "UpServerList", type = DataSourceType.INFORMATIONAL)
    protected volatile List<Server> upServerList = Collections.synchronizedList(new ArrayList<Server>());
    
    @Override
    public List<Server> getAllServers() {
        return Collections.unmodifiableList(allServerList);
    }

可以看出getAllServers()方法操作了一个allServerList变量,并返回一个不可修改的服务列表,这里的allServerList就是客户端用来缓存服务列表信息的,那具体allServerList是什么时候被赋值得嘞?这时候我们就要回到ILoadBalancer实例初始化的时候了,我们回到RibbonClientConfiguration中ZoneAwareLoadBalancer被初始化的地方

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

当子类ZoneAwareLoadBalancer被初始化的时候会去调用父类的构造方法,进入父类DynamicServerListLoadBalancer

    public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping,
                                         ServerList<T> serverList, ServerListFilter<T> filter,
                                         ServerListUpdater serverListUpdater) {
        super(clientConfig, rule, ping);
        this.serverListImpl = serverList;
        this.filter = filter;
        this.serverListUpdater = serverListUpdater;
        if (filter instanceof AbstractServerListFilter) {
            ((AbstractServerListFilter) filter).setLoadBalancerStats(getLoadBalancerStats());
        }
        restOfInit(clientConfig);
    }

从这里我们可以看出在父类初始化的时候调用了一个restOfInit()方法,在这个方法中有两个操作方法enableAndInitLearnNewServersFeature()和updateListOfServers()

    void restOfInit(IClientConfig clientConfig) {
        boolean primeConnection = this.isEnablePrimingConnections();
        // turn this off to avoid duplicated asynchronous priming done in BaseLoadBalancer.setServerList()
        this.setEnablePrimingConnections(false);
        enableAndInitLearnNewServersFeature();

        updateListOfServers();
        if (primeConnection && this.getPrimeConnections() != null) {
            this.getPrimeConnections()
                    .primeConnections(getReachableServers());
        }
        this.setEnablePrimingConnections(primeConnection);
        LOGGER.info("DynamicServerListLoadBalancer for client {} initialized: {}", clientConfig.getClientName(), this.toString());
    }

我们先看enableAndInitLearnNewServersFeature()方法,这个方法的作用就是启动一个任务定时去更新服务列表信息

    public void enableAndInitLearnNewServersFeature() {
        LOGGER.info("Using serverListUpdater {}", serverListUpdater.getClass().getSimpleName());
        //这里开启一个updateAction的任务
        serverListUpdater.start(updateAction);
    }
    
    //updateAction任务
    protected final ServerListUpdater.UpdateAction updateAction = new ServerListUpdater.UpdateAction() {
	    @Override
	    public void doUpdate() {
	        updateListOfServers();
	    }
    };
    
	//由于服务列表信息是配置在本地的我们进入到PollingServerListUpdater实现类
    @Override
    public synchronized void start(final UpdateAction updateAction) {
        if (isActive.compareAndSet(false, true)) {
            final Runnable wrapperRunnable = new Runnable() {
                @Override
                public void run() {
                    if (!isActive.get()) {
                        if (scheduledFuture != null) {
                            scheduledFuture.cancel(true);
                        }
                        return;
                    }
                    try {
                        updateAction.doUpdate();
                        lastUpdated = System.currentTimeMillis();
                    } catch (Exception e) {
                        logger.warn("Failed one update cycle", e);
                    }
                }
            };
			
			//使用scheduled来做定时任务调用
            scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay(
            		//定时更新客户端缓存中的服务列表信息的任务
                    wrapperRunnable,
                    //任务延迟1s执行
                    initialDelayMs,
                    //会先读取配置文件中的ServerListRefreshInterval的信息,没有配置就默认30s执行一次
                    refreshIntervalMs,
                    TimeUnit.MILLISECONDS
            );
        } else {
            logger.info("Already active, no-op");
        }
    }
	
	//获取服务列表刷新间隔时间
    private static long getRefreshIntervalMs(IClientConfig clientConfig) {
       return clientConfig.get(CommonClientConfigKey.ServerListRefreshInterval, LISTOFSERVERS_CACHE_REPEAT_INTERVAL);
    }
    

到这里其实可以看到,最终执行的任务也是updateListOfServers() 目的就是不断更新客户端缓存中的服务列表信息

    @VisibleForTesting
    public void updateListOfServers() {
        List<T> servers = new ArrayList<T>();
        if (serverListImpl != null) {
       		 //读取配置文件中配置的服务列表信息
            servers = serverListImpl.getUpdatedListOfServers();
            LOGGER.debug("List of Servers for {} obtained from Discovery client: {}",
                    getIdentifier(), servers);

            if (filter != null) {
                servers = filter.getFilteredListOfServers(servers);
                LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}",
                        getIdentifier(), servers);
            }
        } 
        //将读取到的服务列表信息设置到客户端缓存中,也就是将值赋值给BaseLoadBalancer中的 allServerList
        updateAllServerList(servers);
    }
    
    //读取配置文件中配置的服务列表信息
	@Override
	public List<Server> getUpdatedListOfServers() {
        String listOfServers = clientConfig.get(CommonClientConfigKey.ListOfServers);
        return derive(listOfServers);
	}
	
    protected void updateAllServerList(List<T> ls) {
		//···
	             setServersList(ls);
		//···
    }
  
    //DynamicServerListLoadBalancer
    public void setServersList(List lsrv) {
        //这里调用父类进行赋值
        super.setServersList(lsrv);
        //···
    }

	//BaseLoadBalancer中进行赋值操作
    public void setServersList(List lsrv) {
        //···
        allServerList = allServers;
        //···
	}

6.负载均衡获取服务节点信息

到这里我们已经获取到了服务列表信息,接下来就是基于负载均衡算法,获取某个服务节点

 public Optional<Server> chooseRoundRobinAfterFiltering(List<Server> servers, Object loadBalancerKey) {
        //获取服务节点信息
        List<Server> eligible = getEligibleServers(servers, loadBalancerKey);
        if (eligible.size() == 0) {
            return Optional.absent();
        }
        //这里是轮训算法实现,将服务节点数量传进去,这里incrementAndGetModulo(eligible.size())就是算出下一个服务节点的下标,eligible.get(index),返回目标服务节点
        return Optional.of(eligible.get(incrementAndGetModulo(eligible.size())));
    }
    
	//轮训算法实现
    private int incrementAndGetModulo(int modulo) {
        for (;;) {
            int current = nextIndex.get();
            int next = (current + 1) % modulo;
            if (nextIndex.compareAndSet(current, next) && current < modulo)
                return current;
        }
    }

List<Server> eligible 获取的服务节点信息,以及通过轮训计算后获得的order-service服务节点信息
服务节点信息

7.重构请求Url

拿到服务节点信息之后,最后一个操作就是重构请求 Url ,将http://order-service/orders请求地址重构为服务节点信息对应的地址http://localhost:9100/orders

	@Override
	public ListenableFuture<ClientHttpResponse> intercept(final HttpRequest request, final byte[] body, final AsyncClientHttpRequestExecution execution) throws IOException {
		final URI originalUri = request.getURI();
		String serviceName = originalUri.getHost();
		return this.loadBalancer.execute(serviceName, new LoadBalancerRequest<ListenableFuture<ClientHttpResponse>>() {
			@Override
			public ListenableFuture<ClientHttpResponse> apply(final ServiceInstance instance) throws Exception {
				//ServiceRequestWrapper中进行了请求地址的重构
				HttpRequest serviceRequest = new ServiceRequestWrapper(request,instance, AsyncLoadBalancerInterceptor.this.loadBalancer);
				return execution.executeAsync(serviceRequest, body);
			}
		});
	}
	
	//请求重构方法
	@Override
	public URI getURI() {
		URI uri = this.loadBalancer.reconstructURI(this.instance, getRequest().getURI());
		return uri;
	}

在这里插入图片描述


四、总结

从整个流程上来看,Ribbon 先获取所有带 @LoadBalanced 注解的RestTemplate对象,并对其增加一个拦截器对其增强,当客户端发起请求的时候,会被拦截器拦截。并委托给了LoadBalancerClient去执行,之后由实现类SpringClientFactory去获取负载均衡器ILoadBalancer(接口) ,在获取负载均衡器ZoneAwareLoadBalancer(实现类)的时候会去调用父类DynamicServerListLoadBalancer的构造方法,其中的restOfInit()的方法会被执行,该方法会去创建一个schedule,该任务会定时去拉取最新的服务端的列表信息同步至客户端缓存,之后服务端的服务状态信息会通过IPing不同的实现策略来定时检测服务端服务是否有效。如果失效会被剔除。在获取到服务端的有效列表之后,会通过负载均衡规则IRule来对服务进行筛选从而从集群中获得某个具体的Server实例,获得Server实例后会去重构请求Url,将Url中的服务名称替换为具体的Server服务地址,最后向目标服务发送请求.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值