转载链接: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);
}
}

配置文件同上,服务名称修改即可。

测试

  1. 启动两个discovery-service,由于端口设置为0,所以是随机端口。

  2. 启动服务调用方

  3. 浏览器访问服务调用方的提供的接口,路径参数需要加上调用的服务名称,例如http://localhost:8080/discovery-service/ribbonService,然后服务调用方使用ribbon的RestTemplate调用服务提供方的接口。

  4. 结果返回: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的使用和实现原理的更多相关文章

  1. 撸一撸Spring Cloud Ribbon的原理-负载均衡器

    在上一篇<撸一撸Spring Cloud Ribbon的原理>中整理发现,RestTemplate内部调用负载均衡拦截器,拦截器内最终是调用了负载均衡器来选择服务实例. 接下来撸一撸负载均 ...

  2. 撸一撸Spring Cloud Ribbon的原理-负载均衡策略

    在前两篇<撸一撸Spring Cloud Ribbon的原理>,<撸一撸Spring Cloud Ribbon的原理-负载均衡器>中,整理了Ribbon如何通过负载均衡拦截器植 ...

  3. Spring Cloud Ribbon说明

    浅谈Spring Cloud Ribbon的原理 Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现.通过Spring Clo ...

  4. SpringCloud微服务实战二:Spring Cloud Ribbon 负载均衡 + Spring Cloud Feign 声明式调用

    1.Spring Cloud Ribbon的作用 Ribbon是Netflix开发的一个负载均衡组件,它在服务体系中起着重要作用,Pivotal将其整合成为Spring Cloud Ribbon,与其 ...

  5. 笔记:Spring Cloud Ribbon 客户端配置详解

    自动化配置 由于 Ribbon 中定义的每一个接口都有多种不同的策略实现,同时这些接口之间又有一定的依赖关系,Spring Cloud Ribbon 中的自动化配置能够很方便的自动化构建接口的具体实现 ...

  6. 笔记:Spring Cloud Ribbon 客户端负载均衡

    Spring Cloud Ribbon 是一个基于 HTTP 和 TCP 的客户端负载均衡工具,基于 Netflix Ribbon 实现,通过Spring Cloud 的封装,可以让我们轻松的将面向服 ...

  7. 为Spring Cloud Ribbon配置请求重试(Camden.SR2+)

    当我们使用Spring Cloud Ribbon实现客户端负载均衡的时候,通常都会利用@LoadBalanced来让RestTemplate具备客户端负载功能,从而实现面向服务名的接口访问. 下面的例 ...

  8. Spring Cloud微服务笔记(四)客户端负载均衡:Spring Cloud Ribbon

    客户端负载均衡:Spring Cloud Ribbon 一.负载均衡概念 负载均衡在系统架构中是一个非常重要,并且是不得不去实施的内容.因为负载均衡对系统的高可用性. 网络压力的缓解和处理能力的扩容的 ...

  9. 基于Spring cloud Ribbon和Eureka实现客户端负载均衡

    前言 本案例将基于Spring cloud Ribbon和Eureka实现客户端负载均衡,其中Ribbon用于实现客户端负载均衡,Eureka主要是用于服务注册及发现: 传统的服务端负载均衡 常见的服 ...

随机推荐

  1. springboot 使用freemarker自定义标签

    1.pom依赖引入 <dependencies> <dependency> <groupId>org.springframework.boot</groupI ...

  2. 从零开始搭建WebAPI Core_SqlSugar管理系统(一) 项目环境需求以及项目搭建

    从零开始搭建WebAPI Core_SqlSugar管理系统(一) 项目环境需求以及项目搭建 环境需求 想要使用.NET Core,首先你的Visual Studio(以下简称vs)升级到较高的版本, ...

  3. [Note] Clipboard.js 使用

    clipboard.js是一个用来设置剪切板的库,小巧无依赖,但用法有点诡异,必须依赖一个DOM元素 据作者说,由于浏览器相关安全策略的缘故,无法使用下面这种方式来设置剪切板 clipboard.co ...

  4. MongoDB 学习笔记之 Nested doc/DBRef (Spark)

    Nested doc/DBRef (Spark) 实现: 运行结果:

  5. SpringBoot2+Netty打造通俗简版RPC通信框架

    2019-07-19:完成基本RPC通信! 2019-07-22:优化此框架,实现单一长连接! 2019-07-24:继续优化此框架:1.增加服务提供注解(带版本号),然后利用Spring框架的在启动 ...

  6. nginx(tengine)访问日志分片

    说明 nginx日志按天分片是运维的基本要求,不仅可以减小文件大小,方便检索关键数据,也可以定时删除过期的日志.可是nginx和tengine默认并不支持文件分片,因此需要额外处理. 另外,日志分片需 ...

  7. Redis之数据类型及命令

    Redis(REmote DIctionary Server) 是一个遵守BSD协议.支持网络.可基于内存亦可持久化的日志型key-value存储系统. KEY 常用指令: 指令 注释 备注 exit ...

  8. .bash_profile does not exist

    localhost:test jerry$ open .bash_profile The file /Users/je/Desktop/test/.bash_profile does not exis ...

  9. C语言--计算数组的平均值

    //数组 int main() { ]; double sum = 0.0; ; int x; scanf("%d", &x); ) { muber[cnt] = x; c ...

  10. Java内存模型总结

    Java内存模型 内存模型可以理解为在特定的操作协议下,对特定的内存或者高速缓存进行读写访问的过程抽象,不同架构下的物理机拥有不一样的内存模型,Java虚拟机也有自己的内存模型,即Java内存模型(J ...