zoukankan      html  css  js  c++  java
  • SpringCloud系列之客户端负载均衡Netflix Ribbon

    1. 什么是负载均衡?

    负载均衡是一种基础的网络服务,它的核心原理是按照指定的负载均衡算法,将请求分配到后端服务集群上,从而为系统提供并行处理和高可用的能力。提到负载均衡,你可能想到nginx。对于负载均衡,一般分为服务端负载均衡和客户端负载均衡

    • 服务端负载均衡:在消费者和服务提供方中间使用独立的代理方式进行负载,有硬件的负载均衡器,比如 F5,也有软件,比如 Nginx。
      在这里插入图片描述

    • 客户端负载均衡:所谓客户端负载均衡,就是客户端根据自己的请求情况做负载,本文介绍的Netflix Ribbon就是客户端负载均衡的组件
      在这里插入图片描述

    2. 什么是Netflix Ribbon?

    上一章的学习中,我们知道了微服务的基本概念,知道怎么基于Ribbon+restTemplate的方式实现服务调用,接着上篇博客,我们再比较详细学习客户端负载均衡Netflix Ribbon,学习本博客之前请先学习上篇博客,然后再学习本篇博客

    Ribbon 是由 Netflix 发布的负载均衡器,它有助于控制 HTTP 和 TCP 的客户端的行为。Ribbon 属于客户端负载均衡。

    3. Netflix Ribbon实验环境准备

    环境准备:

    • JDK 1.8
    • SpringBoot2.2.3
    • SpringCloud(Hoxton.SR6)
    • Maven 3.2+
    • 开发工具
      • IntelliJ IDEA
      • smartGit

    创建一个SpringBoot Initialize项目,详情可以参考我之前博客:SpringBoot系列之快速创建项目教程

    可以引入Eureka Discovery Client,也可以单独添加Ribbon
    在这里插入图片描述

    Spring Cloud Hoxton.SR6版本不需要引入spring-cloud-starter-netflix-ribbon,已经默认集成
    在这里插入图片描述
    也可以单独添加Ribbon依赖:
    在这里插入图片描述
    本博客的是基于spring-cloud-starter-netflix-eureka-client进行试验,试验前要运行eureka服务端,eureka服务提供者,代码请参考上一章博客

    补充:IDEA中多实例运行方法

    step1:如图,不要加上勾选
    在这里插入图片描述

    step2:指定不同的server端口和实例id,如图:
    在这里插入图片描述
    启动成功后,是可以看到多个实例的
    在这里插入图片描述

    4. Netflix Ribbon API使用

    使用LoadBalancerClient

     @Autowired
        LoadBalancerClient loadBalancerClient;
    
        @Test
        void contextLoads() {
            ServiceInstance serviceInstance = loadBalancerClient.choose("EUREKA-SERVICE-PROVIDER");
            URI uri = URI.create(String.format("http://%s:%s", serviceInstance.getHost() , serviceInstance.getPort()));
            System.out.println(uri.toString());
        }
    

    构建BaseLoadBalancer 实例例子:

     @Test
        void testLoadBalancer(){
            // 服务列表
            List<Server> serverList = Arrays.asList(new Server("localhost", 8083), new Server("localhost", 8084));
            // 构建负载实例
            BaseLoadBalancer loadBalancer = LoadBalancerBuilder.newBuilder().buildFixedServerListLoadBalancer(serverList);
            loadBalancer.setRule(new RandomRule());
            for (int i = 0; i < 5; i++) {
                String result = LoadBalancerCommand.<String>builder().withLoadBalancer(loadBalancer).build()
                        .submit(new ServerOperation<String>() {
                            public Observable<String> call(Server server) {
                                try {
                                    String address = "http://" + server.getHost() + ":" + server.getPort()+"/EUREKA-SERVICE-PROVIDER/api/users/mojombo";
                                    System.out.println("调用地址:" + address);
                                    return Observable.just("");
                                } catch (Exception e) {
                                    return Observable.error(e);
                                }
                            }
                        }).toBlocking().first();
                System.out.println("result:" + result);
            }
        }
    

    5. 负载均衡@LoadBalanced

    Ribbon负载均衡实现,RestTemplate 要加上@LoadBalanced

    package com.example.springcloud.ribbon.configuration;
    
    import org.springframework.cloud.client.loadbalancer.LoadBalanced;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.client.RestTemplate;
    
    /**
     * <pre>
     *  RestConfiguration
     * </pre>
     *
     * <pre>
     * @author mazq
     * 修改记录
     *    修改后版本:     修改人:  修改日期: 2020/07/31 09:43  修改内容:
     * </pre>
     */
    @Configuration
    public class RestConfiguration {
        @Bean
        @LoadBalanced
        public RestTemplate restTemplate() {
            return new RestTemplate();
        }
    }
    
    

    yaml配置:

    server:
      port: 8082
    spring:
      application:
        name: eureka-service-consumer
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:8761/eureka/
        fetch-registry: true
        register-with-eureka: false
        healthcheck:
          enabled: false
      instance:
        status-page-url-path: http://localhost:8761/actuator/info
        health-check-url-path: http://localhost:8761/actuator//health
        prefer-ip-address: true
        instance-id: eureka-service-consumer8082
    
    

    在这里插入图片描述
    关键点,使用SpringCloud的@LoadBalanced,才能调http://EUREKA-SERVICE-PROVIDER/api/users/? 接口的数据,浏览器是不能直接调的

    
    import com.example.springcloud.ribbon.bean.User;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
    import org.springframework.web.bind.annotation.*;
    import org.springframework.web.client.RestTemplate;
    
    import java.net.URI;
    
    @SpringBootApplication
    @EnableEurekaClient
    @RestController
    @Slf4j
    public class SpringcloudRibbonApplication {
    
    
        @Autowired
        RestTemplate restTemplate;
    
        public static void main(String[] args) {
            SpringApplication.run(SpringcloudRibbonApplication.class, args);
        }
    
        @GetMapping("/findUser/{username}")
        public User index(@PathVariable("username")String username){
            return restTemplate.getForObject("http://EUREKA-SERVICE-PROVIDER/api/users/"+username,User.class);
        }
    
    }
    

    在这里插入图片描述

    6. 定制Netflix Ribbon client

    具体怎么定制?可以参考官网,@RibbonClient指定定制的配置类既可
    在这里插入图片描述

    package com.example.springcloud.ribbon.configuration;
    
    import com.example.springcloud.ribbon.component.MyRule;
    import com.netflix.loadbalancer.IPing;
    import com.netflix.loadbalancer.IRule;
    import com.netflix.loadbalancer.PingUrl;
    import org.springframework.cloud.netflix.ribbon.ZonePreferenceServerListFilter;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    /**
     * <pre>
     *   Ribbon Clients configuration
     * </pre>
     *
     * <pre>
     * @author mazq
     * 修改记录
     *    修改后版本:     修改人:  修改日期: 2020/07/29 14:22  修改内容:
     * </pre>
     */
    //@Configuration(proxyBeanMethods = false)
    //@IgnoreComponentScan
    public class RibbonClientConfiguration {
    
    //    @Autowired
    //    IClientConfig config;
    
        @Bean
        public IRule roundRobinRule() {
            return new MyRule();
        }
    
        @Bean
        public ZonePreferenceServerListFilter serverListFilter() {
            ZonePreferenceServerListFilter filter = new ZonePreferenceServerListFilter();
            filter.setZone("myTestZone");
            return filter;
        }
    
        @Bean
        public IPing ribbonPing() {
            return new PingUrl();
        }
    
    }
    
    

    在Application类加上@RibbonClient,name是为服务名称,跟bootstrap.yml配置的一样既可

    @RibbonClient(name = "eureka-service-provider",configuration = RibbonClientConfiguration.class)
    

    特别注意:官网这里特意提醒,这里的意思是说@RibbonClient指定的配置类必须加@Configuration(不过在Hoxton.SR6版本经过我的验证,其实是可以不加的,加了反而可能报错),@ComponentScan扫描要排除自定义的配置类,否则,它由所有@RibbonClients共享。如果你使用@ComponentScan(或@SpringBootApplication)
    在这里插入图片描述
    其实就是想让我们排除这个配置的全局扫描,所以我们可以进行编码,写个注解类@IgnoreComponentScan ,作用于类,指定@Target(ElementType.TYPE)

    package com.example.springcloud.ribbon.configuration;
    
    import java.lang.annotation.*;
    
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface IgnoreComponentScan {
    
    }
    
    

    加上自定义的注解类
    在这里插入图片描述

    任何在Application加上代码,避免全局扫描:

    @ComponentScan(excludeFilters={@ComponentScan.Filter(type= FilterType.ANNOTATION,value= IgnoreComponentScan.class)})
    
    

    7. Netflix Ribbon常用组件

    ps:介绍Netflix Ribbon的负载策略之前,先介绍Netflix Ribbon常用组件及其作用:

    组件 作用
    ILoadBalancer 定义一系列的操作接口,比如选择服务实例。
    IRule 负载算法策略,内置算法策略来为服务实例的选择提供服务。
    ServerList 负责服务实例信息的获取(可以获取配置文件中的,也可以从注册中心获取。)
    ServerListFilter 过滤掉某些不想要的服务实例信息。
    ServerListUpdater 更新本地缓存的服务实例信息。
    IPing 对已有的服务实例进行可用性检查,保证选择的服务都是可用的。

    8. 定制Netflix Ribbon策略

    因为服务提供者是多实例的,所以再写个接口测试,调用了哪个实例,来看看Netflix Ribbon的负载策略

    @Autowired
        LoadBalancerClient loadBalancerClient;
    
        @GetMapping(value = {"/test"})
        public String test(){
            ServiceInstance serviceInstance = loadBalancerClient.choose("EUREKA-SERVICE-PROVIDER");
            URI uri = URI.create(String.format("http://%s:%s", serviceInstance.getHost() , serviceInstance.getPort()));
            System.out.println(uri.toString());
            return uri.toString();
        }
    

    部署成功,多次调用,可以看到每次调用的服务实例都不一样?其实Netflix Ribbon默认是按照轮询的方式调用的
    在这里插入图片描述

    要定制Netflix Ribbon的负载均衡策略,需要实现AbstractLoadBalancerRule抽象类,下面给出类图:
    在这里插入图片描述
    Netflix Ribbon内置了如下的负载均衡策略,引用https://juejin.im/post/6854573215587500045的归纳:
    在这里插入图片描述

    ok,接着我们可以在配置类,修改规则

    @Bean
        public IRule roundRobinRule() {
            return new BestAvailableRule();
        }
    

    测试,基本都是调8083这个实例,因为这个实例性能比较好
    在这里插入图片描述
    显然,也可以自己写个策略类,代码参考com.netflix.loadbalancer.RandomRule,网上也有很多例子,思路是修改RandomRule原来的策略,之前随机调服务实例一次,现在改成每调5次后,再调其它的服务实例

    package com.example.springcloud.ribbon.component;
     
    import com.netflix.client.config.IClientConfig;
    import com.netflix.loadbalancer.AbstractLoadBalancerRule;
    import com.netflix.loadbalancer.ILoadBalancer;
    import com.netflix.loadbalancer.Server;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.ThreadLocalRandom;
    
    
    public class MyRule extends AbstractLoadBalancerRule
    {
        // 总共被调用的次数,目前要求每台被调用5次
        private int total = 0;
        // 当前提供服务的机器号
        private int index = 0;
    
        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) {
                   // 没有获取到服务
                    return null;
                }
    
                //int index = chooseRandomInt(serverCount);
                //server = upList.get(index);
                if(total < 5)
                {
                    server = upList.get(index);
                    total++;
                }else {
                    total = 0;
                    index++;
                    if(index >= upList.size())
                    {
                        index = 0;
                    }
                }
    
                if (server == null) {
                    // 释放线程
                    Thread.yield();
                    continue;
                }
    
                if (server.isAlive()) {
                    return (server);
                }
    
                server = null;
                Thread.yield();
            }
    
            return server;
        }
    
        protected int chooseRandomInt(int serverCount) {
            return ThreadLocalRandom.current().nextInt(serverCount);
        }
    
        @Override
        public Server choose(Object key) {
            return choose(getLoadBalancer(), key);
        }
    
        @Override
        public void initWithNiwsConfig(IClientConfig clientConfig) {
        }
    }
    
    

    修改IRule ,返回MyRule

     @Bean
        public IRule roundRobinRule() {
            return new MyRule();
        }
    

    附录:

    ok,本博客参考官方教程进行实践,仅仅作为入门的学习参考资料,详情可以参考Spring Cloud官方文档https://docs.spring.io/spring-cloud-netflix/docs/2.2.x-SNAPSHOT/reference/html/#customizing-the-ribbon-client

    代码例子下载:code download

    优质学习资料参考:

  • 相关阅读:
    JMETER接口测试问题三之Host of origin may not be blank
    JMETER接口测试问题解决二之后续接口请求依赖登录接口的操作
    JMETER接口测试问题一之请求超时报错
    jmeter接口测试之json提取器的使用方法二
    JMETER接口测试之Debug sample
    JMTER接口测试之JSON提取器
    EXCEL批量导入到Sqlserver数据库并进行两表间数据的批量修改
    Linq的整型或实体类null引发的报错问题
    SqlServer 统计1-12月份 每个月的数据(临时表)
    select2的多选下拉框上传
  • 原文地址:https://www.cnblogs.com/mzq123/p/13411111.html
Copyright © 2011-2022 走看看