天天看点

spring cloud Alibaba 实现负载均衡 Ribbon学习笔记五一、实现方式二、手写客户端侧负载均衡器三、使用Ribbon实现负载均衡四、Ribbon 组成五、Ribbon 配置六、Ribbon 饥饿加载七、Ribbon扩展八、现有架构存在问题

Ribbon实现负载均衡

  • 一、实现方式
  • 二、手写客户端侧负载均衡器
  • 三、使用Ribbon实现负载均衡
  • 四、Ribbon 组成
  • 五、Ribbon 配置
  • 六、Ribbon 饥饿加载
  • 七、Ribbon扩展
  • 八、现有架构存在问题

一、实现方式

  • 服务器端负载均衡(单体架构)
    spring cloud Alibaba 实现负载均衡 Ribbon学习笔记五一、实现方式二、手写客户端侧负载均衡器三、使用Ribbon实现负载均衡四、Ribbon 组成五、Ribbon 配置六、Ribbon 饥饿加载七、Ribbon扩展八、现有架构存在问题
  • 客户端侧负载均衡(内容中心相对用户中心,属于客户端,可在内容中心集群中配置负载均衡规则)
    spring cloud Alibaba 实现负载均衡 Ribbon学习笔记五一、实现方式二、手写客户端侧负载均衡器三、使用Ribbon实现负载均衡四、Ribbon 组成五、Ribbon 配置六、Ribbon 饥饿加载七、Ribbon扩展八、现有架构存在问题

二、手写客户端侧负载均衡器

  • 随机负载均衡算法
    spring cloud Alibaba 实现负载均衡 Ribbon学习笔记五一、实现方式二、手写客户端侧负载均衡器三、使用Ribbon实现负载均衡四、Ribbon 组成五、Ribbon 配置六、Ribbon 饥饿加载七、Ribbon扩展八、现有架构存在问题

三、使用Ribbon实现负载均衡

1、定义:Netfix开源的客户端侧负载均衡器,即提供了负载均衡算法

2、架构演进:

spring cloud Alibaba 实现负载均衡 Ribbon学习笔记五一、实现方式二、手写客户端侧负载均衡器三、使用Ribbon实现负载均衡四、Ribbon 组成五、Ribbon 配置六、Ribbon 饥饿加载七、Ribbon扩展八、现有架构存在问题

3、整合Ribbon

  • 加依赖:不需要,spring-cloud-starter-alibaba-nacos-discovery已默认提供
    spring cloud Alibaba 实现负载均衡 Ribbon学习笔记五一、实现方式二、手写客户端侧负载均衡器三、使用Ribbon实现负载均衡四、Ribbon 组成五、Ribbon 配置六、Ribbon 饥饿加载七、Ribbon扩展八、现有架构存在问题
  • 加注解
spring cloud Alibaba 实现负载均衡 Ribbon学习笔记五一、实现方式二、手写客户端侧负载均衡器三、使用Ribbon实现负载均衡四、Ribbon 组成五、Ribbon 配置六、Ribbon 饥饿加载七、Ribbon扩展八、现有架构存在问题
  • 写配置:无配置

PS:代码改写,使用http://user-center/users/{userId},Ribbon会自动读取名称,进行地址查找并转换

spring cloud Alibaba 实现负载均衡 Ribbon学习笔记五一、实现方式二、手写客户端侧负载均衡器三、使用Ribbon实现负载均衡四、Ribbon 组成五、Ribbon 配置六、Ribbon 饥饿加载七、Ribbon扩展八、现有架构存在问题

四、Ribbon 组成

spring cloud Alibaba 实现负载均衡 Ribbon学习笔记五一、实现方式二、手写客户端侧负载均衡器三、使用Ribbon实现负载均衡四、Ribbon 组成五、Ribbon 配置六、Ribbon 饥饿加载七、Ribbon扩展八、现有架构存在问题

1、内置负载均衡规则

spring cloud Alibaba 实现负载均衡 Ribbon学习笔记五一、实现方式二、手写客户端侧负载均衡器三、使用Ribbon实现负载均衡四、Ribbon 组成五、Ribbon 配置六、Ribbon 饥饿加载七、Ribbon扩展八、现有架构存在问题

PS:默认为ZoneAvoidanceRule,无Zone环境,等价于RoundRobinRule

五、Ribbon 配置

1、java代码配置,采用随机算法

  • 新建RibbonConfiguration 配置类
package com.hzb2i.contentcenter.configuration.Ribbon;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RibbonConfiguration {
    @Bean
    public IRule ribbonRule(){
        return new RandomRule();
    }
}
           
  • 新建 UserCenterRibbonConfiguration 类
package com.hzb2i.contentcenter.configuration;

import RibbonConfiguration.RibbonConfiguration;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.Configuration;

@Configuration
@RibbonClient(name = "user-center",configuration = RibbonConfiguration.class)
public class UserCenterRibbonConfiguration {
}

           

PS:需要注意UserCenterRibbonConfiguration 需要放到@SpringBootApplication启动类扫描不到的包下

spring cloud Alibaba 实现负载均衡 Ribbon学习笔记五一、实现方式二、手写客户端侧负载均衡器三、使用Ribbon实现负载均衡四、Ribbon 组成五、Ribbon 配置六、Ribbon 饥饿加载七、Ribbon扩展八、现有架构存在问题
  • @SpringBootApplication注解默认扫描当前的包和子包
  • 若将UserCenterRibbonConfiguration 放到启动类相同路径下,将会出现@SpringBootApplication 主上下文和UserCenterRibbonConfiguration 类的@Configuration 子上下文出现重复扫描,UserCenterRibbonConfiguration 的配置为全局配置,将会被所有RibbonClient共享,即不管内容中心访问用户中心还是其他的微服务,都会采用UserCenterRibbonConfiguration 配置(重复扫描带来的问题,感兴趣可看此分享:spring重复扫描事务失败)

2、用配置属性配置:application.yml加入以下配置

user-center:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
           

PS:两种配置方式对比

spring cloud Alibaba 实现负载均衡 Ribbon学习笔记五一、实现方式二、手写客户端侧负载均衡器三、使用Ribbon实现负载均衡四、Ribbon 组成五、Ribbon 配置六、Ribbon 饥饿加载七、Ribbon扩展八、现有架构存在问题

PS:写配置方式,线上无需重新打包,采用的是读取外部配置文件方式

3、最佳实践:

  • 尽量使用属性配置,属性配置实现不了情况再考虑用代码配置
  • 在同一个微服务内尽量保持单一性,比如统一使用属性配置,不要用两种方式混用,增加定位代码的复杂性

4、全局配置:

  • 方式一:让ComponentScan重叠,即将RibbonConfiguration放到启动类相同路径(不建议使用,会导致项目无法启动)
  • 正确途径
    spring cloud Alibaba 实现负载均衡 Ribbon学习笔记五一、实现方式二、手写客户端侧负载均衡器三、使用Ribbon实现负载均衡四、Ribbon 组成五、Ribbon 配置六、Ribbon 饥饿加载七、Ribbon扩展八、现有架构存在问题
    5、Ribbon其他配置项
  • 代码配置:
    spring cloud Alibaba 实现负载均衡 Ribbon学习笔记五一、实现方式二、手写客户端侧负载均衡器三、使用Ribbon实现负载均衡四、Ribbon 组成五、Ribbon 配置六、Ribbon 饥饿加载七、Ribbon扩展八、现有架构存在问题
  • 写配置:
    spring cloud Alibaba 实现负载均衡 Ribbon学习笔记五一、实现方式二、手写客户端侧负载均衡器三、使用Ribbon实现负载均衡四、Ribbon 组成五、Ribbon 配置六、Ribbon 饥饿加载七、Ribbon扩展八、现有架构存在问题

六、Ribbon 饥饿加载

  • 问题:Ribbon默认采用懒加载,只有http://user-center/users/{userId}执行,才会使用Ribbon自动获取用户中心地址,导致第一次访问加载速度慢
  • 措施:写配置,开启饥饿加载
    spring cloud Alibaba 实现负载均衡 Ribbon学习笔记五一、实现方式二、手写客户端侧负载均衡器三、使用Ribbon实现负载均衡四、Ribbon 组成五、Ribbon 配置六、Ribbon 饥饿加载七、Ribbon扩展八、现有架构存在问题

七、Ribbon扩展

1、支持Nacos权重(根据权重判断的负载均衡规则):

  • 场景:若微服务有众多实例,分别部署到不同服务器上,可以把在性能好的机器上的实例权重设高点,性能差的机器的实例权重设低点,这样权重高的实例被调用的几率就高一些
  • ribbon默认不支持nacos权重,需通过以下方式开发

    ① 可实现IRule接口

    ② 继承AbstractLoadBalancerRule抽象类,并将此负载均衡算法设为全局配置

package com.hzb2i.contentcenter.configuration;

import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.ribbon.NacosServer;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.BaseLoadBalancer;
import com.netflix.loadbalancer.Server;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;

/*
 * ribbon 扩展:支撑nacos权重
 */
@Slf4j
public class NacosWeightRule extends AbstractLoadBalancerRule {
    @Autowired
    private NacosDiscoveryProperties nacosDiscoveryProperties;

    @Override
    public void initWithNiwsConfig(IClientConfig iClientConfig) {
        //读取配置,并初始化NacosWeightRule
    }

    @Override
    public Server choose(Object o) {
        try {
            //入口
            BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
            //想要请求的微服务名称
            String name = loadBalancer.getName();
            //实现负载均衡算法
            //拿到服务发现的相关API
            NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
            // nacos client 自动通过基于权重的负责均衡算法,给我们选择一个实例
            Instance instance =  namingService.selectOneHealthyInstance(name);
            log.info("选择的实例:port={},instance={}",instance.getPort(),instance);
            return new NacosServer(instance);
        } catch (NacosException e) {
            e.printStackTrace();
        }
        return null;
    }
}
           

在nacos控制台修改权重:

spring cloud Alibaba 实现负载均衡 Ribbon学习笔记五一、实现方式二、手写客户端侧负载均衡器三、使用Ribbon实现负载均衡四、Ribbon 组成五、Ribbon 配置六、Ribbon 饥饿加载七、Ribbon扩展八、现有架构存在问题

启动用户中心、内容中心测试:请求基本调用权重高的实例

spring cloud Alibaba 实现负载均衡 Ribbon学习笔记五一、实现方式二、手写客户端侧负载均衡器三、使用Ribbon实现负载均衡四、Ribbon 组成五、Ribbon 配置六、Ribbon 饥饿加载七、Ribbon扩展八、现有架构存在问题

PS:nacos client内置了基于权重的负载均衡算法,spring cloud Alibaba为何还要整合ribbon,直接用nacos client不就行了吗?主要是为符合spring cloud标准,spring cloud有个子项目spring cloud commons(定义了标准),其下边的子项目spring cloud loadbalancer(无权重概念),spring cloud Alibaba符合此标准,故未整合权重的算法

PS:其他实现权重负载均衡算法方式请移步 扩展Ribbon支持Nacos权重的三种方式

2、同集群优先调用

  • 场景:如存在北京机房和南京机房,想让北京机房的内容中心有限调用同机房的用户中心,在同机房用户中心不存在的情况下再调用南京机房的用户中心,可以采用此方式
  • 代码开发,并将此负载均衡算法设为全局配置:
package com.hzb2i.contentcenter.configuration;

import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.ribbon.NacosServer;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.client.naming.core.Balancer;
import com.alibaba.nacos.client.naming.utils.Chooser;
import com.alibaba.nacos.client.naming.utils.Pair;
import com.alibaba.nacos.client.utils.LogUtils;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.BaseLoadBalancer;
import com.netflix.loadbalancer.Server;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
public class NacosSameClusterWeightRule extends AbstractLoadBalancerRule {

    @Autowired
    private NacosDiscoveryProperties nacosDiscoveryProperties;

    @Override
    public void initWithNiwsConfig(IClientConfig iClientConfig) {

    }

    @Override
    public Server choose(Object o) {
        try {
            String clusterName = nacosDiscoveryProperties.getClusterName();
            //入口
            BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
            //想要请求的微服务名称
            String name = loadBalancer.getName();
            //实现负载均衡算法
            //拿到服务发现的相关API
            NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
            //找到指定服务的所有API
            List<Instance> instances = namingService.selectInstances(name,true);
            //过滤相同集群的所有实例
            List<Instance> sameClusterInstances = instances.stream().filter(instance -> {
                return Objects.equals(instance.getClusterName(),clusterName);
            }).collect(Collectors.toList());
            //如果B(sameClusterInstances)是空,否则用A(instances)
            List<Instance> instancesToBeChosen = new ArrayList<>();
            if(CollectionUtils.isEmpty(sameClusterInstances)){
                instancesToBeChosen = instances;
                log.info("跨集群调用:name={},clusterName={},instances={}",name,clusterName,instances);
            }else {
                instancesToBeChosen = sameClusterInstances;
                log.info("同集群调用:name={},clusterName={},instances={}",name,clusterName,sameClusterInstances);
            }
            //基于权重的负载均衡算法,返回一个实例(namingService无根据list直接返回实例)
            Instance instance = ExtendBalancer.getHostByRandomWeight2(instancesToBeChosen);
            return new NacosServer(instance);
        } catch (NacosException e) {
            e.printStackTrace();
        }
        return null;
    }
}
class ExtendBalancer extends Balancer {
    public static Instance getHostByRandomWeight2(List<Instance> hosts) {
        LogUtils.NAMING_LOGGER.debug("entry randomWithWeight");
        if (hosts != null && hosts.size() != 0) {
            LogUtils.NAMING_LOGGER.debug("new Chooser");
            List<Pair<Instance>> hostsWithWeight = new ArrayList();
            Iterator var2 = hosts.iterator();

            while(var2.hasNext()) {
                Instance host = (Instance)var2.next();
                if (host.isHealthy()) {
                    hostsWithWeight.add(new Pair(host, host.getWeight()));
                }
            }

            LogUtils.NAMING_LOGGER.debug("for (Host host : hosts)");
            Chooser<String, Instance> vipChooser = new Chooser("www.taobao.com");
            vipChooser.refresh(hostsWithWeight);
            LogUtils.NAMING_LOGGER.debug("vipChooser.refresh");
            return (Instance)vipChooser.randomWithWeight();
        } else {
            LogUtils.NAMING_LOGGER.debug("hosts == null || hosts.size() == 0");
            return null;
        }
    }
}
           

启动测试:优先访问同集群的端口,同集群微服务挂了,才进行跨集群访问

spring cloud Alibaba 实现负载均衡 Ribbon学习笔记五一、实现方式二、手写客户端侧负载均衡器三、使用Ribbon实现负载均衡四、Ribbon 组成五、Ribbon 配置六、Ribbon 饥饿加载七、Ribbon扩展八、现有架构存在问题
spring cloud Alibaba 实现负载均衡 Ribbon学习笔记五一、实现方式二、手写客户端侧负载均衡器三、使用Ribbon实现负载均衡四、Ribbon 组成五、Ribbon 配置六、Ribbon 饥饿加载七、Ribbon扩展八、现有架构存在问题

3、基于元数据的版本控制:扩展Ribbon支持基于元数据的版本管理

PS:深入理解Nacos NameSpace:微服务只能调用同一空间的实例

八、现有架构存在问题

spring cloud Alibaba 实现负载均衡 Ribbon学习笔记五一、实现方式二、手写客户端侧负载均衡器三、使用Ribbon实现负载均衡四、Ribbon 组成五、Ribbon 配置六、Ribbon 饥饿加载七、Ribbon扩展八、现有架构存在问题

继续阅读