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

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

  把性能差的机器权重设低,性能好的机器权重设高,让请求优先打到性能高的机器上去;

  某个实例出现异常时,把权重设低,排查问题,问题排查完再把权重恢复;

  想要下线某个实例时,可先将该实例的权重设为0,这样流量就不会打到该实例上了——此时再去关停该实例,这样就能实现优雅下线啦。当然这是为Nacos量身定制的优雅下线方案——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) {

  Listservers = this.getLoadBalancer().getReachableServers();

  ListinstanceWithWeights = 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(Listlist) {

  Listinstances = 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 ListgetServers() {

  try {

  Listinstances = 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{

  private NacosDiscoveryProperties discoveryProperties;

  private String serviceId;

  public NacosRibbonServerList(NacosDiscoveryProperties discoveryProperties) {

  this.discoveryProperties = discoveryProperties;

  }

  @Override

  public ListgetInitialListOfServers() {

  return getServers();

  }

  @Override

  public ListgetUpdatedListOfServers() {

  return getServers();

  }

  private ListgetServers() {

  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 ListinstancesToServerList(Listinstances) {

  Listresult = 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提供的负载均衡能力。

你可能感兴趣的:(个人技术分享)