Talk about how to use ribbon independently to achieve business client load balancing

foreword

Ribbon is Netflix's open source client load balancing tool. Ribbon implements a series of load balancing algorithms, and uses these load balancing algorithms to find corresponding services. Ribbon is well known to everyone, and it may come from spring cloud. Today, let’s talk about how to use ribbon alone to achieve business client load balancing.

Realize the key

The springcloud ribbon obtains the service list through the registration center, and the ribbon is used alone. Because there is no registration center blessing, the service list must be configured separately

example

1. Introduce ribbon GAV into the pom in the business project

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

However, after importing it, I found that if I introduce netfiix-related classes, such as IPing, I will find that it cannot be introduced. The reason is that the life cycle of the jar that GAV depends on is runtime, that is, it only takes effect during the runtime or test phase, and it takes effect during the compilation phase. is not valid. For convenience, we can directly introduce
spring cloud ribbon separately

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

In this article, we want to break away from springcloud and use ribbon directly, so we can directly introduce the following 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. Create ribbon metadata configuration class

@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. Create a load balancing factory [core implementation]

 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;
    }


Core implementation class : com.netflix.loadbalancer.LoadBalancerBuilder Use this class to create the corresponding load balancer

4. Test

a. Two new service providers occupy port 6666 and port 6667

b. The application.yml configuration on the consumer side is as follows

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. Test class

  @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);

        }


    }

When the service provider is providing services normally, observe the console


You can observe that the service provider is called in a polling manner, and when one of the service providers is disconnected, observe the console


You will find that only the one with normal service is called

Summarize

It is actually not difficult to use the ribbon independently. You can customize the load balancer you want if you are familiar with the LoadBalancerBuilder API. Springcloud has abandoned the ribbon since the 2020 version, and instead supports its own son loadbalancer. As far as the current functions are concerned, loadbalancer is not as rich as ribbon. Through this article, I will commemorate the ribbon abandoned by springcloud

demo link

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

Guess you like

Origin blog.csdn.net/kingwinstar/article/details/129878695