微服务架构之均衡组件 Ribbon解析:和RestTemplate一起使用

举报
qingting-fly 发表于 2021/07/02 17:31:35 2021/07/02
【摘要】 Ribbon可以和RestTemplate一起使用,也可以集成到Feign中使用。下面的文章将会分别讲述Ribbon在这两种使用方法下的示例。 和RestTemplate一起使用Ribbon和RestTemplate之间存在一定的关系,而使二者产生联系的就是@LoadBalanced注解。@LoadBalanced是一个标记注解,可以表明被修饰的RestTemplate应该使用RibbonL...

Ribbon可以和RestTemplate一起使用,也可以集成到Feign中使用。下面的文章将会分别讲述Ribbon在这两种使用方法下的示例。

和RestTemplate一起使用

RibbonRestTemplate之间存在一定的关系,而使二者产生联系的就是@LoadBalanced注解。@LoadBalanced是一个标记注解,可以表明被修饰的RestTemplate应该使用RibbonLoadBalancerClient来发送HTTP请求。RibbonLoadBalancerClient是Ribbon Client实例,用来进行HTTP请求的发送和响应的接受,后续的文章将会对它进行讲解。

/**
 * 使用这个注解标注RestTemplate bean可以让LoadBalanceClient来配置这个RestTemplate
 */
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}

Ribbon是如何识别被@LoadBalanced标注的RestTemplate实例的呢?如何将Ribbon相关的拦截器添加到RestTemplate中呢?关键在于LoadBalancerAutoConfiguration这个配置类中。LoadBalancerAutoConfiguration类初始化一个匿名的SmartInitializingSingleton实例在所有的RestTemplate实例初始化结束之后对该进行配置。根据Spring框架的原理,SmartInitializingSingletonafterSingletonsInstantiated`会在所有单例类型的Bean实例创建完成之后被调用。

afterSingletonsInstantiated中,Ribbon使用RestTemplateCustomizerRestTemplate实例进行配置,加入自己的拦截器。

@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
//只有当RestTemplate class和LoadBalancerClient的bean都存在时才执行。LoadBalancerClient会在RibbonAutoConfiguration配置类中给出。
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {

	@LoadBalanced
	@Autowired(required = false)
	private List<RestTemplate> restTemplates = Collections.emptyList();
    /**
     * 创建SmartInitializingSingleton接口的实现类。Spring会在所有
     * 单例Bean初始化完成后回调该实现类的afterSingletonsInstantiated()
     * 方法。在这个方法中会为所有被@LoadBalanced注解标识的
     * RestTemplate添加ribbon的自定义拦截器LoadBalancerInterceptor。
     */
	@Bean
	public SmartInitializingSingleton loadBalancedRestTemplateInitializer(
			final List<RestTemplateCustomizer> customizers) {
		return new SmartInitializingSingleton() {
			@Override
			public void afterSingletonsInstantiated() {
				for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
					for (RestTemplateCustomizer customizer : customizers) {
						customizer.customize(restTemplate);
					}
				}
			}
		};
	}
}

 通过LoadBalancerInterceptorConfig或者RetryAutoConfiguration配置类的RestTemplateCustomizer函数,Ribbon提供了对RestTemplate进行配置的RestTemplateCustomizer实例。它会将Ribbon自己的LoadBalancerInterceptor实例添加到RestTemplate的HTTP拦截器列表中。

/**
 * 创建Ribbon自定义拦截器LoadBalancerInterceptor
 * 创建前提是当前classpath下不存在spring-retry。
 * 所以LoadBalancerInterceptor是默认的Ribbon拦截
 * 请求的拦截器。
*/
@Configuration
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {
    @Bean
    public LoadBalancerInterceptor ribbonInterceptor(
        LoadBalancerClient loadBalancerClient,
        LoadBalancerRequestFactory requestFactory) {
        //提供Ribbon实现的拦截器
        return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
    }

    /**
    * 添加拦截器具体方法。首先获取当前拦截器集合(List)
    * 然后将loadBalancerInterceptor添加到当前集合中
    * 最后将新的集合放回到restTemplate中。
    */
    @Bean
    @ConditionalOnMissingBean
    public RestTemplateCustomizer restTemplateCustomizer(
        final LoadBalancerInterceptor loadBalancerInterceptor) {
        return new RestTemplateCustomizer() {
        @Override
        public void customize(RestTemplate restTemplate) {
            //将自己的LoadBalancerInterceptor拦截器添加到RestTemplate实例中
            List<ClientHttpRequestInterceptor> list = new ArrayList<>(
                    restTemplate.getInterceptors());
            list.add(loadBalancerInterceptor);
            restTemplate.setInterceptors(list);
        }
        };
    }
}

LoadBalancerInterceptor实现了ClientHttpRequestInterceptor接口。ClientHttpRequestInterceptor接口是客户端HTTP请求拦截器接口,它可以被注册给RestTemplate实例上,来修改发送的ClientHttpRequest对象和接受的ClientHttpResponse对象。

public interface ClientHttpRequestInterceptor {
    /**
     *实现类可以实现该方法,在该方法内完成拦截请求后的逻辑内容。
     *对于Ribbon而言,在该方法内完成了根据具体规则从服务集群中选取一个服务,并向该服务发起请求的操作。
     */
   ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException;
}

LoadBalancerInterceptor会在RestTemplate发送网络请求时使用LoadBalancerClient实例来代替RestTemplate进行实际的网络请求的发送和处理。LoadBalancerRequestFactory会将HttpRequest转化为LoadbalancerRequest

//LoadBalancerInterceptor是Ribbon对于ClientHttpRequestInterceptor接口的实现类。
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));
	}

	@Override
	public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
			final ClientHttpRequestExecution execution) throws IOException {
		final URI originalUri = request.getURI();
		String serviceName = originalUri.getHost();
        //将请求交给LoadBalancerClient来执行,具体执行过程之后会详解。
		return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
	}
}

如上的代码即可实现 Ribbon 与RestTemplate一起使用。

小结

Ribbon使用的是客户端负载均衡。客户端负载均衡和服务端负载均衡最大的区别在于服务清单所存储的位置,在客户端负载均衡中,所有的客户端节点都有一份自己要访问的服务端地址列表,这些列表统统都是从Eureka服务注册中心获取的。在Spring Cloud中我们如果想要使用客户端负载均衡,方法很简单,开启@LoadBalanced注解即可,这样客户端在发起请求的时候会先自行选择一个服务端,向该服务端发起请求,从而实现负载均衡。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。