前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring Cloud系列二:Ribbon

Spring Cloud系列二:Ribbon

作者头像
心平气和
发布2021-02-26 15:13:35
3560
发布2021-02-26 15:13:35
举报
文章被收录于专栏:程序员升级之路

一、Ribbon介绍

Ribbon是Netfix开源的一款客户端负载均衡工具,GitHub地址:

https://github.com/Netflix/ribbon

概括2点:

1、主要作用是实现负载均衡

2、它实现在客户端

在微服务环境下每个服务实例少则几个,多则上百个,如何让请求均匀分布到各服务实例上是微服务架构下必须解决的一个问题,这方面有2种解决方案:

一种是在服务端即服务提供方实现,硬件如F5,软件如Nginx都是此类成熟的方案,不过这种方案也有弊端,增加运维复杂性及可能引入新的单点,因为每次请求都要经过负载均衡器;

另一方面是在客户端即调用方实现,Ribbon就是这类方案的代表,即调用方根据一定规则选择一个合适的服务提供方进行调用。

二、Ribbon使用

1、直接使用

1)、引入依赖

代码语言:javascript
复制
<dependency>
      <groupId>com.netfix.ribbon</groupId>
      <artifactId>ribbon</artifactId>
      <version>2.2.5</version>
    </dependency>

2、编写测试代码

代码语言:javascript
复制
public class RibbonTest {

    /**
     *
     * @param args
     */
    public static void main(String[] args){
        ribbon();
    }

    /**
     *
     */
    protected static void ribbon(){
        List<Server> serverList = new ArrayList<>();
        serverList.add(new Server("172.21.107.76", 80));
        serverList.add(new Server("172.21.107.77", 80));

        ILoadBalancer loadBalancer = LoadBalancerBuilder.newBuilder().buildFixedServerListLoadBalancer(serverList);


        for(int i=0; i<10; i++) {
            String result = LoadBalancerCommand.<String>builder().withLoadBalancer(loadBalancer).build().submit(
                    new ServerOperation<String>() {
                        @Override
                        public Observable<String> call(Server server) {
                            String url = "http://" + server.getHost() + ":" + server.getPort() + "/test";
                            System.out.println("调用地址:" + url);

                            try {
                                HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
                                connection.setRequestMethod("GET");
                                connection.connect();

                                InputStream in = connection.getInputStream();
                                byte[] response = new byte[in.available()];
                                in.read();

                                return Observable.just(new String(response));
                            } catch (Exception ex) {
                                return Observable.just(ex.getMessage());
                            }
                        }
                    }
            ).toBlocking().first();

            System.out.println("response:" + result);
        }
    }
}

这里通过构造一个固定的Server列表,然后通过一个循环来发送Http请求,通过打印每次请求的地址就知道是否负载均衡了。

2、整合Spring Cloud使用

2.1 RestTemplate使用

在Spring Cloud中发送Http协议请求官方推荐使用RestTemplate,RestTemplate使用比较简单,如getForObject方法:

代码语言:javascript
复制
@RestController
public class TestController {
  @Autowired
  private RestTemplate restTemplate;  

@GetMapping("/test")     
  public String getData(@RequestParam("name") String name) {         
     return restTemplate.getForObject(
    "http://localhost:9000/test/{name}", String.class, name);     
  }   
       

getForObject参数如下:

1:请求Url

2:返回结果的数据类型

3:参数列表(参数变长)

如果是POST请求则调用postForObject方法。

2.2 RestTemplate结合Ribbon

在RestTemplate上启用负载均衡只要加一个@LoadBalanced的注解就可以了:

代码语言:javascript
复制
Configuration
public class BeanConfig {
  @Bean
  @LoadBalanced
  public RestTemplate getRestTemplate() {
    return new RestTemplate();
  }

}

还是用上面的Controller测试下,应该就可以看到效果了。

三、Ribbon原理分析

spring-cloud-common在启动的时候LoadBalancerAutoConfiguration会对RestTemplate进行拦截:

代码语言:javascript
复制
public class LoadBalancerAutoConfiguration {

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

  @Bean
  public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
      final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
    return () -> restTemplateCustomizers.ifAvailable(customizers -> {
            for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
                for (RestTemplateCustomizer customizer : customizers) {
                    customizer.customize(restTemplate);
                }
            }
        });
  }
}

这个customizer会把LoadBalancerInterceptor加进去:

代码语言:javascript
复制
public RestTemplateCustomizer restTemplateCustomizer(
        final LoadBalancerInterceptor loadBalancerInterceptor) {
      return restTemplate -> {
                List<ClientHttpRequestInterceptor> list = new ArrayList<>(
                        restTemplate.getInterceptors());
                list.add(loadBalancerInterceptor);
                restTemplate.setInterceptors(list);
            };
    }

LoadBalancerInterceptor调用loadBalancer进行处理:

代码语言:javascript
复制
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));
  }

loadBalancer的类型为LoadBalancerClient,execute的核心逻辑是getServer:

代码语言:javascript
复制
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);
  }

它又交给ILoadBalancer处理:

代码语言:javascript
复制
  protected Server getServer(ILoadBalancer loadBalancer) {
    if (loadBalancer == null) {
      return null;
    }
    return loadBalancer.chooseServer("default"); // TODO: better handling of key
  }

这里有不同的实现:BaseLoadBalancer和ZoneAwareLoadBalancer

我们看下BaseLoadBalancer:

代码语言:javascript
复制
 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;
            }
        }
    }

它交给rule处理了,rule的类型是IRule:

代码语言:javascript
复制
public interface IRule{
    /*
     * choose one alive server from lb.allServers or
     * lb.upServers according to key
     * 
     * @return choosen Server object. NULL is returned if none
     *  server is available 
     */

    public Server choose(Object key);
    
    public void setLoadBalancer(ILoadBalancer lb);
    
    public ILoadBalancer getLoadBalancer();    
}

它的核心函数就是choose,即根据域名选择服务器,实现有:

看下随机的实现RandomRule:

代码语言:javascript
复制
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;

    }

先获取所有可达服务器(现在能连通的),再获取所有服务器(不管是否可用),然后生成一个随机数,再用这个随机数作为索引拿到相应服务器看是否可用,如果可用直接返回,反之退出时间分片后继续循环。

四、总结

1、spring-cloud-common包中的LoadBalancerAutoConfiguration在框架启动时会对所有RestTemplate进行拦截,所有请求转发给LoadBalancerInterceptor处理;

2、LoadBalancerInterceptor拦截请求调用,并转发给LoadBalancerClient处理;

3、LoadBalancerClient转发给ILoadBalancer选择服务器;

4、ILoadBalancer的实现主要有BaseLoadBalancer和ZoneAwareLoadBalancer,BaseLoadBalancer再调用IRule选择服务器;

5、IRule实现具体选择服务器的逻辑,如果我们要实现自己的负载均衡策略就可以实现IRule就可以了。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-02-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员升级之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
负载均衡
负载均衡(Cloud Load Balancer,CLB)提供安全快捷的四七层流量分发服务,访问流量经由 CLB 可以自动分配到多台后端服务器上,扩展系统的服务能力并消除单点故障。轻松应对大流量访问场景。 网关负载均衡(Gateway Load Balancer,GWLB)是运行在网络层的负载均衡。通过 GWLB 可以帮助客户部署、扩展和管理第三方虚拟设备,操作简单,安全性强。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档