Nacos支持权重配置,这是个比较实用的功能,例如:
•把性能差的机器权重设低,性能好的机器权重设高,让请求优先打到性能高的机器上去;•某个实例出现异常时,把权重设低,排查问题,问题排查完再把权重恢复;•想要下线某个实例时,可先将该实例的权重设为0,这样流量就不会打到该实例上了——此时再去关停该实例,这样就能实现优雅下线啦。当然这是为Nacos量身定制的优雅下线方案——Spring Cloud中,要想实现优雅下线还有很多姿势,详见:《实用技巧:Spring Cloud中,如何优雅下线微服务?》[1] ,里面笔者总结了四种优雅下线的方式。
然而测试发现,Nacos权重配置对Spring Cloud Alibaba无效。也就是说,不管在Nacos控制台上如何配置,调用时都不管权重设置的。
Spring Cloud Alibaba通过整合Ribbon的方式,实现了负载均衡。所使用的负载均衡规则是 ZoneAvoidanceRule
。
本节来探讨如何扩展Ribbon,让其支持Nacos的权重配置,笔者总结了三种方案。
思路:
自己首先一个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循环,还挺吃内存的,建议百度其他权重随机算法优化。不过实际项目一个微服务一般也就三五个实例,所以其实内存消耗也能忍受。不优化问题也不大。
思路:
在阅读代码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) { } }
思路:
在阅读源码的过程中,发现如下代码:
// 来自: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]
本文分享自微信公众号 - IT牧场(itmuch_com),作者:itmuch
原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。
原始发表时间:2019-07-01
本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。
我来说两句