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

 更新时间:2023年06月27日 09:05:40   作者:linyb极客之路  
这篇文章主要为大家介绍了详解如何独立使用ribbon实现业务客户端负载均衡,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

前言

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

        }


    }

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

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

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

总结

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

demo链接

以上就是详解如何独立使用ribbon实现业务客户端负载均衡的详细内容,更多关于ribbon客户端负载均衡的资料请关注脚本之家其它相关文章!

相关文章

  • java实现双色球机选号码生成器

    java实现双色球机选号码生成器

    这篇文章主要为大家详细介绍了java实现双色球机选号码生成器,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-01-01
  • Java桥接模式实例详解【简单版与升级版】

    Java桥接模式实例详解【简单版与升级版】

    这篇文章主要介绍了Java桥接模式,结合实例形式分析了java桥接模式简单版与升级版两种实现技巧,需要的朋友可以参考下
    2019-07-07
  • Java C++题解leetcode消失的两个数字实例

    Java C++题解leetcode消失的两个数字实例

    这篇文章主要介绍了Java C++题解leetcode消失的两个数字实例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-09-09
  • Django之多对多查询与操作方法详解

    Django之多对多查询与操作方法详解

    这篇文章主要介绍了Django之多对多查询与操作方法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-05-05
  • Spark-Sql入门程序示例详解

    Spark-Sql入门程序示例详解

    Spark SQL 作为 Spark 四大核心组件之一,主要用于处理结构化数据或半结构化数据,它支持在Spark 中使用 SQL 对数据进行查询,本文给大家介绍Spark-Sql入门程序,感兴趣的朋友跟随小编一起看看吧
    2021-12-12
  • springboot 无法扫描到父类模块中Bean的原因及解决

    springboot 无法扫描到父类模块中Bean的原因及解决

    这篇文章主要介绍了springboot 无法扫描到父类模块中Bean的原因及解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • 一文搞懂Java MD5算法的原理及实现

    一文搞懂Java MD5算法的原理及实现

    MD5信息摘要算法,一种被广泛使用的密码散列函数,可以产生出一个128位(16字节)的散列值(hash value),用于确保信息传输完整一致。本文将详解MD5算法的原理及实现,感兴趣的可以了解一下
    2022-06-06
  • SpringBoot Bean被加载时进行控制

    SpringBoot Bean被加载时进行控制

    很多时候我们需要根据不同的条件在容器中加载不同的Bean,或者根据不同的条件来选择是否在容器中加载某个Bean,这就是Bean的加载控制,一般我们可以通过编程式或注解式两种不同的方式来完成Bean的加载控制
    2023-02-02
  • SpringBoot基于自定义注解实现切面编程

    SpringBoot基于自定义注解实现切面编程

    这篇文章主要介绍了SpringBoot基于自定义注解实现切面编程,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-11-11
  • 编写Java代码对HDFS进行增删改查操作代码实例

    编写Java代码对HDFS进行增删改查操作代码实例

    这篇文章主要介绍了Java代码对HDFS进行增删改查操作,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-04-04

最新评论