• 客户端侧负载均衡

在下图中,负载均衡能力算法是由内容中心提供,内容中心相对于用户中心来说,是用户中心的客户端,所以又被称为客户端侧负载均衡


自定义实现Client Random负载均衡

  1. 获取所有的服务list
  2. 随机获取需要访问的服务信息
// 自定义客户端负载均衡能力
        // 获取所有用户中心服务的实例列表
        List<String> targetUris = instances.stream().map(i -> i.getUri().toString() + "/users/{id}").collect(Collectors.toList());

        //获取随机实例
        int i = ThreadLocalRandom.current().nextInt(targetUris.size());

        //调用用户微服务 /users/{userId}
        log.info("请求的目标地址:{}", targetUris.get(i));
        ResponseEntity<UserDTO> userEntity = restTemplate.getForEntity(
                targetUris.get(i),
                UserDTO.class, userId
        );

Ribbon

什么是Ribbon

Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法。

组成接口

![image-20190713132523310](/Users/zhangpan/Library/Application Support/typora-user-images/image-20190713132523310.png)

内置负载均衡规则

![image-20190713133911384](/Users/zhangpan/Library/Application Support/typora-user-images/image-20190713133911384.png)

配置方式

  • Java 代码配置
/**
 * RibbonConfiguration for TODO
 *
 * @author <a href="mailto:magicianisaac@gmail.com">Isaac.Zhang | 若初</a>
 * @since 2019/7/13
 */
@Configuration
public class RibbonConfiguration {
    @Bean
    public IRule ribbonRule(){
        return new RandomRule();
    }
}
/*------------------------------------------------------------*/
/**
 * UserCenterRibbonConfiguration for 自定义实现User-center service ribbon client
 *
 * @author <a href="mailto:magicianisaac@gmail.com">Isaac.Zhang | 若初</a>
 * @since 2019/7/13
 */
@Configuration
@RibbonClient(name = "user-center", configuration = RibbonConfiguration.class)
public class UserCenterRibbonConfiguration {
    
}
/*------------------------------------------------------------*/
@Configuration
//@RibbonClient(name = "user-center", configuration = RibbonConfiguration.class) //作用域为 user-center
@RibbonClients(defaultConfiguration = RibbonConfiguration.class) //作用域为全局
public class UserCenterRibbonConfiguration {

}
  • 使用配置文件
user-center: # service name
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 规则类的全路径名称


  • 对比

![image-20190713142916635](/Users/zhangpan/Library/Application Support/typora-user-images/image-20190713142916635.png)

  • 最佳使用
  • 尽量使用属性配置
  • 在同一个微服务中尽量保持单一配置,不要混合使用,增加定位复杂性

Tip

在使用Ribbon的时候,配置class一定不能处于启动类的同级目录及其子目录,否则会导致父子上下文重叠问题,带来的结果就是,Ribbon规则会被配置称全局配置规则,从而被所有微服务应用。

![image-20190713141743025](/Users/zhangpan/Library/Application Support/typora-user-images/image-20190713141743025.png)

The CustomConfiguration class must be a @Configuration class, but take care that it is not in a @ComponentScan for the main application context. Otherwise, it is shared by all the @RibbonClients. If you use @ComponentScan (or @SpringBootApplication), you need to take steps to avoid it being included (for instance, you can put it in a separate, non-overlapping package or specify the packages to scan explicitly in the @ComponentScan).

Ribbon 饥饿加载

默认情况下,Ribbon是懒加载,在第一次请求的时候才会创建客户端。

ribbon:
  eager-load:
    enabled: true # 饥饿加载激活
    clients: user-center,xxx,xxx # 为哪些clients开启

使用Ribbon 替代自定义实现

  1. 添加依赖(Spring-Cloud-Alibaba-Nacos-Discovery已经依赖了Ribbon,因此不需要额外依赖)
  2. 添加注解(只需要在RestTemplate IOC添加 @LoadBalance)
/**
     * 在Spring 容器中,创建一个对象,类型是{@link RestTemplate}
     * 名称/ID 为 restTemplate
     * <bean id ="restTemplate" class="XXX.RestTemplate" />
     * {@link LoadBalanced} 为RestTemplate整合Ribbon调用
     *
     * @return restTemplate
     */
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
  1. 添加配置,直接使用
ResponseEntity<UserDTO> userEntity = restTemplate.getForEntity(
                "http://user-center/users/{userId}",
                UserDTO.class, userId
        );

扩展Ribbon - 支持Nacos权重

  • 实现接口com.netflix.loadbalancer.IRule
  • 实现抽象类 com.netflix.loadbalancer.AbstractLoadBalancerRule

![image-20190713150041397](/Users/zhangpan/Library/Application Support/typora-user-images/image-20190713150041397.png)

@Slf4j
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class NacosWeightRule4Ribbon extends AbstractLoadBalancerRule {

    private final NacosDiscoveryProperties nacosDiscoveryProperties;

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
        // 读取配置文件,并初始化 NacosWeightRule4Ribbon
    }

    @Override
    public Server choose(Object key) {

        try {
            // ILoadBalancer 是Ribbon的入口,基本上我们想要的元素都可以在这个对象中找到
            BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
            log.info("NacosWeightRule4Ribbon lb = {}", loadBalancer);
            // 想要请求的微服务名称
            String name = loadBalancer.getName();

            // 实现负载均衡算法
            // 可得到服务发现相关的API(nacos内部实现)
            NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();

            // nacos client 通过基于权重的负载均衡算法,选择一个实例
            Instance instance = namingService.selectOneHealthyInstance(name);
            log.info("port = {}, weight = {}, instance = {}", instance.getPort(), instance.getWeight(), instance);
            return new NacosServer(instance);
        } catch (NacosException e) {
            log.error("NacosWeightRule4Ribbon {}", e.getMessage());
        }
        return null;
    }
}


@Configuration
@RibbonClients(defaultConfiguration = NacosWeightRule4Ribbon.class) //全局配置
public class UserCenterRibbonConfiguration {
}

拓展Ribbon - 同集群优先

public Server choose(Object key) {

        try {
            // 获取到配置文件中的集群名称 BJ
            String clusterName = nacosDiscoveryProperties.getClusterName();

            BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
            String serviceName = loadBalancer.getName();

            //获取服务发现的相关API
            NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
            // 1. 找到指定服务的所有实例 A
            List<Instance> instances = namingService.selectInstances(serviceName, true);
            // 2. 过滤出相同集群下的所有实例 B
            List<Instance> sameClusterInstances = instances.stream()
                                                           .filter(instance -> Objects.equals(instance.getClusterName(), clusterName))
                                                           .collect(Collectors.toList());
            // 3. 如果B为空,则使用 A
            List<Instance> instancesChoosen = new ArrayList<>();
            if (CollectionUtils.isEmpty(sameClusterInstances)) {
                instancesChoosen = instances;
                log.warn("发生跨集群调用,name = {},clusterName = {}", serviceName, clusterName);
            } else {
                instancesChoosen = sameClusterInstances;
            }

            // 4. 基于权重的负载均衡算法,返回一个实例 A
            Instance instance = ExtendBalancer.getHostByRandomWeightOverride(instancesChoosen);
            log.info("choose instance is : port = {}, instance = {}", instance.getPort(), instance);
            return new NacosServer(instance);
        } catch (NacosException e) {
            e.printStackTrace();
            log.error(e.getErrMsg());
        }
        return null;
    }

/**
* 调用Nacos内部方法,进行一次包装
*/
class ExtendBalancer extends Balancer {
    public static Instance getHostByRandomWeightOverride(List<Instance> hosts) {
        return getHostByRandomWeight(hosts);
    }
}

扩展Ribbon - 基于元数据的版本控制

| [51CTO]