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

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

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

然而测试发现,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提供的负载均衡能力。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,406评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,732评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,711评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,380评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,432评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,301评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,145评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,008评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,443评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,649评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,795评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,501评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,119评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,731评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,865评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,899评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,724评论 2 354

推荐阅读更多精彩内容