Spring Cloud Ribbon如何操作与原理剖析详解?

2026-06-08 17:43:25 692阅读 0评论 SEO基础
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计1983个文字,预计阅读时间需要8分钟。

这篇文章主要介绍了Spring Cloud Ribbon的使用方法和原理解析,通过示例代码展示了其非详细的使用方式,对于有志于学习或从事相关工作的人来说具有一定的参考价值,需要的伙伴可以参考学习。

Spring Cloud Ribbon如何操作与原理剖析详解?

这篇文章主要介绍了spring cloud Ribbon用法及原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

简介

Spring Cloud Ribbon如何操作与原理剖析详解?

这篇文章主要介绍一下ribbon在程序中的基本使用,在这里是单独拿出来写用例测试的,实际生产一般是配置feign一起使用,更加方便开发。同时这里也通过源码来简单分析一下ribbon的基本实现原理。

基本使用

这里使用基于zookeeper注册中心+ribbon的方式实现一个简单的客户端负载均衡案例。

服务提供方

首先是一个服务提供方。代码如下。

application.properties配置文件

spring.application.name=discovery-service
server.port=0
service-B.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
bootstrap.properties配置文件

spring.cloud.zookeeper.connect-string=192.168.0.15:2181
引导程序,提供了一个ribbonService的rest接口服务,注册程序到zookeeper中。

@SpringBootApplication @EnableDiscoveryClient @RestController public class DiscoverClient { public static void main(String[] args) { SpringApplication.run(DiscoverClient.class, args); } ​ @RequestMapping("/ribbonService") public String ribbonService(){ return "hello too ribbon"; } }

服务调用方

服务调用方就是进行负载均衡的一方,利用ribbo的RestTemplate进行负载调用服务。

RibbonConfig,配置ribbon的RestTemplate,通过@LoadBalanced注解实现,具体原理稍后分析。

@Configuration public class RibbonConfig { ​ /** * 实例化ribbon使用的RestTemplate * @return */ @Bean @LoadBalanced public RestTemplate rebbionRestTemplate(){ return new RestTemplate(); } /** * 配置随机负载策略,需要配置属性service-B.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule */ @Bean public IRule ribbonRule() { return new RandomRule(); } } ​

引导程序

@SpringBootApplication(scanBasePackages = "garine.learn.ribbon.loadblance") @EnableDiscoveryClient @RestController public class TestRibbonApplocation { public static void main(String[] args) { SpringApplication.run(TestRibbonApplocation.class, args); } ​ @Autowired @LoadBalanced RestTemplate restTemplate; ​ @GetMapping("/{applicationName}/ribbonService") public String ribbonService(@PathVariable("applicationName") String applicationName){ return restTemplate.getForObject("" + applicationName+"/ribbonService", String.class); } }

配置文件同上,服务名称修改即可。

测试

启动两个discovery-service,由于端口设置为0,所以是随机端口。

启动服务调用方

浏览器访问服务调用方的提供的接口,路径参数需要加上调用的服务名称,例如localhost:8080/discovery-service/ribbonService,然后服务调用方使用ribbon的RestTemplate调用服务提供方的接口。

结果返回:hello too ribbon ,同时服务提供方启动的两个服务都可能被调用,取决于怎么配置负载策略。

上面就是一个简单使用ribbon的例子,结合feign使用基本上是做类似上面所写的工作,那么ribbon到底是怎么实现的呢?

原理与源码分析

ribbon实现的关键点是为ribbon定制的RestTemplate,ribbon利用了RestTemplate的拦截器机制,在拦截器中实现ribbon的负载均衡。负载均衡的基本实现就是利用applicationName从服务注册中心获取可用的服务地址列表,然后通过一定算法负载,决定使用哪一个服务地址来进行http调用。

Ribbon的RestTemplate

RestTemplate中有一个属性是List<ClientHttpRequestInterceptor> interceptors,如果interceptors里面的拦截器数据不为空,在RestTemplate进行http请求时,这个请求就会被拦截器拦截进行,拦截器实现接口ClientHttpRequestInterceptor,需要实现方法是

ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException;

也就是说拦截器需要完成http请求,并封装一个标准的response返回。

ribbon中的拦截器

在Ribbon 中也定义了这样的一个拦截器,并且注入到RestTemplate中,是怎么实现的呢?

在Ribbon实现中,定义了一个LoadBalancerInterceptor,具体的逻辑先不说,ribbon就是通过这个拦截器进行拦截请求,然后实现负载均衡调用。

拦截器定义在org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration.LoadBalancerInterceptorConfig#ribbonInterceptor

@Configuration @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate") static class LoadBalancerInterceptorConfig { @Bean //定义ribbon的拦截器 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); }; } }

ribbon中的拦截器注入到RestTemplate

定义了拦截器,自然需要把拦截器注入到、RestTemplate才能生效,那么ribbon中是如何实现的?上面说了拦截器的定义与拦截器注入器的定义,那么肯定会有个地方使用注入器来注入拦截器的。

在org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration#loadBalancedRestTemplateInitializerDeprecated方法里面,进行注入,代码如下。

@Configuration @ConditionalOnClass(RestTemplate.class) @ConditionalOnBean(LoadBalancerClient.class) @EnableConfigurationProperties(LoadBalancerRetryProperties.class) public class LoadBalancerAutoConfiguration { ​ @LoadBalanced @Autowired(required = false) private List<RestTemplate> restTemplates = Collections.emptyList(); ​ @Bean public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated( final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) { //遍历context中的注入器,调用注入方法。 return () -> restTemplateCustomizers.ifAvailable(customizers -> { for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) { for (RestTemplateCustomizer customizer : customizers) { customizer.customize(restTemplate); } } }); } //...... }

遍历context中的注入器,调用注入方法,为目标RestTemplate注入拦截器,注入器和拦截器都是我们定义好的。

还有关键的一点是:需要注入拦截器的目标restTemplates到底是哪一些?因为RestTemplate实例在context中可能存在多个,不可能所有的都注入拦截器,这里就是@LoadBalanced注解发挥作用的时候了。

LoadBalanced注解

严格上来说,这个注解是spring cloud实现的,不是ribbon中的,它的作用是在依赖注入时,只注入实例化时被@LoadBalanced修饰的实例。

例如我们定义Ribbon的RestTemplate的时候是这样的

@Bean @LoadBalanced public RestTemplate rebbionRestTemplate(){ return new RestTemplate(); }

因此才能为我们定义的RestTemplate注入拦截器。

那么@LoadBalanced是如何实现这个功能的呢?其实都是spring的原生操作,@LoadBalance的源码如下

/** * Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient * @author Spencer Gibb */ @Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Qualifier public @interface LoadBalanced { }

很明显,‘继承'了注解@Qualifier,我们都知道以前在xml定义bean的时候,就是用Qualifier来指定想要依赖某些特征的实例,这里的注解就是类似的实现,restTemplates通过@Autowired注入,同时被@LoadBalanced修饰,所以只会注入@LoadBalanced修饰的RestTemplate,也就是我们的目标RestTemplate。

拦截器逻辑实现

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) { // for backwards compatibility 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(); Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri); return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution)); } }

拦截请求执行

@Override public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException { ILoadBalancer loadBalancer = getLoadBalancer(serviceId); //在这里负载均衡选择服务 Server server = getServer(loadBalancer); 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); }

我们重点看getServer方法,看看是如何选择服务的

protected Server getServer(ILoadBalancer loadBalancer) { if (loadBalancer == null) { return null; } // return loadBalancer.chooseServer("default"); // TODO: better handling of key }

代码配置随机loadBlancer,进入下面代码

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

这里配置的是RandomRule,所以进入RandomRule代码

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 = rand.nextInt(serverCount); server = upList.get(index); ​ if (server == null) { /* * The only time this should happen is if the server list were * somehow trimmed. This is a transient condition. Retry after * yielding. */ Thread.yield(); continue; } ​ if (server.isAlive()) { return (server); } ​ // Shouldn't actually happen.. but must be transient or a bug. server = null; Thread.yield(); } return server; }

随机负载规则很简单,随机整数选择服务,最终达到随机负载均衡。我们可以配置不同的Rule来实现不同的负载方式。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持自由互联。

标签: 详解 原理 操作

本文共计1983个文字,预计阅读时间需要8分钟。

这篇文章主要介绍了Spring Cloud Ribbon的使用方法和原理解析,通过示例代码展示了其非详细的使用方式,对于有志于学习或从事相关工作的人来说具有一定的参考价值,需要的伙伴可以参考学习。

Spring Cloud Ribbon如何操作与原理剖析详解?

这篇文章主要介绍了spring cloud Ribbon用法及原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

简介

Spring Cloud Ribbon如何操作与原理剖析详解?

这篇文章主要介绍一下ribbon在程序中的基本使用,在这里是单独拿出来写用例测试的,实际生产一般是配置feign一起使用,更加方便开发。同时这里也通过源码来简单分析一下ribbon的基本实现原理。

基本使用

这里使用基于zookeeper注册中心+ribbon的方式实现一个简单的客户端负载均衡案例。

服务提供方

首先是一个服务提供方。代码如下。

application.properties配置文件

spring.application.name=discovery-service
server.port=0
service-B.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
bootstrap.properties配置文件

spring.cloud.zookeeper.connect-string=192.168.0.15:2181
引导程序,提供了一个ribbonService的rest接口服务,注册程序到zookeeper中。

@SpringBootApplication @EnableDiscoveryClient @RestController public class DiscoverClient { public static void main(String[] args) { SpringApplication.run(DiscoverClient.class, args); } ​ @RequestMapping("/ribbonService") public String ribbonService(){ return "hello too ribbon"; } }

服务调用方

服务调用方就是进行负载均衡的一方,利用ribbo的RestTemplate进行负载调用服务。

RibbonConfig,配置ribbon的RestTemplate,通过@LoadBalanced注解实现,具体原理稍后分析。

@Configuration public class RibbonConfig { ​ /** * 实例化ribbon使用的RestTemplate * @return */ @Bean @LoadBalanced public RestTemplate rebbionRestTemplate(){ return new RestTemplate(); } /** * 配置随机负载策略,需要配置属性service-B.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule */ @Bean public IRule ribbonRule() { return new RandomRule(); } } ​

引导程序

@SpringBootApplication(scanBasePackages = "garine.learn.ribbon.loadblance") @EnableDiscoveryClient @RestController public class TestRibbonApplocation { public static void main(String[] args) { SpringApplication.run(TestRibbonApplocation.class, args); } ​ @Autowired @LoadBalanced RestTemplate restTemplate; ​ @GetMapping("/{applicationName}/ribbonService") public String ribbonService(@PathVariable("applicationName") String applicationName){ return restTemplate.getForObject("" + applicationName+"/ribbonService", String.class); } }

配置文件同上,服务名称修改即可。

测试

启动两个discovery-service,由于端口设置为0,所以是随机端口。

启动服务调用方

浏览器访问服务调用方的提供的接口,路径参数需要加上调用的服务名称,例如localhost:8080/discovery-service/ribbonService,然后服务调用方使用ribbon的RestTemplate调用服务提供方的接口。

结果返回:hello too ribbon ,同时服务提供方启动的两个服务都可能被调用,取决于怎么配置负载策略。

上面就是一个简单使用ribbon的例子,结合feign使用基本上是做类似上面所写的工作,那么ribbon到底是怎么实现的呢?

原理与源码分析

ribbon实现的关键点是为ribbon定制的RestTemplate,ribbon利用了RestTemplate的拦截器机制,在拦截器中实现ribbon的负载均衡。负载均衡的基本实现就是利用applicationName从服务注册中心获取可用的服务地址列表,然后通过一定算法负载,决定使用哪一个服务地址来进行http调用。

Ribbon的RestTemplate

RestTemplate中有一个属性是List<ClientHttpRequestInterceptor> interceptors,如果interceptors里面的拦截器数据不为空,在RestTemplate进行http请求时,这个请求就会被拦截器拦截进行,拦截器实现接口ClientHttpRequestInterceptor,需要实现方法是

ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException;

也就是说拦截器需要完成http请求,并封装一个标准的response返回。

ribbon中的拦截器

在Ribbon 中也定义了这样的一个拦截器,并且注入到RestTemplate中,是怎么实现的呢?

在Ribbon实现中,定义了一个LoadBalancerInterceptor,具体的逻辑先不说,ribbon就是通过这个拦截器进行拦截请求,然后实现负载均衡调用。

拦截器定义在org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration.LoadBalancerInterceptorConfig#ribbonInterceptor

@Configuration @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate") static class LoadBalancerInterceptorConfig { @Bean //定义ribbon的拦截器 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); }; } }

ribbon中的拦截器注入到RestTemplate

定义了拦截器,自然需要把拦截器注入到、RestTemplate才能生效,那么ribbon中是如何实现的?上面说了拦截器的定义与拦截器注入器的定义,那么肯定会有个地方使用注入器来注入拦截器的。

在org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration#loadBalancedRestTemplateInitializerDeprecated方法里面,进行注入,代码如下。

@Configuration @ConditionalOnClass(RestTemplate.class) @ConditionalOnBean(LoadBalancerClient.class) @EnableConfigurationProperties(LoadBalancerRetryProperties.class) public class LoadBalancerAutoConfiguration { ​ @LoadBalanced @Autowired(required = false) private List<RestTemplate> restTemplates = Collections.emptyList(); ​ @Bean public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated( final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) { //遍历context中的注入器,调用注入方法。 return () -> restTemplateCustomizers.ifAvailable(customizers -> { for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) { for (RestTemplateCustomizer customizer : customizers) { customizer.customize(restTemplate); } } }); } //...... }

遍历context中的注入器,调用注入方法,为目标RestTemplate注入拦截器,注入器和拦截器都是我们定义好的。

还有关键的一点是:需要注入拦截器的目标restTemplates到底是哪一些?因为RestTemplate实例在context中可能存在多个,不可能所有的都注入拦截器,这里就是@LoadBalanced注解发挥作用的时候了。

LoadBalanced注解

严格上来说,这个注解是spring cloud实现的,不是ribbon中的,它的作用是在依赖注入时,只注入实例化时被@LoadBalanced修饰的实例。

例如我们定义Ribbon的RestTemplate的时候是这样的

@Bean @LoadBalanced public RestTemplate rebbionRestTemplate(){ return new RestTemplate(); }

因此才能为我们定义的RestTemplate注入拦截器。

那么@LoadBalanced是如何实现这个功能的呢?其实都是spring的原生操作,@LoadBalance的源码如下

/** * Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient * @author Spencer Gibb */ @Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Qualifier public @interface LoadBalanced { }

很明显,‘继承'了注解@Qualifier,我们都知道以前在xml定义bean的时候,就是用Qualifier来指定想要依赖某些特征的实例,这里的注解就是类似的实现,restTemplates通过@Autowired注入,同时被@LoadBalanced修饰,所以只会注入@LoadBalanced修饰的RestTemplate,也就是我们的目标RestTemplate。

拦截器逻辑实现

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) { // for backwards compatibility 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(); Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri); return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution)); } }

拦截请求执行

@Override public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException { ILoadBalancer loadBalancer = getLoadBalancer(serviceId); //在这里负载均衡选择服务 Server server = getServer(loadBalancer); 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); }

我们重点看getServer方法,看看是如何选择服务的

protected Server getServer(ILoadBalancer loadBalancer) { if (loadBalancer == null) { return null; } // return loadBalancer.chooseServer("default"); // TODO: better handling of key }

代码配置随机loadBlancer,进入下面代码

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

这里配置的是RandomRule,所以进入RandomRule代码

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 = rand.nextInt(serverCount); server = upList.get(index); ​ if (server == null) { /* * The only time this should happen is if the server list were * somehow trimmed. This is a transient condition. Retry after * yielding. */ Thread.yield(); continue; } ​ if (server.isAlive()) { return (server); } ​ // Shouldn't actually happen.. but must be transient or a bug. server = null; Thread.yield(); } return server; }

随机负载规则很简单,随机整数选择服务,最终达到随机负载均衡。我们可以配置不同的Rule来实现不同的负载方式。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持自由互联。

标签: 详解 原理 操作