spring cloud Ribbon用法及原理解析

作者:天宇轩-王 时间:2021-11-28 15:27:21 

这篇文章主要介绍了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("http://" + applicationName+"/ribbonService", String.class);
 }
}

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

测试

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

启动服务调用方

浏览器访问服务调用方的提供的接口,路径参数需要加上调用的服务名称,例如http://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来实现不同的负载方式。

来源:https://www.cnblogs.com/dalianpai/p/11690322.html

标签:spring,cloud,ribbon
0
投稿

猜你喜欢

  • VS2019配置OpenCV时找不到Microsoft.Cpp.x64.user的解决方法

    2023-07-01 01:57:36
  • Maven属性与版本管理详细步骤分解

    2023-11-15 14:38:45
  • Java 格式化输出JSON字符串的2种实现操作

    2023-11-13 09:41:10
  • Spring Security OAuth2 实现登录互踢的示例代码

    2023-09-04 19:09:28
  • 关于通过java调用datax,返回任务执行的方法

    2023-11-28 21:26:45
  • Java中synchronized的几种使用方法

    2023-06-19 16:33:28
  • 手把手带你了解Java-Stream流方法学习及总结

    2023-11-25 19:30:15
  • 带你入门Java的泛型

    2023-06-08 02:48:24
  • Java入门绊脚石之Override和Overload的区别详解

    2022-05-04 15:24:51
  • 利用java反射机制实现自动调用类的简单方法

    2023-11-29 15:57:22
  • java 动态生成SQL的实例讲解

    2021-05-25 18:26:06
  • 教你用Java在个人电脑上实现微信扫码支付

    2023-07-22 20:52:15
  • 详解Java动态字节码技术

    2022-06-20 03:20:20
  • springboot 文件上传大小配置的方法

    2023-08-02 11:49:16
  • springboot+gradle 构建多模块项目的步骤

    2023-02-19 00:33:31
  • IntelliJ IDEA2019实现Web项目创建示例

    2023-06-05 00:29:33
  • 基于java 线程的几种状态(详解)

    2022-08-31 19:51:47
  • 用java开发dota英雄最华丽的技能(实例讲解)

    2022-05-27 00:10:20
  • 详解@ConfigurationProperties实现原理与实战

    2023-11-24 05:19:26
  • 详解Java中自定义注解的使用

    2023-11-27 07:36:30
  • asp之家 软件编程 m.aspxhome.com