美文网首页
聊聊如何独立使用ribbon实现业务客户端负载均衡

聊聊如何独立使用ribbon实现业务客户端负载均衡

作者: linyb极客之路 | 来源:发表于2023-06-19 09:59 被阅读0次

    前言

    ribbon是Netflix开源的客户端负载均衡工具,ribbon实现一系列的负载均衡算法,通过这些负载均衡算法去查找相应的服务。ribbon被大家所熟知,可能是来源于spring cloud,今天就来聊聊如何单独使用ribbon来实现业务客户端负载均衡

    实现关键

    springcloud ribbon获取服务列表是通过注册中心,而单独使用ribbon,因为没有注册中心加持,就得单独配置服务列表

    示例

    1、在业务项目中pom引入ribbon GAV

    <dependency>
        <groupId>com.netflix.ribbon</groupId>
        <artifactId>ribbon</artifactId>
        <version>2.2.2</version>
    </dependency>
    

    不过引进去,发现如果引入netfiix的相关的类,比如IPing,会发现引不进去,原因是因为这个GAV里面依赖的jar的生命周期是runtime,即在运行期或者测试阶段才生效,在编译阶段是不生效的。如果我们为了方便,可以直接单独引入
    spring cloud ribbon

        <dependency>
               <groupId>org.springframework.cloud</groupId>-->
                <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>-->
               <version>2.2.2.RELEASE</version>
        </dependency>
    

    本文我们是想脱离springcloud,直接使用ribbon,因此我们可以直接 引入如下GAV

       <!--  核心的通用性代码-->
            <dependency>
                <groupId>com.netflix.ribbon</groupId>
                <artifactId>ribbon-core</artifactId>
                <version>${ribbon.version}</version>
            </dependency>
            <!-- 基于apache httpClient封装的rest客户端,集成了负载均衡模块,内嵌http心跳检测-->
            <dependency>
                <groupId>com.netflix.ribbon</groupId>
                <artifactId>ribbon-httpclient</artifactId>
                <version>${ribbon.version}</version>
            </dependency>
            <!-- 负载均衡模块-->
            <dependency>
                <groupId>com.netflix.ribbon</groupId>
                <artifactId>ribbon-loadbalancer</artifactId>
                <version>${ribbon.version}</version>
            </dependency>
    
            <!-- IClientConfig配置相关-->
            <dependency>
                <groupId>com.netflix.archaius</groupId>
                <artifactId>archaius-core</artifactId>
                <version>0.7.6</version>
            </dependency>
    
            <!-- IClientConfig配置相关-->
            <dependency>
                <groupId>commons-configuration</groupId>
                <artifactId>commons-configuration</artifactId>
                <version>1.8</version>
            </dependency>
    

    2、创建ribbon元数据配置类

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @Builder
    public class RuleDefinition {
    
        /**
         * 服务名称
         */
        private String serviceName;
    
        /**
         * 命名空间,当服务名相同时,可以通过namesapce来进行隔离区分
         * 未指定默认为public
         */
        @Builder.Default
        private String namespace = DEFAULT_NAMESPACE;
    
        /**
         * 自定义负载均衡策略,未指定默认为轮询
         */
        @Builder.Default
        private String loadBalancerRuleClassName = RoundRobin;
    
        /**
         * 自定义心跳检测,未指定不检测
         */
        @Builder.Default
        private String loadBalancerPingClassName = DummyPing;
    
        /**
         * 服务列表,多个用英文逗号隔开
         */
        private String listOfServers;
    
    
        /**
         * 该优先级大于loadBalancerPingClassName
         */
        private IPing ping;
    
        /**
         * 心跳间隔,不配置默认是10秒,单位秒
         */
        private int pingIntervalSeconds;
    
    
        /**
         * 该优先级大于loadBalancerRuleClassName
         */
        private IRule rule;
    
    
    
    }
    
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @ConfigurationProperties(prefix = PREFIX)
    public class LoadBalanceProperty {
    
        public static final String PREFIX = "lybgeek.loadbalance";
    
        private List<RuleDefinition> rules;
    
        public Map<String,RuleDefinition> getRuleMap(){
            if(CollectionUtils.isEmpty(rules)){
                return Collections.emptyMap();
            }
    
            Map<String,RuleDefinition> ruleDefinitionMap = new LinkedHashMap<>();
            for (RuleDefinition rule : rules) {
                String key = rule.getServiceName() + RULE_JOIN + rule.getNamespace();
                ruleDefinitionMap.put(key,rule);
            }
    
            return Collections.unmodifiableMap(ruleDefinitionMap);
        }
    }
    

    3、创建负载均衡工厂【核心实现】

     private final LoadBalanceProperty loadBalanceProperty;
    
        // key:serviceName + nameSpace
        private static final Map<String, ILoadBalancer> loadBalancerMap = new ConcurrentHashMap<>();
    
        public ILoadBalancer getLoadBalancer(String serviceName,String namespace){
            String key = serviceName + RULE_JOIN + namespace;
            if(loadBalancerMap.containsKey(key)){
                return loadBalancerMap.get(key);
            }
            RuleDefinition ruleDefinition = getAvailableRuleDefinition(serviceName,namespace);
            IPing ping = ruleDefinition.getPing();
            if(ObjectUtils.isEmpty(ping)){
                // 无法通过ConfigurationManager.getConfigInstance().setProperty(serviceName + DOT + namespace + DOT + PING_CLASS_NAME, ruleDefinition.getLoadBalancerPingClassName());
                //LoadBalancerBuilder没提供通过ClientConfig配置ping方法,只能通过withPing修改
                ping = getPing(serviceName,namespace);
            }
    
            IRule rule = ruleDefinition.getRule();
            if(ObjectUtils.isEmpty(rule)){
                // 也可以通过ConfigurationManager.getConfigInstance().setProperty(serviceName + DOT + namespace + DOT + RULE_CLASS_NAME, ruleDefinition.getLoadBalancerRuleClassName());
                rule = getRule(serviceName,namespace);
            }
    
    
            // 配置服务列表
            ConfigurationManager.getConfigInstance().setProperty(serviceName + DOT + namespace + DOT + SERVER_LIST, ruleDefinition.getListOfServers());
            // 因为服务列表目前是配置写死,因此关闭列表更新,否则当触发定时更新时,会重新将服务列表状态恢复原样,这样会导致server的isLive状态不准确
            // 不设置默认采用com.netflix.loadbalancer.PollingServerListUpdater
            ConfigurationManager.getConfigInstance().setProperty(serviceName + DOT + namespace + DOT + SERVERLIST_UPDATER_CLASS_NAME, EmptyServerListUpdater.class.getName());
            IClientConfig config = new DefaultClientConfigImpl(namespace);
            config.loadProperties(serviceName);
            ZoneAwareLoadBalancer<Server> loadBalancer = getLoadBalancer(config, ping, rule);
            loadBalancerMap.put(key,loadBalancer);
            if(ruleDefinition.getPingIntervalSeconds() > 0){
                // 默认每隔10秒进行心跳检测
                loadBalancer.setPingInterval(ruleDefinition.getPingIntervalSeconds());
            }
    
            return loadBalancer;
    
    
        }
    
    
    
        public ZoneAwareLoadBalancer<Server> getLoadBalancer(IClientConfig config, IPing ping, IRule rule){
            ZoneAwareLoadBalancer<Server> serverZoneAwareLoadBalancer = LoadBalancerBuilder.newBuilder()
                    .withClientConfig(config)
                    .withPing(ping)
                    .withRule(rule)
                    .buildDynamicServerListLoadBalancerWithUpdater();
            return serverZoneAwareLoadBalancer;
        }
    
    
    
    
    
    
        /**
         * 获取 iping
         * @param serviceName
         * @param namespace
         * @return
         */
        @SneakyThrows
        public IPing getPing(String serviceName, String namespace){
            RuleDefinition ruleDefinition = getAvailableRuleDefinition(serviceName,namespace);
            Class<?> loadBalancerPingClass = ClassUtils.forName(ruleDefinition.getLoadBalancerPingClassName(), Thread.currentThread().getContextClassLoader());
            Assert.isTrue(IPing.class.isAssignableFrom(loadBalancerPingClass),String.format("loadBalancerPingClassName : [%s] is not Iping class type",ruleDefinition.getLoadBalancerPingClassName()));
            return (IPing) BeanUtils.instantiateClass(loadBalancerPingClass);
    
    
        }
    
        /**
         * 获取 loadbalanceRule
         * @param serviceName
         * @param namespace
         * @return
         */
        @SneakyThrows
        public IRule getRule(String serviceName, String namespace){
            RuleDefinition ruleDefinition = getAvailableRuleDefinition(serviceName,namespace);
            Class<?> loadBalancerRuleClass = ClassUtils.forName(ruleDefinition.getLoadBalancerRuleClassName(), Thread.currentThread().getContextClassLoader());
            Assert.isTrue(IRule.class.isAssignableFrom(loadBalancerRuleClass),String.format("loadBalancerRuleClassName : [%s] is not Irule class type",ruleDefinition.getLoadBalancerRuleClassName()));
            return (IRule) BeanUtils.instantiateClass(loadBalancerRuleClass);
    
        }
    
        private RuleDefinition getAvailableRuleDefinition(String serviceName,String namespace){
            Map<String, RuleDefinition> ruleMap = loadBalanceProperty.getRuleMap();
            Assert.notEmpty(ruleMap,"ruleDefinition is empty");
            String key = serviceName + RULE_JOIN + namespace;
            RuleDefinition ruleDefinition = ruleMap.get(key);
            Assert.notNull(ruleDefinition,String.format("NOT FOUND AvailableRuleDefinition with serviceName : [{}] in namespace:[{}]",serviceName,namespace));
            return ruleDefinition;
        }
    
    
    

    核心实现类:com.netflix.loadbalancer.LoadBalancerBuilder 利用该类创建相应的负载均衡

    4、测试

    a、 新起2个服务提供者占用6666端口、和6667端口

    b、 在消费端的application.yml配置如下内容

    lybgeek:
      loadbalance:
        rules:
          - serviceName: provider
            namespace: test
            loadBalancerPingClassName: com.github.lybgeek.loadbalance.ping.TelnetPing
            pingIntervalSeconds: 3
         #   loadBalancerRuleClassName: com.github.lybgeek.loadbalance.rule.CustomRoundRobinRule
            listOfServers: 127.0.0.1:6666,127.0.0.1:6667
    

    c、 测试类

      @Override
        public void run(ApplicationArguments args) throws Exception {
    
            ServerChooser serverChooser = ServerChooser.builder()
                    .loadBalancer(loadbalanceFactory.getLoadBalancer("provider", "test"))
                    .build();
    
            while(true){
                Server reachableServer = serverChooser.getServer("provider");
                if(reachableServer != null){
                    System.out.println(reachableServer.getHostPort());
                }
                TimeUnit.SECONDS.sleep(1);
    
            }
    
    
        }
    

    当服务提供者都正常提供服务时,观察控制台

    a5148ea02e0da439333ffd7cbe580729_ae0df305f528570bd4dac10a8c58ea7f.png

    可以观察以轮询的方式调用服务提供者,当断掉其中一台服务提供者时,再观察控制台

    a20220a479c001f1483c0f92916d73c3_4ec19edc921002a5df0123272cd06db1.png

    会发现只调用服务正常的那台

    总结

    独立使用ribbon其实不会很难,主要对LoadBalancerBuilder这个API熟悉就可以定制自己想要的负载均衡器。springcloud从2020版本就将ribbon废弃了,转而要扶持其亲儿子loadbalancer,就目前功能而言,loadbalancer还没ribbon丰富,通过本文纪念一下被springcloud遗弃的ribbon

    demo链接

    https://github.com/lyb-geek/springboot-learning/tree/master/springboot-ribbon-loadbalance

    相关文章

      网友评论

          本文标题:聊聊如何独立使用ribbon实现业务客户端负载均衡

          本文链接:https://www.haomeiwen.com/subject/gnceydtx.html