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

栏目: Java · 发布时间: 5年前

内容简介: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: 加技术讨论群

往期精彩

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

想知道更多?长按/扫码关注我吧↓↓↓ 扩展Ribbon支持Nacos权重的三种方式 >>>技术讨论群<<< 喜欢就点个 "在看" 呗^_^


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

构建之法(第三版)

构建之法(第三版)

邹欣 / 人民邮电出版社 / 2017-6 / 69.00元

软件工程牵涉的范围很广, 同时也是一般院校的同学反映比较空洞乏味的课程。 但是,软件工程 的技术对于投身 IT 产业的学生来说是非常重要的。作者有在世界一流软件企业 20 年的一线软件开 发经验,他在数所高校进行了多年的软件工程教学实践,总结出了在 16 周的时间内让同学们通过 “做 中学 (Learning By Doing)” 掌握实用的软件工程技术的教学计划,并得到高校师生的积极反馈。在此 ......一起来看看 《构建之法(第三版)》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

SHA 加密
SHA 加密

SHA 加密工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具