spring cloud Ribbon的使用和实现原理
转载链接:https://blog.csdn.net/qq_20597727/article/details/82860521
简介
这篇文章主要介绍一下ribbon在程序中的基本使用,在这里是单独拿出来写用例测试的,实际生产一般是配置feign一起使用,更加方便开发。同时这里也通过源码来简单分析一下ribbon的基本实现原理。
基本使用
这里使用基于zookeeper注册中心+ribbon的方式实现一个简单的客户端负载均衡案例。
服务提供方
首先是一个服务提供方。代码如下。
application.properties配置文件
- spring.application.name=discovery-service
- server.port=0
- service-B.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
bootstrap.properties配置文件
- spring.cloud.zookeeper.connect-string=192.168.0.15:2181
引导程序,提供了一个ribbonService的rest接口服务,注册程序到zookeeper中。
- @SpringBootApplication
- @EnableDiscoveryClient
- @RestController
- public class DiscoverClient {
- public static void main(String[] args) {
- SpringApplication.run(DiscoverClient.class, args);
- }
-
- @RequestMapping("/ribbonService")
- public String ribbonService(){
- return "hello too ribbon";
- }
- }
服务调用方
服务调用方就是进行负载均衡的一方,利用ribbo的RestTemplate进行负载调用服务。
RibbonConfig,配置ribbon的RestTemplate,通过@LoadBalanced注解实现,具体原理稍后分析。
- @Configuration
- public class RibbonConfig {
-
- /**
- * 实例化ribbon使用的RestTemplate
- * @return
- */
- @Bean
- @LoadBalanced
- public RestTemplate rebbionRestTemplate(){
- return new RestTemplate();
- }
- /**
- * 配置随机负载策略,需要配置属性service-B.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
- */
- @Bean
- public IRule ribbonRule() {
- return new RandomRule();
- }
- }
-
引导程序
- @SpringBootApplication(scanBasePackages = "garine.learn.ribbon.loadblance")
- @EnableDiscoveryClient
- @RestController
- public class TestRibbonApplocation {
- public static void main(String[] args) {
- SpringApplication.run(TestRibbonApplocation.class, args);
- }
-
- @Autowired
- @LoadBalanced
- RestTemplate restTemplate;
-
- @GetMapping("/{applicationName}/ribbonService")
- public String ribbonService(@PathVariable("applicationName") String applicationName){
- return restTemplate.getForObject("http://" + applicationName+"/ribbonService", String.class);
- }
- }
配置文件同上,服务名称修改即可。
测试
启动两个discovery-service,由于端口设置为0,所以是随机端口。
启动服务调用方
浏览器访问服务调用方的提供的接口,路径参数需要加上调用的服务名称,例如http://localhost:8080/discovery-service/ribbonService,然后服务调用方使用ribbon的RestTemplate调用服务提供方的接口。
结果返回:hello too ribbon ,同时服务提供方启动的两个服务都可能被调用,取决于怎么配置负载策略。
上面就是一个简单使用ribbon的例子,结合feign使用基本上是做类似上面所写的工作,那么ribbon到底是怎么实现的呢?
原理与源码分析
ribbon实现的关键点是为ribbon定制的RestTemplate,ribbon利用了RestTemplate的拦截器机制,在拦截器中实现ribbon的负载均衡。负载均衡的基本实现就是利用applicationName从服务注册中心获取可用的服务地址列表,然后通过一定算法负载,决定使用哪一个服务地址来进行http调用。
Ribbon的RestTemplate
RestTemplate中有一个属性是List<ClientHttpRequestInterceptor> interceptors
,如果interceptors里面的拦截器数据不为空,在RestTemplate进行http请求时,这个请求就会被拦截器拦截进行,拦截器实现接口ClientHttpRequestInterceptor
,需要实现方法是
- ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
- throws IOException;
也就是说拦截器需要完成http请求,并封装一个标准的response返回。
ribbon中的拦截器
在Ribbon 中也定义了这样的一个拦截器,并且注入到RestTemplate中,是怎么实现的呢?
在Ribbon实现中,定义了一个LoadBalancerInterceptor
,具体的逻辑先不说,ribbon就是通过这个拦截器进行拦截请求,然后实现负载均衡调用。
拦截器定义在org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration.LoadBalancerInterceptorConfig#ribbonInterceptor
- @Configuration
- @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
- static class LoadBalancerInterceptorConfig {
- @Bean
- //定义ribbon的拦截器
- public LoadBalancerInterceptor ribbonInterceptor(
- LoadBalancerClient loadBalancerClient,
- LoadBalancerRequestFactory requestFactory) {
- return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
- }
-
- @Bean
- @ConditionalOnMissingBean
- //定义注入器,用来将拦截器注入到RestTemplate中,跟上面配套使用
- public RestTemplateCustomizer restTemplateCustomizer(
- final LoadBalancerInterceptor loadBalancerInterceptor) {
- return restTemplate -> {
- List<ClientHttpRequestInterceptor> list = new ArrayList<>(
- restTemplate.getInterceptors());
- list.add(loadBalancerInterceptor);
- restTemplate.setInterceptors(list);
- };
- }
- }
ribbon中的拦截器注入到RestTemplate
定义了拦截器,自然需要把拦截器注入到、RestTemplate才能生效,那么ribbon中是如何实现的?上面说了拦截器的定义与拦截器注入器的定义,那么肯定会有个地方使用注入器来注入拦截器的。
在org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration#loadBalancedRestTemplateInitializerDeprecated方法里面,进行注入,代码如下。
- @Configuration
- @ConditionalOnClass(RestTemplate.class)
- @ConditionalOnBean(LoadBalancerClient.class)
- @EnableConfigurationProperties(LoadBalancerRetryProperties.class)
- public class LoadBalancerAutoConfiguration {
-
- @LoadBalanced
- @Autowired(required = false)
- private List<RestTemplate> restTemplates = Collections.emptyList();
-
- @Bean
- public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
- final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
- //遍历context中的注入器,调用注入方法。
- return () -> restTemplateCustomizers.ifAvailable(customizers -> {
- for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
- for (RestTemplateCustomizer customizer : customizers) {
- customizer.customize(restTemplate);
- }
- }
- });
- }
- //......
- }
遍历context中的注入器,调用注入方法,为目标RestTemplate注入拦截器,注入器和拦截器都是我们定义好的。
还有关键的一点是:需要注入拦截器的目标restTemplates到底是哪一些?因为RestTemplate实例在context中可能存在多个,不可能所有的都注入拦截器,这里就是@LoadBalanced注解发挥作用的时候了。
LoadBalanced注解
严格上来说,这个注解是spring cloud实现的,不是ribbon中的,它的作用是在依赖注入时,只注入实例化时被@LoadBalanced修饰的实例。
例如我们定义Ribbon的RestTemplate的时候是这样的
- @Bean
- @LoadBalanced
- public RestTemplate rebbionRestTemplate(){
- return new RestTemplate();
- }
因此才能为我们定义的RestTemplate注入拦截器。
那么@LoadBalanced是如何实现这个功能的呢?其实都是spring的原生操作,@LoadBalance的源码如下
- /**
- * Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient
- * @author Spencer Gibb
- */
- @Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- @Inherited
- @Qualifier
- public @interface LoadBalanced {
- }
很明显,‘继承’了注解@Qualifier,我们都知道以前在xml定义bean的时候,就是用Qualifier来指定想要依赖某些特征的实例,这里的注解就是类似的实现,restTemplates通过@Autowired注入,同时被@LoadBalanced修饰,所以只会注入@LoadBalanced修饰的RestTemplate,也就是我们的目标RestTemplate。
拦截器逻辑实现
LoadBalancerInterceptor源码如下。
- public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
-
- private LoadBalancerClient loadBalancer;
- private LoadBalancerRequestFactory requestFactory;
-
- public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) {
- this.loadBalancer = loadBalancer;
- this.requestFactory = requestFactory;
- }
-
- public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
- // for backwards compatibility
- this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
- }
-
- @Override
- public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
- final ClientHttpRequestExecution execution) throws IOException {
- final URI originalUri = request.getURI();
- String serviceName = originalUri.getHost();
- Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
- return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
- }
- }
拦截请求执行
- @Override
- public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
- ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
- //在这里负载均衡选择服务
- Server server = getServer(loadBalancer);
- if (server == null) {
- throw new IllegalStateException("No instances available for " + serviceId);
- }
- RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,
- serviceId), serverIntrospector(serviceId).getMetadata(server));
- //执行请求逻辑
- return execute(serviceId, ribbonServer, request);
- }
我们重点看getServer方法,看看是如何选择服务的
- protected Server getServer(ILoadBalancer loadBalancer) {
- if (loadBalancer == null) {
- return null;
- }
- //
- return loadBalancer.chooseServer("default"); // TODO: better handling of key
- }
代码配置随机loadBlancer,进入下面代码
- public Server chooseServer(Object key) {
- if (counter == null) {
- counter = createCounter();
- }
- counter.increment();
- if (rule == null) {
- return null;
- } else {
- try {
- //使用配置对应负载规则选择服务
- return rule.choose(key);
- } catch (Exception e) {
- logger.warn("LoadBalancer [{}]: Error choosing server for key {}", name, key, e);
- return null;
- }
- }
- }
这里配置的是RandomRule,所以进入RandomRule代码
- public Server choose(ILoadBalancer lb, Object key) {
- if (lb == null) {
- return null;
- }
- Server server = null;
-
- while (server == null) {
- if (Thread.interrupted()) {
- return null;
- }
- //获取可用服务列表
- List<Server> upList = lb.getReachableServers();
- List<Server> allList = lb.getAllServers();
-
- //随机一个数
- int serverCount = allList.size();
- if (serverCount == 0) {
- /*
- * No servers. End regardless of pass, because subsequent passes
- * only get more restrictive.
- */
- return null;
- }
-
- int index = rand.nextInt(serverCount);
- server = upList.get(index);
-
- if (server == null) {
- /*
- * The only time this should happen is if the server list were
- * somehow trimmed. This is a transient condition. Retry after
- * yielding.
- */
- Thread.yield();
- continue;
- }
-
- if (server.isAlive()) {
- return (server);
- }
-
- // Shouldn't actually happen.. but must be transient or a bug.
- server = null;
- Thread.yield();
- }
-
- return server;
-
- }
随机负载规则很简单,随机整数选择服务,最终达到随机负载均衡。我们可以配置不同的Rule来实现不同的负载方式。
spring cloud Ribbon的使用和实现原理的更多相关文章
- 撸一撸Spring Cloud Ribbon的原理-负载均衡器
在上一篇<撸一撸Spring Cloud Ribbon的原理>中整理发现,RestTemplate内部调用负载均衡拦截器,拦截器内最终是调用了负载均衡器来选择服务实例. 接下来撸一撸负载均 ...
- 撸一撸Spring Cloud Ribbon的原理-负载均衡策略
在前两篇<撸一撸Spring Cloud Ribbon的原理>,<撸一撸Spring Cloud Ribbon的原理-负载均衡器>中,整理了Ribbon如何通过负载均衡拦截器植 ...
- Spring Cloud Ribbon说明
浅谈Spring Cloud Ribbon的原理 Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现.通过Spring Clo ...
- SpringCloud微服务实战二:Spring Cloud Ribbon 负载均衡 + Spring Cloud Feign 声明式调用
1.Spring Cloud Ribbon的作用 Ribbon是Netflix开发的一个负载均衡组件,它在服务体系中起着重要作用,Pivotal将其整合成为Spring Cloud Ribbon,与其 ...
- 笔记:Spring Cloud Ribbon 客户端配置详解
自动化配置 由于 Ribbon 中定义的每一个接口都有多种不同的策略实现,同时这些接口之间又有一定的依赖关系,Spring Cloud Ribbon 中的自动化配置能够很方便的自动化构建接口的具体实现 ...
- 笔记:Spring Cloud Ribbon 客户端负载均衡
Spring Cloud Ribbon 是一个基于 HTTP 和 TCP 的客户端负载均衡工具,基于 Netflix Ribbon 实现,通过Spring Cloud 的封装,可以让我们轻松的将面向服 ...
- 为Spring Cloud Ribbon配置请求重试(Camden.SR2+)
当我们使用Spring Cloud Ribbon实现客户端负载均衡的时候,通常都会利用@LoadBalanced来让RestTemplate具备客户端负载功能,从而实现面向服务名的接口访问. 下面的例 ...
- Spring Cloud微服务笔记(四)客户端负载均衡:Spring Cloud Ribbon
客户端负载均衡:Spring Cloud Ribbon 一.负载均衡概念 负载均衡在系统架构中是一个非常重要,并且是不得不去实施的内容.因为负载均衡对系统的高可用性. 网络压力的缓解和处理能力的扩容的 ...
- 基于Spring cloud Ribbon和Eureka实现客户端负载均衡
前言 本案例将基于Spring cloud Ribbon和Eureka实现客户端负载均衡,其中Ribbon用于实现客户端负载均衡,Eureka主要是用于服务注册及发现: 传统的服务端负载均衡 常见的服 ...
随机推荐
- ZK集群的Leader选举源码阅读
前言 ZooKeeper对Zab协议的实现有自己的主备模型,即Leader和learner(Observer + Follower),有如下几种情况需要进行领导者的选举工作 情形1: 集群在启动的过程 ...
- php常用操作(第二版)
1.多个字段多重排序 function sortArrByManyField(){ $args = func_get_args(); // 获取函数的参数的数组 if(empty($args)){ r ...
- 在 Vue-cli 创建的项目中引入 Element-UI
Element-UI 是饿了么前端团队退出了一套基于 vue.js 开发的 UI 组件库,在与 Vue-cli 创建的项目结合时,需要做以下配置: 1. 安装 loader 模块 cnpm insta ...
- Go语言基础之net/http
Go语言基础之net/http 2017年6月26日 Go语言内置的net/http包十分的优秀,提供了HTTP客户端和服务端的实现. net/http介绍 Go语言内置的net/http包提供了HT ...
- MySQL 深入理解索引B+树存储 (转载))
出处:http://blog.codinglabs.org/articles/theory-of-mysql-index.html 摘要 本文以MySQL数据库为研究对象,讨论与数据库索引相关的一 ...
- [Note] Windows 10 Python 3.6.4 安装scrapy
直接使用pip install安装时会在安装Twisted出错,以下主要是解决Twisted的安装问题 1. 安装wheel pip install wheel 2. 安装Twisted 在Pytho ...
- 通俗易懂spring之singleton和prototype
关于spring bean作用域,基于不同的容器,会有所不同,如BeanFactory和ApplicationContext容器就有所不同,在本篇文章,主要讲解基于ApplicationContext ...
- WebGL简易教程(十):光照
目录 1. 概述 2. 原理 2.1. 光源类型 2.2. 反射类型 2.2.1. 环境反射(enviroment/ambient reflection) 2.2.2. 漫反射(diffuse ref ...
- Vue中使用key的作用
key的作用是为了在diff算法执行时更快的找到对应的节点,提高diff速度 key具有唯一性 vue中循环需加 :key=“唯一标识” ,唯一标识可以使item里面id index 等,因为vue组 ...
- 直通BAT面试题库锦集
01 python面试题(汇总) 02 面向对象 03 网络和并发编程 04 模块 05 设计模式 06 前端 07 Django框架 08 Flask 09 tornado 10 DB