spring cloud Ribbon用法及原理解析

论坛 期权论坛 脚本     
niminba   2021-5-22 14:49   51   0

这篇文章主要介绍了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就是通过这个拦截器进行拦截请求,然后实现负载均衡调用。

拦截器定义ute(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来实现不同的负载方式。

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

分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:1060120
帖子:212021
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP