如何使用原生的Ribbon

什么是Ribbon

之前分析了如何使用原生的Feign,今天我们来研究 Netflix 团队开发的另外一个类库–Ribbon。
Ribbon 和 Feign 有很多相似的地方,首先,它们本质上都是 HTTP client,其次,它们都具备重试、集成断路器等功能。最大的区别在于,Ribbon 内置了一个负载均衡器,而 Feign 没有。

本文将介绍如何使用原生的 Ribbon,注意是原生的,而不是被 Spring 层层封装的 Ribbon。

为什么要使用Ribbon

这里我们需要回答两个问题:

  1. 为什么要使用 HTTP client?
  2. 为什么要在 HTTP client 里内置负载均衡器?

其中,第一个问题在如何使用原生的Feign中已经讲过,这里就不啰嗦了,我们直接看第二个问题。

我们知道,Apache HTTP client、Feign 并没有内置负载均衡器,也就是说,HTTP client 并不一定要内置负载均衡器,那为什么 Ribbon 要搞特殊呢?

其实,我们可以想想, Ribbon 更多地被用在内部调用,而这种场景有一个比较大的特点–目标服务为集群部署。通常情况下,在调用目标服务时,我们希望请求尽可能平均地分发到每个实例。通过内置的负载均衡器,Ribbon 可以很好地满足要求,而 Apache HTTP client、Feign 就无法做到。

所以,在 HTTP client 里内置负载均衡器是为了能够在目标服务为集群部署时提供负载均衡支持。

如何使用原生的Ribbon

有的人可能会说,你单独部署一台负载均衡器就行了嘛,搞那么复杂干嘛。当然,你可以这么做。但是你要考虑很重要的一点,mid-tier services 的请求量要远大于 edge services,所以你需要一台性能极高的负载均衡器。从这个角度来说,Ribbon 的方案帮你省下了独立部署负载均衡器的开销。

如何使用原生的Ribbon

如何使用Ribbon

项目中我用 RxNettty 写了一个简单的 HTTP 接口(见 cn.zzs.ribbon.RxUserServer)供后面的例子调用,这个接口运行在本机的 8080、8081、8082 接口,用来模拟三台不同的实例。所以,如果你想要测试项目中的例子,要先把这三台实例先启动好。

http://127.0.0.1:8080/user/getUserById?userId={userId}
request:userId=1
response:User [id=1, name=zzs001, age=18]

这里提醒一下,Ribbon 的 API 用到了很多 RxJava 代码,如果之前没接触过,最好先了解下。

项目环境

os:win 10

jdk:1.8.0_231

maven:3.6.3

IDE:Spring Tool Suite 4.6.1.RELEASE

Ribbon:2.7.17

作为HTTP client的用法

和 Feign 一样,Ribbon 支持使用注解方式定义 HTTP 接口,除此之外,Ribbon 还支持使用 HttpRequestTemplateHttpClientRequest等方式定义,这部分的例子我也提供了,感兴趣可以移步项目源码。

服务实例的列表通过 ConfigurationManager设置。当你看到 ConfigurationManager时,会不会觉得很熟悉呢?我们之前在Eureka详解系列(三)–探索Eureka强大的配置体系中详细介绍过,没错,Ribbon 用的还是这套配置体系。需要强调下,Netflix 团队开发的这套配置体系提供了动态配置支持(当然,你要会用才行),就拿本项目为例,如果运行过程中你通过 ConfigurationManager更改了 listOfServers,那么,Ribbon 会感知到这种变化,使用最新的 listOfServers 来负载均衡,只是会有一点点延迟。

// 使用注解定义HTTP API
@ClientProperties(properties = {
        @Property(name="ReadTimeout", value="2000"),
        @Property(name="ConnectTimeout", value="1000"),
        @Property(name="MaxAutoRetries", value="1"),
        @Property(name="MaxAutoRetriesNextServer", value="2")
}, exportToArchaius = true)
interface UserService {
    @TemplateName("getUserById")
    @Http(
            method = HttpMethod.GET,
            uri = "/user/getUserById?userId={userId}",
            headers = {
                    @Header(name = "X-Platform-Version", value = "xyz"),
                    @Header(name = "X-Auth-Token", value = "abc")
            })
    RibbonRequest getUserById(@Var("userId") String userId);
}

public class RxUserProxyTest {
    @Test
    public void testBase() throws InterruptedException {
        // 指定服务实例的地址
        // key:服务+".ribbon."+配置项名称(见com.netflix.client.config.CommonClientConfigKey)
        ConfigurationManager.getConfigInstance().setProperty(
                "UserService.ribbon.listOfServers", "127.0.0.1:8080,127.0.0.1:8081,127.0.0.1:8082");

        UserService userService = Ribbon.from(UserService.class);

        userService.getUserById("1")
            .toObservable()
            .subscribe(new Subscriber() {
                @Override
                public void onCompleted() {
                    LOG.info("onCompleted");
                }

                @Override
                public void onError(Throwable e) {
                    e.printStackTrace();
                }

                @Override
                public void onNext(Object t) {
                    LOG.info("onNext:{}", t);
                    if(t != null && t instanceof ByteBuf) {
                        LOG.info(ByteBuf.class.cast(t).toString(Charset.defaultCharset()));
                    }
                }
            });
        // 因为请求HTTP接口是异步的,这里要让测试主线程先睡一会
        Thread.sleep(10000);
    }
}

默认的负载均衡规则

为了观察多次请求在三台实例的分配情况,现在我们更改下代码,试着发起 6 次请求。

    @Test
    public void test01() throws InterruptedException {
        ConfigurationManager.getConfigInstance().setProperty(
                "UserService.ribbon.listOfServers", "127.0.0.1:8080,127.0.0.1:8081,127.0.0.1:8082");

        UserService userService = Ribbon.from(UserService.class);
        // 发起多次请求
        Observable[] requestList = new Observable[]{
                userService.getUserById("1").toObservable(),
                userService.getUserById("2").toObservable(),
                userService.getUserById("3").toObservable(),
                userService.getUserById("4").toObservable(),
                userService.getUserById("5").toObservable(),
                userService.getUserById("6").toObservable()
        };
        Observable.concat(Observable.from(requestList))
                .subscribe(subscriber);
        Thread.sleep(10000);
    }

运行测试,可以看到,6 次请求被平均地分配到了 3 台实例。

如何使用原生的Ribbon

在日志中,可以看到了默认的负载均衡规则。

如何使用原生的Ribbon

通过源码可以看到,这个默认的规则本质上采用的是轮询策略 RoundRobinRule。除此之外,Ribbon 还定义了 RandomRuleRetryRule等规则供我们选择。

public class AvailabilityFilteringRule {
    RoundRobinRule roundRobinRule = new RoundRobinRule();
}

自定义负载均衡规则

自定义负载均衡规则需要继承 com.netflix.loadbalancer.AbstractLoadBalancerRule,并实现 choose 方法。这里我定义的规则是:不管有多少实例,默认访问第一台。

public class MyLoadBalancerRule extends AbstractLoadBalancerRule {
    @Override
    public Server choose(Object key) {

        ILoadBalancer lb = getLoadBalancer();

        List allServers = lb.getAllServers();

        return allServers.stream().findFirst().orElse(null);
    }
}

接着,只需要通过 ConfigurationManager配置自定义规则就行。

    @Test
    public void test01() throws InterruptedException {
        ConfigurationManager.getConfigInstance().setProperty(
                "UserService.ribbon.listOfServers", "127.0.0.1:8080,127.0.0.1:8081,127.0.0.1:8082");
        // 配置自定义规则
        ConfigurationManager.getConfigInstance().setProperty(
                "UserService.ribbon.NFLoadBalancerRuleClassName", "cn.zzs.ribbon.MyLoadBalancerRule");

        UserService userService = Ribbon.from(UserService.class);

        Observable[] requestList = new Observable[]{
                userService.getUserById("1").toObservable(),
                userService.getUserById("2").toObservable(),
                userService.getUserById("3").toObservable(),
                userService.getUserById("1").toObservable(),
                userService.getUserById("2").toObservable(),
                userService.getUserById("3").toObservable()
        };
        Observable.concat(Observable.from(requestList))
                .subscribe(subscriber);
        Thread.sleep(10000);
    }

运行测试,可以看到,所有请求都被分配到了第一台实例。自定义负载均衡规则生效。

如何使用原生的Ribbon

动态刷新listOfServers

在前面的例子中,我们的服务实例地址 listOfServers 都是写死的,然而,在实际项目中,目标服务的实例数量、地址都是变化的,所以,我们需要动态地更新 listOfServers,而不能写死。

上面说过 Netflix 开发的这套配置体系是支持动态配置的,所以,我能想到的最简方案就是,开一个任务,定时地从注册中心等地方拉取最新的实例列表,再把这个新的列表塞进 ConfigurationManager就行了。

针对以 eureka server 为注册中心的项目,官方提供了一个实现方案。对比我上面说的方案,这个方案逻辑虽简单,但实现更复杂,源码可读性挺高的,感兴趣的朋友可以看看。

    @Test
    public void testEureka() throws InterruptedException {
        // 指定实例列表从eureka获取
        ConfigurationManager.getConfigInstance().setProperty(
                "UserService.ribbon.NIWSServerListClassName", "com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList");
        ConfigurationManager.getConfigInstance().setProperty(
                "UserService.ribbon.DeploymentContextBasedVipAddresses", "UserService");

        // 初始化EurekaClient
        DiscoveryManager.getInstance().initComponent(new MyDataCenterInstanceConfig(), new DefaultEurekaClientConfig());

        UserService userService = Ribbon.from(UserService.class);
        userService.getUserById("1")
            .toObservable()
            .subscribe(subscriber);

        Thread.sleep(10000);
    }
}

结语

以上,基本讲完 Ribbon 的使用方法,其实 Ribbon 还有其他可以扩展的东西,例如,断路器、重试等等。感兴趣的话,可以自行分析。

最后,感谢阅读。

参考资料

ribbon github

相关源码请移步:https://github.com/ZhangZiSheng001/ribbon-demo

本文为原创文章,转载请附上原文出处链接:https://www.cnblogs.com/ZhangZiSheng001/p/15484505.html

Original: https://www.cnblogs.com/ZhangZiSheng001/p/15484505.html
Author: 子月生
Title: 如何使用原生的Ribbon

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/575780/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • MVCC – Read View的可见性判断理解

    读了 @SnailMann大佬【MySQL笔记】正确的理解MySQL的MVCC及实现原理 收益颇丰,非常感谢! 但对其中如何判断事务是否可见性还是不太理解,于是作了本文,在原博客基…

    数据库 2023年5月24日
    092
  • centos系统下mysql的配置

    配置文件路径 /etc/my.cnf Hole yor life get everything if you never give up. Original: https://ww…

    数据库 2023年6月9日
    072
  • MySQL并行复制(MTS)原理(完整版)

    MySQL 5.6并行复制架构 MySQL 5.7并行复制原理 Master 组提交(group commit) 支持并行复制的GTID slave LOGICAL_CLOCK(由…

    数据库 2023年5月24日
    0108
  • 探究MySQL中SQL查询的成本

    成本 什么是成本,即SQL进行查询的花费的时间成本,包含IO成本和CPU成本。 IO成本:即将数据页从硬盘中读取到内存中的读取时间成本。通常1页就是1.0的成本。 CPU成本:即是…

    数据库 2023年5月24日
    0114
  • 14 在 Java 中,如何跳出当前的多重嵌套循环

    在最外层添加一个标记如A,然后用breakA,即可跳出多重循环 关键字break 使用范围:switch-case,循环结构中 break在循环结构中的作用:结束 当前循环 bre…

    数据库 2023年6月6日
    093
  • 通过Python收集汇聚MySQL 表信息

    一.需求 统计收集各个实例上table的信息,主要是表的记录数及大小。 收集的范围是cmdb中所有的数据库实例。 二.公共基础文件说明 1.配置文件 配置文为db_servers_…

    数据库 2023年6月16日
    0120
  • ES6中的模块化

    历史上,JavaScript一直没有自己模块体系(module),无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。其他语言如java、python等都具备这项功能,…

    数据库 2023年6月6日
    083
  • MySQL实战45讲 15

    15 | 答疑文章(一):日志和索引相关问题 日志相关 binlog(归档日志)和redo log(重做日志)配合崩溃恢复,在两阶段提交的不同瞬间,MySQL如果发生异常重启,是怎…

    数据库 2023年6月16日
    0136
  • 达梦数据库_DM8配置MPP主备

    为了提高MPP系统可靠性,克服由于单节点故障导致整个系统不能继续正常工作,DM 在普通的MPP系统基础上,引入主备守护机制,将MPP节点作为主库节点,增加备库作为备份节点,必要时可…

    数据库 2023年6月11日
    099
  • MySQL的undo日志—MVCC前置知识

    undo日志 前面学习了redo日志,redo日志保证的是崩溃时事务持久性。我们可以从redo日志恢复到系统崩溃以前。 undo日志就是为了保证事务回滚时事务所作所为都能回到事务执…

    数据库 2023年5月24日
    0108
  • MySQL中如何选择合适的备份策略和备份工具

    ​数据库备份的重要性毋庸置疑,可以说,它是数据安全的最后一道防线。鉴于此,对于备份,我们通常会做以下要求: 多地部署 对于核心数据库,我们通常有两地三中心的部署要求。对于备份来说,…

    数据库 2023年6月11日
    082
  • SQLZOO练习7–Using NULL

    teacher表: iddeptnamephonemobile 101 1 Shrivell 2753 07986 555 1234 102 1 Throd 2754 07122 …

    数据库 2023年6月16日
    068
  • 1_requests基础用法

    requests 模块的基本使用 什么是requests 模块? Python 中封装好的一个基于网络请求的模块 requests 模块的作用? 用来模拟浏览器发请求 reques…

    数据库 2023年6月11日
    080
  • 员工离职困扰?来看AI如何解决,基于人力资源分析的 ML 模型构建全方案 ⛵

    💡 作者:韩信子@ShowMeAI📘 数据分析实战系列:https://www.showmeai.tech/tutorials/40📘 机器学习实战系列:https://www.s…

    数据库 2023年6月14日
    094
  • 视频语义分割基准数据集与评估方法

    概述 本文来源于《A Benchmark Dataset and Evaluation Methodology for Video Object Segmentation》,论文主…

    数据库 2023年6月11日
    083
  • 线程池执行流程图

    public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeU…

    数据库 2023年6月16日
    0132
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球