RestTemplate与Ribbon整合原理(基于spring-cloud-starter-alibaba-nacos-discovery、LoadBalancerClient负载均衡接口)


相关文章:
Nacos Spring Cloud 快速开始 nacos入门例子,里面用到了RestTemplate
springcloud中RestTemplate三种使用方式(LoadBalancerClient、负载均衡、Nacos、Ribbon ) 介绍RestTemplate的常见用法
RestTemplate与Ribbon结合原理(基于spring-cloud-starter-alibaba-nacos-discovery)

前言

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

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

Spring Cloud Ribbon是基于Netflix Ribbon 实现的一套客户端的负载均衡工具,Ribbon客户端组件提供一系列的完善的配置,如超时,重试等。通过Load Balancer获取到服务提供的所有机器实例,Ribbon会自动基于某种规则(轮询,随机)去调用这些服务。Ribbon也可以实现我们自己的负载均衡算法。

Ribbon的原理是代理机制,通过代理实现LoadBalancerClient统一负载均衡接口,对应的实现类是RibbonLoadBalancerClient

除了Ribbon,还可以用其它的负载均衡框架,例如spring-cloud-loadbalancer框架,对应的实现类是BlockingLoadBalancerClient,对spring-cloud-loadbalancer框架的介绍参见 【spring cloud hoxton】Ribbon 真的能被 spring-cloud-loadbalancer 替代吗(负载均衡)

1. 名词解释

1.1 RestTemplate

restTemplate作用是发送http请求的客户端,功能类似httpclient,是spring原生的框架,用于2个服务之间的请求发送。

与其功能相似的是Spring Cloud Netflix桶的Feign技术。

package org.springframework.web.client;
  public class RestTemplate extends InterceptingHttpAccessor implements RestOperations {
  
 }

RestTemplate位于spring的web包,是spring web的基本模块。因此,一般通过spring-boot-starter-web来引入。

1.2 Nacos

Nacos是Spring Cloud Alibaba下的服务注册和发现框架,与之类似的技术是Spring Cloud Netflix桶的Eureka。

注意:

  • Spring Cloud Nacos描述了服务如何进行注册,注册到哪里,服务消费者如何获取服务生产者的服务信息。

    当然真正连接的时候,需要Nacos-client.jar、Eureka-client.jar之类的包,这样才能与注册中心进行交互。

  • Nacos只是维护了服务生产者与注册中心关系服务消费者与注册中心关系,真正的服务消费者调用服务生产者提供的数据是通过RestTemplate实现的。

  • 如果存在多个服务生产者,那么服务消费者调用哪个合适呢?此时需要Spring Cloud Ribbon来解决

1.3 负载均衡器 Ribbon

所谓负载均衡,就是为资源分配负载,就是选择合适的服务处理请求。

在微服务架构下,SpringCloud提供了统一的客户端负载均衡器的接口LoadBalancerClient,需要具体的技术来实现该接口,而Ribbon框架提供RibbonLoadBalancerClient作为实现类。

负载均衡器分为客户端负载均衡器和服务器端负载均衡器(nginx)

我们在微服务架构中,往往通过RestTemplate发送RPC请求,然后通过Ribbon做客户端负载均衡。那么它们是如何配合工作的。

1.4 Ribbon、nacos整合

Ribbon 需要访问nacos,获得可用服务列表,spring cloud 提供了统一的查询服务列表的接口类,由nacos提供实现。

2. RestTemplate与Ribbon整合

很简单,通过 @LoadBalanced标记即可。当然前提是需要引入ribbon的依赖。

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

完整示例参见

3. RestTemplate与Ribbon整合的原理

3.1 Ribbon的RibbonLoadBalancerClient类

前文知道SpringCloud提供了统一的客户端负载均衡器的接口LoadBalancerClient,而Ribbon使用RibbonLoadBalancerClient作为实现类:
在这里插入图片描述
从上图得知,RibbonLoadBalancerClient的choose()方法,最终委托Ribbon内部的ILoadBalancer接口实现具体的功能

3.1.1 Ribbon的ILoadBalancer接口

RibbonLoadBalancerClient的choose()方法,最终委托Ribbon内部的ILoadBalancer接口实现具体的功能

// ILoadBalancer接口定义了负载均衡器的操作,包括初始化服务列表、选择服务实例、关停服务、获取服务列表等
interface ILoadBalancer {
	void addServers(List<Server> newServers);
	Server chooseServer(Object key);
	void markServerDown(Server server);
	List<Server> getReachableServers();
	List<Server> getAllServers();
}

ILoadBalancer的直接实现类是抽象类AbstractLoadBalancer

3.1.2 Ribbon的AbstractLoadBalancer

ILoadBalancer的直接实现类是抽象类AbstractLoadBalancer

  • 抽象实现类,根据服务实例的状态,定义了一个服务分组枚举类ServerGroup,包含三种状态:All,Up,Not_Up
  • 相应的定义了根据分组类型获取服务实例列表的getServerList方法
  • 此外,还定义了获取负载均衡器统计信息的getLoadBalancerStats方法和选择服务实例的默认方法chooseServer
abstract class AbstractLoadBalancer implements ILoadBalancer{
	public enum ServerGroup {
		ALL, STATUS_UP, STATUS_NOT_UP
	}
	public Server chooseServer() { return chooseServer(null); } 
	public abstract List<Server> getServerList(ServerGroup serverGroup);
	public abstract LoadBalancerStats getLoadBalancerStats();
}

3.1.3 Ribbon的BaseLoadBalancer

BaseLoadBalancer继承自AbstractLoadBalancer,实现了ILoadBalancer定义的所有方法和AbstractLoadBalancer中的抽象方法。

BaseLoadBalancer是一个完整的负载均衡器的实现类,主要由以下职责:初始化服务实例列表、选择服务实例、关停服务、获取服务实例列表

class BaseLoadBalancer extends AbstractLoadBalancer {
	psf IRule DEFAULT_RULE = new RoundRobinRule();
	psf SerialPingStrategy DEFAULT_PING_STRATEGY = new SerialPingStrategy();
	IRule rule = DEFAULT_RULE; // IRule接口有一个Server choose(Object key)方法,用于选择一个合适的服务实例,BaseLoadBalancer的chooseServer方法就是将工作委托给IRule的choose来完成,默认使用RoundRobinRule——线性负载均衡
	IPingStrategy pingStrategy = DEFAULT_PING_STRATEGY; // IPingStrategy接口有一个boolean[] pingServers(IPing, Server[]),定义了ping服务实例的策略,默认使用SerialPingStrategy——使用for循环线性遍历
	IPing ping = null; // IPing接口有一个boolean isAlive(Server)方法,用来定义如何去ping一个服务实例,判断其是否处于正常状态
	@Monitor(name = PREFIX + "AllServerList", type = DataSourceType.INFORMATIONAL)
	volatile List<Server> allServerList = Collections.synchronizedList(new ArrayList<>()); // 全部服务实例
	@Monitor(name = PREFIX + "UpServerList", type = DataSourceType.INFORMATIONAL)
	volatile List<Server> upServerList = Collections.synchronizedList(new ArrayList<>()); // UP正常状态的服务实例
	LoadBalancerStats lbStats; // 存储统计信息
	List<ServerListChangeListener> changeListeners = new CopyOnWriteArrayList<>(); // 监听服务变化的监听器,
	List<ServerStatusChangeListener> serverStatusListeners = new CopyOnWriteArrayList<>(); // 监听服务实例的状态变化的监听器,markServerDown时回调
	
	// -----------AbstractLoadBalancer接口定义的方法-----------
	void addServers(List<Server> newServers) { 
		ArrayList<Server> newList = new ArrayList<>();
		newList.addAll(allServerList); // 将原已维护的服务实例allServerList
		newList.addAll(newServers);    // 和新传入的服务实例newServers一起,加入到newList中
		setServersList(newList);	   // 然后调用setServersList更新服务实例清单
	}
	Server chooseServer(Object key) { 
		return rule.choose(key);	// 委托IRule选择服务实例
	}
	void markServerDown(Server server) { 
		server.setAlive(false);		// 标记服务状态
		notifyServerStatusChangeListener(Collections.singleton(server));	// 通知serverStatusListeners中的ServerStatusChangeListener,回调serverStatusChanged方法
	}
	List<Server> getReachableServers() { 
		return Collections.unmodifiableList(upServerList);	// 返回维护的正常服务实例清单upServerList
	}
	List<Server> getAllServers() {
		return Collections.unmodifiableList(allServerList); // 返回维护的所有服务实例清单allServerList
	}

	// -----------AbstractLoadBalancer定义的方法-----------
	List<Server> getServerList(ServerGroup serverGroup) { // 根据传入的不同分组,返回不同的服务实例清单
		ALL -> allServerList;
		STATUS_UP -> upServerList;
		STATUS_NOT_UP -> allServerList.removeAll(upServerList);
	}
	LoadBalancerStats getLoadBalancerStats() { // 返回统计信息
		return lbStats;
	}
	
	// -----------BaseLoadBalancer自身的方法-----------
	void setupPingTask() { // 启动ping任务,间隔10s检查allServerList中的Server是否健康,并将状态变化的server通知到serverStatusListeners
		// 由BaseLoadBalancer的各构造函数调用,以及ping相关设置变化时调用
		lbTimer = new ShutdownEnabledTimer(name="NFLoadBalancer-PingTimer-" + name, daemon=true);
		lbTimer.schedule(new PingTask(), delay=0, period=pingIntervalSeconds * 1000);
		forceQuickPing();
	}
	public void setServersList(List lsrv) {
		// 比较传入的lsrv中的Server和allServerList中的Server是否相同
		// 如果不同,对changeListeners中的每个ServerListChangeListener,回调其serverListChanged方法
		// 用lsrv中的Server覆盖allServerList
	}
}

3.2 Ribbon的拦截器和@LoadBalanced语法

LoadBalancerAutoConfiguration配置类的作用是将所有被@LoadBalanced注解修饰的RestTemplate beanLoadBalancerInterceptor拦截器进行增强,而LoadBalancerInterceptor又包含loadBalancerClient,这样当用RestTemplate调用时,会首先调用拦截器方法,在拦截器方法里使用loadBalancerClient真正实现负载均衡以及url转换,达到服务名到真正的host之间的转换和负载均衡;

3.2.1 LoadBalancerAutoConfiguration

Ribbon是使用拦截器来实现服务的远程调用的,源码如下:

public class LoadBalancerAutoConfiguration {

	@LoadBalanced
	@Autowired(required = false)
	private List<RestTemplate> restTemplates = Collections.emptyList();

	@Autowired(required = false)
	private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();

	@Bean
	// 循环全部增强类去给restTemplate增强,也是在这里给它添加拦截器的
	public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
			final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
		return () -> restTemplateCustomizers.ifAvailable(customizers -> {
		     //【注入负载均衡器】
			for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
			//【增强RestTemplate】
				for (RestTemplateCustomizer customizer : customizers) {
					customizer.customize(restTemplate);
				}
			}
		});
	}

	@Bean
	@ConditionalOnMissingBean
	public LoadBalancerRequestFactory loadBalancerRequestFactory(
			LoadBalancerClient loadBalancerClient) {
		return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers);
	}

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
	static class LoadBalancerInterceptorConfig {

		@Bean
		// 注册负载均衡拦截器,LoadBalancerClient 是执行正在服务在均衡的功能类
		public LoadBalancerInterceptor ribbonInterceptor(
				LoadBalancerClient loadBalancerClient,
				LoadBalancerRequestFactory requestFactory) {
			return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
		}

		@Bean
		@ConditionalOnMissingBean
		// 注册RestTemplate的增强类
		public RestTemplateCustomizer restTemplateCustomizer(
				final LoadBalancerInterceptor loadBalancerInterceptor) {
			return restTemplate -> {
				List<ClientHttpRequestInterceptor> list = new ArrayList<>(
						restTemplate.getInterceptors());
				// 添加拦截器
				list.add(loadBalancerInterceptor);
				restTemplate.setInterceptors(list);
			};
		}

	}
  //  略......


从上面代码可以看到,增强的核心类是LoadBalancerInterceptor

3.2.2 LoadBalancerInterceptor

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {

	private LoadBalancerClient loadBalancer;
	private LoadBalancerRequestFactory requestFactory;

	@Override
	public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
			final ClientHttpRequestExecution execution) throws IOException {
		final URI originalUri = request.getURI();
		String serviceName = originalUri.getHost();

		return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
	}
}

进入LoadBalancerInterceptor的intercept()可以看到,它是使用LoadBalancerClient去执行真正的流程的,LoadBalancerClient是负责调用流程的。

3.2.3 RibbonLoadBalancerClient

上述源码中LoadBalancerClient 是一个负载均衡的接口,有不同的负载均衡客户端实现,如果采用Ribbon (或nacos 的spring-cloud-starter-alibaba-nacos-discovery,自带Ribbon )这里的实现是RibbonLoadBalancerClient

	public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
			throws IOException {
		// 【1】从容器中获取负载均衡器
		ILoadalancer loadBalancer = getLoadBalancer(serviceId);
		// 【2】使用ILoadBalancer获取一个服务
		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));
		// 【3】调用这个服务
		return execute(serviceId, ribbonServer, request);
	}


  • 1.从容器中获取负载均衡器
  • 2.使用ILoadBalancer获取一个服务
  • 3.调用这个服务
3.2.3.1 getLoadBalancer

从容器中获取负载均衡器,在RibbonClientConfiguration给容器注册了ZoneAwareLoadBalancer,所以实际获取的就是它:
在这里插入图片描述
接着看一个ZoneAwareLoadBalancer的构建过程做了什么,点进去会来到它的父类的DynamicServerListLoadBalancer构造方法,源码如下:

 public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping,
                                         ServerList<T> serverList, ServerListFilter<T> filter,
                                         ServerListUpdater serverListUpdater) {
        // 调用上级构造,主要初始化一些配置信息
        super(clientConfig, rule, ping);
        // serverList服务列表,用于发现服务的
        this.serverListImpl = serverList;
        // 服务的过滤器
        this.filter = filter;
        // 服务列表的更新器
        this.serverListUpdater = serverListUpdater;
        if (filter instanceof AbstractServerListFilter) {
            ((AbstractServerListFilter) filter).setLoadBalancerStats(getLoadBalancerStats());
        }
        // 初始化,拉取服务和开启服务更新
        restOfInit(clientConfig);
    }

核心方法是restOfInit() 初始化,负责拉取服务和开启服务更新

3.2.3.1.1 restOfInit()
    void restOfInit(IClientConfig clientConfig) {
        boolean primeConnection = this.isEnablePrimingConnections();
        // turn this off to avoid duplicated asynchronous priming done in BaseLoadBalancer.setServerList()
        this.setEnablePrimingConnections(false);
        // 【1】开启服务列表的监听
        enableAndInitLearnNewServersFeature();
		// 【2】拉取服务
        updateListOfServers();
        if (primeConnection && this.getPrimeConnections() != null) {
            this.getPrimeConnections()
                    .primeConnections(getReachableServers());
        }
        this.setEnablePrimingConnections(primeConnection);
        LOGGER.info("DynamicServerListLoadBalancer for client {} initialized: {}", clientConfig.getClientName(), this.toString());
    }

  • enableAndInitLearnNewServersFeature()是开启一个定时任务拉取服务
  • 调用的updateListOfServers拉取服务

主要看updateListOfServers是如何拉取服务的,方法源码如下:

    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);
            }
        }
        // 更新内存的服务列表信息
        updateAllServerList(servers);
    }

变量serverListImpl 是负载均衡的规范接口,类型为ServerList,用于获取服务的,Nacos也是实现的这个接口给Ribbon获取的服务列表的,对应的实现类是NacosServerList:

public interface ServerList<T extends Server> {

    public List<T> getInitialListOfServers();
    
    /**
     * Return updated list of servers. This is called say every 30 secs
     * (configurable) by the Loadbalancer's Ping cycle
     * 
     */
    public List<T> getUpdatedListOfServers();   

}

核心抽象方法是getUpdatedListOfServers();

3.2.3.1.2 NacosServerList

NacosServerList的部分源码:

public class NacosServerList extends AbstractServerList<NacosServer> {

	private NacosDiscoveryProperties discoveryProperties;
	@Override
	public List<NacosServer> getUpdatedListOfServers() {
		return getServers();
	}

	private List<NacosServer> getServers() {
		try {
			String group = discoveryProperties.getGroup();
			// 使用Nacos的NameingService发起Api调用获取服务的实例列表
			List<Instance> instances = discoveryProperties.namingServiceInstance()
					.selectInstances(serviceId, group, true);
			return instancesToServerList(instances);
		}
		catch (Exception e) {
			throw new IllegalStateException(
					"Can not get service instances from nacos, serviceId=" + serviceId,
					e);
		}
	}

使用Nacos的NameingService发起Api调用获取服务的实例列表,由NacosDiscoveryProperties 类来实现

到这也就知道服务的发现是怎样的了,剩下的就是根据负载策略选择一个服务去调用了。接着看下一步getServer

3.2.3.2 getServer

重新回到RibbonLoadBalancerClient的第二步getServer,这一步是使用负载均衡器选择一个服务:

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

因为默认的话是不会使用zone的所以会直接进入到BaseLoadBalancerchooseServer方法,源码如下:

public  class BaseLoadBalancer  {

    public Server chooseServer(Object key) {
    	// 创建计数器或者+1操作
        if (counter == null) {
            counter = createCounter();
        }
        counter.increment();
        if (rule == null) {
            return null;
        } else {
            try {
            	// 使用负载策略选择一个服务
                return rule.choose(key);
            } catch (Exception e) {
                logger.warn("LoadBalancer [{}]:  Error choosing server for key {}", name, key, e);
                return null;
            }
        }
    }

这一步使用负载策略选择一个服务服务了,至于Rule策略,我会在下一步文章分析,接着看下一步

3.2.3.3 execute

这一步就是调用服务了:

	public <T> T execute(String serviceId, ServiceInstance serviceInstance,
			LoadBalancerRequest<T> request) throws IOException {
		Server server = null;
		if (serviceInstance instanceof RibbonServer) {
			server = ((RibbonServer) serviceInstance).getServer();
		}
		if (server == null) {
			throw new IllegalStateException("No instances available for " + serviceId);
		}

		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 and rethrow so RestTemplate behaves correctly
		catch (IOException ex) {
			statsRecorder.recordStats(ex);
			throw ex;
		}
		catch (Exception ex) {
			statsRecorder.recordStats(ex);
			ReflectionUtils.rethrowRuntimeException(ex);
		}
		return null;
	}

Ribbon的负载与Nacos的服务发现流程到这就基本分析完了!!

4. 负载均衡策略

具体的策略配置,待后续补充

事实上,DynamicServerListLoadBalancer是通过父类BaseLoadBalancer注入的负载均衡规则IRule接口的实现类完成负载均衡策略的。该接口的实现类是RoundRobinRule,它实现的策略是轮询规则。(这里是通过拉取server的列表,然后通过索引的原子性操作来完成轮询)

public class RoundRobinRule 
    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            log.warn("no load balancer");
            return null;
        }

        Server server = null;
        int count = 0;
        while (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)) {
                log.warn("No up servers available from load balancer: " + lb);
                return null;
            }

            int nextServerIndex = incrementAndGetModulo(serverCount);
            server = allServers.get(nextServerIndex);

            if (server == null) {
                /* Transient. */
                Thread.yield();
                continue;
            }

            if (server.isAlive() && (server.isReadyToServe())) {
                return (server);
            }

            // Next.
            server = null;
        }

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

参考

SpringCloud客户端负载均衡——Ribbon的LoadBalancer(负载均衡器)

Ribbon原理与Nacos的服务发现原理分析

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值