Dubbo自适应随机负载均衡策略的实现

原创 吴就业 134 0 2019-11-23

本文为博主原创文章,未经博主允许不得转载。

本文链接:https://wujiuye.com/article/c89e195c3fc9448c8bb9bd2e8bc3c10c

作者:吴就业
链接:https://wujiuye.com/article/c89e195c3fc9448c8bb9bd2e8bc3c10c
来源:吴就业的网络日记
本文为博主原创文章,未经博主允许不得转载。

本篇文章写于2019年11月23日,从公众号|掘金|CSDN手工同步过来(博客搬家),本篇为原创文章。

写了几篇dubbo源码分析的文章,感觉有些枯燥,而且阅读量也不是很好,我想了许久,怎样表达才能让读者看完后能对dubbo有更多的了解。甚至能让未使用过dubbo的读者能够对dubbo产生兴趣。我想换个方式跟大家一起学习dubbo,即通过一些“好玩”的案例介绍dubbo的一些特性。

通过上篇的学习,我们已经了解了dubbo是如何实现动态修改负载均衡策略的权重的。本篇将继续介绍,如何取巧的通过自定义负载均衡策略+配置中心,实现动态修改负载均衡的权重,以及实现自适应动态修改权重,解除繁琐的配置。下篇再来个好玩些的例子,基于Netty自实现Dubbo注册中心。

我们先总结下前几篇介绍的内容,千字万字不如两句。第一句,掌握Dubbo SPI是看懂Dubbo源码的必修课;第二句,Dubbo URL是衔接各分层的粘合剂,相当于数据总线,实际上Dubbo的自适应扩展点机制也是强依赖URL实现的。注意,此URL非Java.net.URL。只要搞懂这两点,看源码就会很清晰。不要怪我啰嗦,每篇都提一次,实在太重要了。

配置中心+自定义随机负载均衡策略

Dubbo默认使用随机负载均衡策略,据笔者了解,目前Dubbo一共提供了四种可选的负载均衡策略,有关于负载均衡策略的实现,如果不怕阅读源码枯燥的,笔者推荐阅读官网的源码导读部份的文档。目前项目中,我只考虑使用随机负载均衡策略。

虽然默认随机负载均衡策略的权重我们可以通过注册中心动态修改,但实际开发中,我们想要实现的负载权重可能更复杂点。服务部署的变动调整也很正常,我们可能需要用到更贴近线上环境及项目的服务分布情况的负载均衡权重配置。所以自实现随机负载均衡策略,结合配置中心,实现权重的动态调整也是有意义的。实不实用先不说,好好娱乐一下总是可以的,注意,不要在线上环境去玩哦。

那么,为什么需要调整随机负载均衡的权重。比如我有个服务部署两个节点,分别在一台4核的ec2实例和一台2核的ec2实例上,很明显,部署在4核ec2实例上的节点处理能力更强,每秒所能处理的请求数更多,所以需要被随机到的概率要配置大一些,用比例描述被随机到的概率应该配置成2:1。

我在源码提供的demo中实现自定义负载均衡策略,dubbo源码版本为2.7.2。自定义负载均衡策略需要显示声明配置,我将一步步带大家实现一个简单的随机负载均衡策略。

1

注意,所有操作都是在消费端实现。首先自定义一个实现LoadBalance接口的类,比如我给它取名为AutoWeightRandomLoadBalance,即自适应权重负载均衡策略,为什么取这个名字,后面揭晓。

public class AutoWeightRandomLoadBalance implements LoadBalance {
    @Override
    public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException { 
         System.out.println(url.toFullString());
         return invokers.get(0);
    }
}

上一篇还未给大家介绍LoadBalance的几个重要参数。

invokers:可用的所有提供者。属性有:

url:refer url ,类型都是org.apache.dubbo.common.URL。属性有:

invocation:描述目标方法的信息,即远程方法的调用元数据。属性有:

2

为自定义的负载均衡策略取一个名字,并将其注册到Dubbo SPI的描述文件中。在消费者的resources目录下创建目录“/META-INF/dubbo/internal”,并在该目录下创建一个文件,名为org.apache.dubbo.rpc.cluster.LoadBalance。

图片

在文件中注册自定义的负载均衡策略

awrandom=org.apache.dubbo.demo.consumer.AutoWeightRandomLoadBalance

我取名为“awrandom”,记得这个名字,我们还需要配置的。

3

添加配置,声明我们要使用自定义的负载均衡算法awrandom。

图片

三步搞定。为了测试,还需要修改服务提供者代码,让提供者可以在本地启动多个进程,其实就是修改服务提供者暴露服务的端口号,具体代码我就不贴了。看下测试结果。

图片

4

借此机会,我们验证下如何通过元数据获取权重配置。

可以修改服务提供者,也可以修改服务消费者,因为是静态配置。在URL上绑定一个weight参数。

图片

接着修改一下自定义负载均衡算法的实现。

public class AutoWeightRandomLoadBalance implements LoadBalance {
    @Override
    public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException { 
         String weight = url.getParameter("weight");
         System.out.println(weight);
         return invokers.get(0);
    }
}

测试输出结果如下。

图片

5

好了,我们继续实现负载均衡的逻辑。也就是抄袭dubbo提供的随机负载均衡策略的实现。只不过,我们是通过配置中心拿到权重的配置。dubbo随机负载均衡策略的代码,我在上篇文章分析过了哦,可以翻看上篇文章。

public class AutoWeightRandomLoadBalance extends RandomLoadBalance {
    /**
     * 通过配置中心根据invoker的ip拿到权重
     *
     * @param invoker
     * @return
     */
     @Override
     protected int getWeight(Invoker<?> invoker, Invocation invocation) {
        String ip = invoker.getUrl().getIp();
        // 通过ip拿到权重
        int weight = getWeightByIpFromConfigCentre(ip);
        return weight;
     }
}

怎么样,超级简单吧?直接继承自dubbo的随机负载均衡策略,重写获取权重的方法就可以了。实现思路就是通过拿到提供者的ip,去匹配配置中心配置的权重。

# 假设配置中心的配置为
chestnut:
  loadbalance:
    weight: 127.0.0.1:50,127.0.0.2:50

getWeightByIpFromConfigCentre方法的具体实现我也懒得写了。如果你项目中也是用nacos作为配置中心,可以去naocs官网看下spring+naocs实现配置中心的教程。

由消费者自动修改各提供者的权重

最后,我们再改进下,服务部署到另一台机器或者加机器、升级配置就需要去配置中心修改配置多麻烦,如果能实现自动修改权重那多好,这才是自己实现负载均衡策略的意义所在。

到此,我将会抛弃配置中心,转为依赖我为项目所写的服务监控系统,从监控服务根据提供者的ip信息拉取服务提供者所在服务器的硬件配置信息,包括cpu核心数、内存大小、当前CPU的使用率和内存使用率,以此动态调整目标提供者的权重。后台开启一个线程去定时访问监控服务提供的api或者服务节点信息即可。

1

监控服务api响应的服务器信息ServerInfo。

class ServerInfo {
        private String ip;
        // 当前cpu使用率
        private float cpuRate;
        // 当前内存使用率
        private float memoryRate;
        // cpu核心总数
        private int cpuCoreCnt;
        // 单位mb
        private int memory;
 }

2

实现getServerInfoBy方法,模拟调用监控服务api获取到服务器的硬件信息,参数为ip。

private ServerInfo getServerInfoBy(String ip) {
        ServerInfo serverInfo = new ServerInfo();
        serverInfo.setIp(ip);
        serverInfo.setCpuCoreCnt(4);
        serverInfo.setCpuRate(0.2f);
        serverInfo.setMemoryRate(0.6f);
        serverInfo.setMemory(4096);
        return serverInfo;
 }

3

后台开启一个定时任务线程,每分钟或者10分钟、1小时更新一次所有服务提供者的权重,并缓存到内存中。

/**
  * 后台定时更新每个提供者的权重
  *
  * @param invokers
  * @return
 */
private void autoUpdateWeight(List<Invoker<?>> invokers) {
       ServerInfo[] serverInfos = new ServerInfo[invokers.size()];
       // 先获取所有提供者的服务器信息
       int index = 0;
       for (Invoker provider : invokers) {
           String insideIp = provider.getUrl().getIp();
           // 从监控服务获取
           ServerInfo serverInfo = getServerInfoBy(insideIp);
           if (serverInfo == null) {
                return;
           }
           serverInfos[index++] = serverInfo;
        }
        // 按公式计算每个提供者的权重
        // 伪代码:
        for(;;){
         ipWeightMap.put(ip,weight);
        }
   }

如何计算每个服务提供者的权重才是关键。

我临时想到的计算权重的公式是:

权重 = (1 - cpu使用率) * 100 * cpu核心数 + (内存大小 * (1-内存使用率))

可分两段理解:

由于内存大小是以m为单位的,假设机器内存大小是4096,使用率为0.8, 则4096*0.2= 819

cpu核心数为2,使用率为0.30 ,则 0.7 * 100 * 2 = 140

这个公式会导致内存占比较大。为了平衡,可根据内存大小和cpu的比率计算替换公式中的“100”。

按一般ec2的内存和cpu配置:2核4g,4核8g、8核16g 可得cpu与内存的比为 1:2。那么可根据(内存大小 * 13) 计算出一个值替换100。改进后,0.7 * (4096*0.033) * 2 = 1911。当然,这只是demo,暂时想到的方法,如果用于实际项目中,还需要一个更完善的算法。

4

最后还是通过继承dubbo提供的随机负载均衡策略的方式,重写获取权重的方法,简化代码的编写。简直不要太懒……

/**
 * @author wujiuye
 * @version 1.0 on 2019/11/22
 */
public class AutoWeightRandomLoadBalance3 extends RandomLoadBalance {

       // 通过定时任务更新
      private Map<String, Integer> ipWeightMap = new HashMap<>();

      @Override
      protected int getWeight(Invoker<?> invoker, Invocation invocation) {
          return ipWeightMap.get(invoker.getUrl().getIp());
      }
}

至此,一个简陋版的自适应负载均衡策略就实现完毕了。在此案例中,也体现出了我自实现服务监控系统的好处。当然,如果使用自己实现的注册中心也就不需要借助服务监控系统。

#后端

声明:公众号、CSDN、掘金的曾用名:“Java艺术”,因此您可能看到一些早期的文章的图片有“Java艺术”的水印。

文章推荐

线上RPC远程调用频繁超时问题排查,大功臣Arthas

项目不断新增需求,难免不会出现问题,特别是近期新增的增加请求处理耗时的需求。以及一些配置的修改而忽略掉的问题,如dubbo工作线程数调增时,忽略了redis连接池的修改。由于redis可用连接远小于工作线程数,就会出现多个线程竞争redis连接,影响性能。

如何让JedisCluster支持Pipeline

pipeline提升性能的关键,一是RTT,节省往返时间,二是I/O系统调用,read系统调用,需要从用户态,切换到内核态。

Redis性能问题如何排查

并发数上升,到底是哪个服务处理能力到了瓶颈,还是Redis性能到了瓶颈,只有找出是哪里的性能问题,才能对症下药。所以,了解redis的一些运维知识能够帮助我们快速判定是否Redis集群的性能问题。

源码分析Dubbo负载均衡策略的权重如何动态修改

dubbo随机负载均衡的权重很少会用到吗?之前我想给随机负载均衡策略配置权重,各种搜索都找不到答案,包括翻阅官方文档。而且我们项目中用的还是最新的Nacos注册中心,非常无奈,最后只能在源码中寻找答案。

深入理解Dubbo源码,Dubbo与Spring的整合之注解方式分析

dubbo通过BeanFactoryPostProcessor与BeanPostProcessor分别完成ServiceBean的注册与被@Reference注释的属性的依赖注入,通过BeanPostProcessor完成配置文件与相关配置类bean的属性绑定。

Fastjson与Jackson性能问题

可以给出的结论是,jackson在解析大json场景下性能是秒杀fastjson的,不急,我们有图有真相。