Spring Cloud Ribbon主要用于客户端的负载均衡。最基本的用法便是使用RestTemplate进行动态的负载均衡。我们只需要加入如下的配置便能完成客户端的负载均衡。

@Configuration
public class RestConfiguration {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
/**
* 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 {
}

这里的@LoadBalanced使得RestTemplate可以使用LoadBalancerClient,LoadBalancerClient在这个路劲下,还存在着LoadBalancerAutoConfiguration这个配置类只要用于对LoadBalancerClient做出配置。我们就以此为入口,开始分析ribbon

LoadBalancer的自动化配置


@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 loadBalancedRestTemplateInitializer(
final List<RestTemplateCustomizer> customizers) {
return new SmartInitializingSingleton() {
@Override
public void afterSingletonsInstantiated() {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
}
};
} @Autowired(required = false)
private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList(); @Bean
@ConditionalOnMissingBean
public LoadBalancerRequestFactory loadBalancerRequestFactory(
LoadBalancerClient loadBalancerClient) {
return new LoadBalancerRequestFactory(loadBalancerClient, transformers);
} @Configuration
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {
@Bean
public LoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
} @Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return new RestTemplateCustomizer() {
@Override
public void customize(RestTemplate restTemplate) {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
}
};
}
}
}

这个配置类主要做了以下几件事

1.创建了一个LoadBalancerInterceptor的bean,用于实现对客户端发起请求时进行拦截,以实现客户端负载均衡。

2.创建了一个RestTemplateCustomizer的bean,用于给RestTemplate增加LoadBalancerInterceptor拦截器。

3.维护一个@LoadBalanced注解修饰的RestTemplate对象列表,并在这里进行维护,通过调用RestTemplateCustomizer的实例来给需要的客户端负载均衡的RestTemplate增加LoadBalancerInterceptor拦截器。

现在我们看下 LoadBalancerInterceptor 的拦截,我们在这里打上断点,查看一个 RestTemplate 请求是怎么被拦截的。

我们在 org.springframework.http.client.InterceptingClientHttpRequest.InterceptingRequestExecution#execute 发现了如下代码

if (this.iterator.hasNext()) {
ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();
return nextInterceptor.intercept(request, body, this);
}

这里和mybatis比较类似,都是通过责任链模式,一层层的拦截。最终就会到LoadBalancerInterceptor。现在再看LoadBalancerInterceptor的intercept方法

前两步是分别获取这个request的请求地址和ServiceName,这里截图供参考

最开始我们说过,

@LoadBalanced使得RestTemplate可以使用LoadBalancerClient,就是在这里使用LoadBalancerClient的executor方法,做出具体的负载均衡。由于这里的LoadBalancerClient是一个接口,他具体的实现类是RibbonLoadBalancerClient,我们在这里分析具体的execute方法

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);
}

第一步获取 loadBalancer,loadBalancer是定义软件负载均衡器操作的接口,有以下方法。默认配置的是使用 ZoneAwareLoadBalancer 。

public interface ILoadBalancer {

    //向负载均衡器中维护的实例列表增加服务实例
public void addServers(List<Server> newServers); //从负载均衡器中挑选出一个具体的服务实例
public Server chooseServer(Object key); //用来通知和标记负载均衡器中的某个具体实例已经停止服务,不然负载均衡器在下一次获取服务实例清单前都会认为服务实例均是正常服务的。
public void markServerDown(Server server);
//获取当前正常服务的实例列表
public List<Server> getReachableServers(); //获取所有已知的服务实例列表,包括正常服务和停止服务实例。
public List<Server> getAllServers();
}

第二步是根据一定的算法去获取server,这一步也是整个ribbon的核心,关于具体的算法逻辑,我们后面分析

protected Server getServer(ILoadBalancer loadBalancer) {
if (loadBalancer == null) {
return null;
}
return loadBalancer.chooseServer("default"); // TODO: better handling of key
}

第三步 在获取到Server后包装成一个 RibbonServer

一个具体的server被选出来后,便可以接着请求,这时会将剩下的拦截器走完

public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException {
Server server = null;
if(serviceInstance instanceof RibbonServer) {
server = ((RibbonServer)serviceInstance).getServer();
}
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
}
RibbonLoadBalancerContext context = this.clientFactory
.getLoadBalancerContext(serviceId);
RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);
    try {
T returnVal = request.apply(serviceInstance);
statsRecorder.recordStats(returnVal);
return returnVal;
}return null;
}

最终,创建出一个具体的请求并执行。

public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
if (this.iterator.hasNext()) {
ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();
return nextInterceptor.intercept(request, body, this);
}
else {
ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), request.getMethod());
for (Map.Entry<String, List<String>> entry : request.getHeaders().entrySet()) {
List<String> values = entry.getValue();
for (String value : values) {
delegate.getHeaders().add(entry.getKey(), value);
}
}
if (body.length > 0) {
StreamUtils.copy(body, delegate.getBody());
}
return delegate.execute();
}
}

以上就是对ribbon大致流程的分析

SpringCloud Ribbon的分析的更多相关文章

  1. SpringCloud Ribbon的分析(二)

    上文我们分析到 loadBalancer 根据具体的算法选择相应的server. protected Server getServer(ILoadBalancer loadBalancer) { if ...

  2. SpringCloud Feign的分析

    Feign是一个声明式的Web Service客户端,它使得编写Web Serivce客户端变得更加简单.我们只需要使用Feign来创建一个接口并用注解来配置它既可完成. @FeignClient(v ...

  3. Spring Cloud之负载均衡组件Ribbon原理分析

    目录 前言 一个问题引发的思考 Ribbon的简单使用 Ribbon 原理分析 @LoadBalanced 注解 @Qualifier注解 LoadBalancerAutoConfiguration ...

  4. springcloud Ribbon学习笔记二

    之前介绍了如何搭建eureka服务并开发了一个用户服务成功注册到了eureka中,接下来介绍如何通过ribbon来从eureka中获取用户服务: springcloud ribbon提供客户端的负载均 ...

  5. 客户端实现负载均衡:springCloud Ribbon的使用

    Netfilx发布的负载均衡器,是一个基于http.tcp的客户端负载均衡工具,具有控制http.tcp客户端的行为,为ribbon配置服务提供者的地址后,ribbon就 可以经过springClou ...

  6. springcloud ribbon的 @LoadBalanced注解

    在使用springcloud ribbon客户端负载均衡的时候,可以给RestTemplate bean 加一个@LoadBalanced注解,就能让这个RestTemplate在请求时拥有客户端负载 ...

  7. SpringCloud Ribbon 负载均衡 通过服务器名无法连接的神坑一个

    一,问题 采取eureka集群.客户端通过Ribbon调用服务,Ribbon端报下列异常 java.net.UnknownHostException: SERVICE-HI java.lang.Ill ...

  8. springcloud Ribbon学习笔记一

    上篇已经介绍了如何开发eureka服务并让多个服务进行相互注册,接下来记录如何开发一个服务然后注册到eureka中并能通过ribbon成功被调用 开发一个用户服务并注册到eureka中,用户服务负责访 ...

  9. springcloud源码分析(一)之采用redis实现注册中心

    注册中心 在分布式架构中注册中心起到了管理各种服务功能包括服务的注册.发现.熔断.负载.降级等功能,在分布式架构中起到了不可替代的作用.常见的注册中心有eureka,zookeeper等等,在spri ...

随机推荐

  1. BUAA面向对象设计与构造——第二单元总结

    BUAA面向对象设计与构造——第二单元总结 第一阶段:单部傻瓜电梯的调度 第二阶段:单部可捎带电梯的调度 (由于我第一次写的作业就是可捎带模式,第二次只是增加了负数楼层,修改了一部分参数,因此一起总结 ...

  2. CI 框架 隐藏index.php 入口文件 和 设置访问application下子目录

    1.隐藏根目录下 index.php, 在根目录下创建 .htaccess文件 内容如下: <IfModule mod_rewrite.c> RewriteEngine on Rewrit ...

  3. Linux中安装MySQL

    因为使用yum安装.安装过程需保证网络通畅 一.安装mysql 1.yum安装mysqlCentOS7默认数据库是mariadb,配置等用着不习惯,因此决定改成mysql,但是CentOS7的yum源 ...

  4. IntelliJ IDEA之UML类图

    IntelliJ IDEA之UML类图 生成方法 Show Diagrams 选中需要的类,右键单击Diagrams,之后点击Show Diagrams,或者快捷键Ctrl+Alt+Shift+U 生 ...

  5. hadoop2-elasticsearch的安装

    本文主要讲elasticsearch-2.2.1的安装过程. 准备工作: 1.搭建虚拟机 你需要先参考 hadoop2集群环境搭建 把你的虚拟机搭建起来-hadoop环境可以先不用搭建(完成步骤1到步 ...

  6. vue 源码学习三 vue中如何生成虚拟DOM

    vm._render 生成虚拟dom 我们知道在挂载过程中, $mount 会调用 vm._update和vm._render 方法,vm._updata是负责把VNode渲染成真正的DOM,vm._ ...

  7. 发现一款适合php网站的管理软件——kodexplorer,能取代ftp

    今天偶然看到可以利用可道云来管理网站的文件.可道云不需要数据库,因此搭建非常简单.搭建的方法也很简单.传统的 WordPress 站点的文件管理,通常是是通过 FTP 或者服务器面板自带的文件管理器来 ...

  8. 浅谈C++ STL

    C++ STL(标准模板库)是一套功能强大的 C++ 模板类,提供了通用的模板类和函数,这些模板类和函数可以实现多种流行和常用的算法和数据结构,如向量.链表.队列.栈. C++ 标准模板库的核心包括以 ...

  9. js高级3

    1.解决函数内this的指向 可以在函数外提前声明变量_this/that=this 通过apply和call来修改函数内的this指向 (1)二者区别 用法是一样的,就是参数形式不一样        ...

  10. Meltdown Attack

    1. 引言 2018年1月3日,Google Project Zero(GPZ)团队安全研究员Jann Horn在其团队博客中爆出CPU芯片的两组漏洞,分别是Meltdown与Spectre. Mel ...