前言

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@Builderpublic 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链接

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