前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Dubbo自适应随机负载均衡策略的实现

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

作者头像
吴就业
发布2020-07-10 11:30:51
1.8K0
发布2020-07-10 11:30:51
举报
文章被收录于专栏:Java艺术Java艺术

关注“Java艺术”一起来充电吧!

写了几篇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。自定义负载均衡策略需要显示声明配置,我将一步步带大家实现一个简单的随机负载均衡策略。

01

注意,所有操作都是在消费端实现。首先自定义一个实现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:可用的所有提供者。属性有: providerUrl,即提供者的url,同refer url,但不是描述注册中心的url,而是描述服务提供者的url 。 invoker: 被层层包装代理的远程调用逻辑 。 url: 当前消费者的url
  • url:refer url ,类型都是org.apache.dubbo.common.URL。属性有: protocol:协议,注册中心的协议,非rpc协议。 username: 用户名 password: 密码 host: 注册中心ip port:注册中心端口号 parameters: 配置参数,比如负载均衡策略、负载均衡权重、超时时间、是否启动时检查提供者可用等
  • invocation:描述目标方法的信息,即远程方法的调用元数据。属性有: methodName:方法名称 parameterTypes:参数类型 arguments:参数值 returnType:返回值类型

02

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

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

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

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

03

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

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

04

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

可以修改服务提供者,也可以修改服务消费者,因为是静态配置。在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);
    }
}

测试输出结果如下。

05

好了,我们继续实现负载均衡的逻辑。也就是抄袭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或者服务节点信息即可。

01

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

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

02

实现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;
 }

03

后台开启一个定时任务线程,每分钟或者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-内存使用率))

可分两段理解:

第一段:(1 - cpu使用率) * 100 * cpu核心数 ,即cpu核心数越高、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。那么可根据(内存大小 * 1/3) 计算出一个值替换100。改进后,0.7 * (4096*0.033) * 2 = 1911。当然,这只是demo,暂时想到的方法,如果用于实际项目中,还需要一个更完善的算法。

04

最后还是通过继承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());
      }
}

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

笔者有话要说

我希望我能为公司贡献更多有意义的技术沉淀,工具也好、组件、中间件、框架都行。我特别喜欢沉浸在设计、编码实现和维护中间件的世界里,非常纯粹的技术。这可能也跟我喜欢阅读开源框架源码有关。

期待下篇基于Netty自实现Dubbo注册中心吗?关注我,你会看到更多精彩的故事哦!也可能是事故…

上次有朋友问我,关于多数据源使用动态切换的实现方式好,还是静态通过配置隔离的方式好。说实话,我也讲不出到底哪个方案好。

我给的建议是,如果是新项目,我建议是直接使用动态数据源,而如果项目已经开发完且系统庞大复杂,添加数据源最好是通过静态配置隔离,这样兼容性更好,影响也会更小。特别是在没有预演测试环境的情况下,保险起见。此建议不考虑分库及使用分库分表的中间件,单纯不同数据库类型的多数据源。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-11-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java艺术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
负载均衡
负载均衡(Cloud Load Balancer,CLB)提供安全快捷的流量分发服务,访问流量经由 CLB 可以自动分配到云中的多台后端服务器上,扩展系统的服务能力并消除单点故障。负载均衡支持亿级连接和千万级并发,可轻松应对大流量访问,满足业务需求。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档