扩展Ribbon支持Nacos权重的三种方式

点击上方"IT牧场",选择"设为星标"技术干货每日送达!

Nacos支持权重配置,这是个比较实用的功能,例如:

•把性能差的机器权重设低,性能好的机器权重设高,让请求优先打到性能高的机器上去;•某个实例出现异常时,把权重设低,排查问题,问题排查完再把权重恢复;•想要下线某个实例时,可先将该实例的权重设为0,这样流量就不会打到该实例上了——此时再去关停该实例,这样就能实现优雅下线啦。当然这是为Nacos量身定制的优雅下线方案——Spring Cloud中,要想实现优雅下线还有很多姿势,详见:《实用技巧:Spring Cloud中,如何优雅下线微服务?》[1] ,里面笔者总结了四种优雅下线的方式。

然而测试发现,Nacos权重配置对Spring Cloud Alibaba无效。也就是说,不管在Nacos控制台上如何配置,调用时都不管权重设置的。

Spring Cloud Alibaba通过整合Ribbon的方式,实现了负载均衡。所使用的负载均衡规则是 ZoneAvoidanceRule 。

本节来探讨如何扩展Ribbon,让其支持Nacos的权重配置,笔者总结了三种方案。

方案1:自己实现负载均衡规则

思路

自己首先一个Ribbon负载均衡规则就可以了。

•权重配置啥的,都可以在实例信息中获取到。•自己基于权重配置,计算出一个实例即可。

代码

 
 
@Slf4j	
public class NacosWeightRandomV1Rule extends AbstractLoadBalancerRule {	
    @Override	
    public void initWithNiwsConfig(IClientConfig iClientConfig) {	
    }	
	
    @Override	
    public Server choose(Object key) {	
        List<Server> servers = this.getLoadBalancer().getReachableServers();	
	
        List<InstanceWithWeight> instanceWithWeights = servers.stream()	
                .map(server -> {	
                    // 注册中心只用Nacos,没同时用其他注册中心(例如Eureka),理论上不会实现	
                    if (!(server instanceof NacosServer)) {	
                        log.error("参数非法,server = {}", server);	
                        throw new IllegalArgumentException("参数非法,不是NacosServer实例!");	
                    }	
	
                    NacosServer nacosServer = (NacosServer) server;	
                    Instance instance = nacosServer.getInstance();	
                    double weight = instance.getWeight();	
                    return new InstanceWithWeight(	
                            server,	
                            Double.valueOf(weight).intValue()	
                    );	
                })	
                .collect(Collectors.toList());	
	
        Server server = this.weightRandom(instanceWithWeights);	
	
        log.info("选中的server = {}", server);	
        return server;	
    }	
	
    @Data	
    @AllArgsConstructor	
    @NoArgsConstructor	
    private class InstanceWithWeight {	
        private Server server;	
        private Integer weight;	
    }	
	
    /**	
     * 根据权重随机	
     * 算法参考 https://blog.csdn.net/u011627980/article/details/79401026	
     *	
     * @param list 实例列表	
     * @return 随机出来的结果	
     */	
    private Server weightRandom(List<InstanceWithWeight> list) {	
        List<Server> instances = Lists.newArrayList();	
        for (InstanceWithWeight instanceWithWeight : list) {	
            int weight = instanceWithWeight.getWeight();	
            for (int i = 0; i <= weight; i++) {	
                instances.add(instanceWithWeight.getServer());	
            }	
        }	
        int i = new Random().nextInt(instances.size());	
        return instances.get(i);	
    }	
}

WARNING

本段代码存在优化空间,只是用来演示思考的过程,不建议用于生产,如打算使用本方案实现,请参考以下两点优化

•简单起见,我直接把double型的权重(weight),转成了integer计算了,存在精度丢失•InstanceWithWeight太重了,在 weightRandom 还得再两层for循环,还挺吃内存的,建议百度其他权重随机算法优化。不过实际项目一个微服务一般也就三五个实例,所以其实内存消耗也能忍受。不优化问题也不大。

方案2:利用Nacos Client的能力[推荐]

思路

在阅读代码Nacos源码的过程中,发现Nacos Client本身就提供了负载均衡的能力,并且负载均衡算法正是我们想要的根据权重选择实例

代码在 com.alibaba.nacos.api.naming.NamingService#selectOneHealthyInstance ,只要想办法调用到这行代码,就可以实现我们想要的功能啦!

代码

 
 
@Slf4j	
public class NacosWeightRandomV2Rule extends AbstractLoadBalancerRule {	
    @Autowired	
    private NacosDiscoveryProperties discoveryProperties;	
	
    @Override	
    public Server choose(Object key) {	
        DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer) getLoadBalancer();	
        String name = loadBalancer.getName();	
        try {	
            Instance instance = discoveryProperties.namingServiceInstance()	
                    .selectOneHealthyInstance(name);	
	
            log.info("选中的instance = {}", instance);	
	
            /*	
             * instance转server的逻辑参考自:	
             * org.springframework.cloud.alibaba.nacos.ribbon.NacosServerList.instancesToServerList	
             */	
            return new NacosServer(instance);	
        } catch (NacosException e) {	
            log.error("发生异常", e);	
            return null;	
        }	
    }	
	
    @Override	
    public void initWithNiwsConfig(IClientConfig iClientConfig) {	
    }	
}

方案3:最暴力的玩法

思路

在阅读源码的过程中,发现如下代码:

 
 
// 来自:org.springframework.cloud.alibaba.nacos.ribbon.NacosServerList#getServers	
private List<NacosServer> getServers() {	
  try {	
    List<Instance> instances = discoveryProperties.namingServiceInstance()	
      .selectInstances(serviceId, true);	
    return instancesToServerList(instances);	
  }	
  catch (Exception e) {	
    throw new IllegalStateException(	
      "Can not get service instances from nacos, serviceId=" + serviceId,	
      e);	
  }	
}

这个NacosServerList 就是给Ribbon去做负载均衡的”数据源”!如果把这里的代码改成 com.alibaba.nacos.api.naming.NamingService#selectOneHealthyInstance 不也可以实现我们想要的功能吗?

也就是说,交给Ribbon的List永远只有1个实例!这样不管Ribbon用什么负载均衡,都随他便了。

代码

1 参考NacosServerList的代码,重写NacosRibbonServerList

 
 
/**	
 * 参考org.springframework.cloud.alibaba.nacos.ribbon.NacosServerList	
 */	
@Slf4j	
public class NacosRibbonServerList extends AbstractServerList<NacosServer> {	
	
    private NacosDiscoveryProperties discoveryProperties;	
	
    private String serviceId;	
	
    public NacosRibbonServerList(NacosDiscoveryProperties discoveryProperties) {	
        this.discoveryProperties = discoveryProperties;	
    }	
	
    @Override	
    public List<NacosServer> getInitialListOfServers() {	
        return getServers();	
    }	
	
    @Override	
    public List<NacosServer> getUpdatedListOfServers() {	
        return getServers();	
    }	
	
    private List<NacosServer> getServers() {	
        try {	
            Instance instance = discoveryProperties.namingServiceInstance()	
                    .selectOneHealthyInstance(serviceId, true);	
            log.debug("选择的instance = {}", instance);	
            return instancesToServerList(	
                    Lists.newArrayList(instance)	
            );	
        } catch (Exception e) {	
            throw new IllegalStateException(	
                    "Can not get service instances from nacos, serviceId=" + serviceId,	
                    e);	
        }	
    }	
	
    private List<NacosServer> instancesToServerList(List<Instance> instances) {	
        List<NacosServer> result = new ArrayList<>();	
        if (null == instances) {	
            return result;	
        }	
        for (Instance instance : instances) {	
            result.add(new NacosServer(instance));	
        }	
        return result;	
    }	
	
    public String getServiceId() {	
        return serviceId;	
    }	
	
    @Override	
    public void initWithNiwsConfig(IClientConfig iClientConfig) {	
        this.serviceId = iClientConfig.getClientName();	
    }	
}

2 编写配置类

 
 
/**	
 * 参考:org.springframework.cloud.alibaba.nacos.ribbon.NacosRibbonClientConfiguration	
 */	
@Configuration	
public class NacosRibbonClientExtendConfiguration {	
    @Bean	
    public ServerList<?> ribbonServerList(IClientConfig config, NacosDiscoveryProperties nacosDiscoveryProperties) {	
        NacosRibbonServerList serverList = new NacosRibbonServerList(nacosDiscoveryProperties);	
        serverList.initWithNiwsConfig(config);	
        return serverList;	
    }	
}

3 添加注解,让上面的NacosRibbonClientExtendConfiguration成为Ribbon的默认配置

 
 
// ...其他注解	
@RibbonClients(defaultConfiguration = NacosRibbonClientExtendConfiguration.class)	
public class ConsumerMovieApplication {	
  public static void main(String[] args) {	
    SpringApplication.run(ConsumerMovieApplication.class, args);	
  }	
}

注意 :

务必注意,将 NacosRibbonClientExtendConfiguration 放在ComponentScan上下文(默认是启动类所在包及其子包)以外!!!

总结与对比

•方案1:是最容易想到的玩法。•方案2:是个人目前最喜欢的方案。首先简单,并且都是复用Nacos/Ribbon现有的代码——而Ribbon/Nacos本身都是来自于大公司生产环境,经过严苛的生产考验。•方案3:太暴力了,把Ribbon架空了。此方案中,扔给Ribbon做负载均衡选择时,List只有1个元素,不管用什么算法去算,最后总是会返回这个元素!

思考

既然Nacos Client已经有负载均衡的能力,Spring Cloud Alibaba为什么还要去整合Ribbon呢?

个人认为,这主要是为了符合Spring Cloud标准。Spring Cloud Commons有个子项目 spring-cloud-loadbalancer ,该项目制定了标准,用来适配各种客户端负载均衡器(虽然目前实现只有Ribbon,但Hoxton就会有替代的实现了)。

Spring Cloud Alibaba遵循了这一标准,所以整合了Ribbon,而没有去使用Nacos Client提供的负载均衡能力。

配套代码

GitHub[2]Gitee[3]

干货分享

最近将个人学习笔记整理成册,使用PDF分享。关注我,回复如下代码,即可获得百度盘地址,无套路领取!

•001:《Java并发与高并发解决方案》学习笔记;•002:《深入JVM内核——原理、诊断与优化》学习笔记;•003:《Java面试宝典》•004:《Docker开源书》•005:《Kubernetes开源书》•006:《DDD速成(领域驱动设计速成)》•007:全部•008:加技术讨论群

往期精彩

e.printStackTrace()不是打印吗,还能锁死?一次 MySQL 千万级大表的优化过程高级 Java 程序猿必须掌握的 17 个 JVM 参数日志打印的正确姿势!重构一时爽,构错火葬场SpringBoot 2.0 教程实战 MySQL 读写分离

References

[1] 《实用技巧:Spring Cloud中,如何优雅下线微服务?》: http://www.itmuch.com/spring-cloud-sum/how-to-unregister-service-in-eureka/
[2] GitHub: https://github.com/eacdy/spring-cloud-study/tree/master/2019-Spring-Cloud-Alibaba/microservice-consumer-movie-ribbon-rule-with-nacos
[3] Gitee: https://gitee.com/itmuch/spring-cloud-study/tree/master/2019-Spring-Cloud-Alibaba/microservice-consumer-movie-ribbon-rule-with-nacos


想知道更多?长按/扫码关注我吧↓↓↓640?wx_fmt=jpeg>>>技术讨论群<<<喜欢就点个"在看"呗^_^

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值