Spring Cloud 学习 之 Spring Cloud Ribbon(基础知识铺垫)

简介: Spring Cloud 学习 之 Spring Cloud Ribbon(基础知识铺垫)

1.负载均衡:


负载均衡在系统架构中是一个非常重要,并且不得不去实施的内容。因为负载均衡是对系统高可用,网络压力的缓解和处理能力扩容的重要手段之一。我们通常说的负载均衡都指的是服务端负载均衡,其中分为硬件负载均衡和软件负载均衡。硬件负载均衡主要通过在服务器节点之间安装专门用于负载均衡的设备,而软件负载均衡则是通过在服务器上安装一些具有均衡负载功能或模块的软件来完成请求分发的工作,比如Nginx等。不论采用硬件负载均衡还是软件负载均衡,只要是服务端负载均衡都能以类似下图的架构方式构建起来:

image.png

硬件负载均衡的设备或是软件负载均衡的软件模块都会维护一个下挂可用的服务端清单,通过心跳检测来剔除故障的服务端节点以保证清单中都是可以正常访问的服务端节点。当客户端发送请求到负载均衡设备的时候,该设备按某种算法(比如线性轮询,按权重负载,按流量负载)从维护的可用服务清单中取出一台服务端的地址,然后进行转发。


而客户端负载均衡跟服务端负载均衡最大的不同点在于上面所提到的服务清单所存储的位置。在客户端负载均衡中,所有客户端节点都维护着自己要访问的服务端清单,而这些服务端清单来自于注册中心,比如之前我们所学习的Eureka Server。同服务端负载均衡的架构类似,在客户端负载均衡中也需要心跳去维护服务端清单的健康性,只是这个步骤需要与服务注册中心配合完成。在Spring Cloud 实现的服务治理框架中,默认会创建针对各个服务治理框架的Ribbon自动化整合配置,比如Eureka中的org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration。在实际使用的时候,我们可以通过查看这两个类的实现,以找到他们的配置详情来帮助我们更好的使用它。


通过Spring Cloud Ribbon的封装,我们在微服务架构中使用客户端负载均衡调用非常简单,只需要如下两步:


服务提供者只需要启动多个服务实例并注册到一个注册中心或是多个相关联的服务注册中心

服务消费者直接通过调用被@LoadBalanced注解修饰过的RestTemplate来实现面向服务的接口调用

这样,我们就可以将服务提供者的高可用以及服务消费者的负载均衡调用一起实现了。


2.RestTemplate详解:


xxxForEntity/xxxForObject:主要介绍get跟post


image.png

get方法主要有6中调用方式,其中又可以分为getForEntity和getForObject,每个方法有3中重载方式。


getForEntity函数。该方法返回的是ResponseEntity,该对象是Spring对HTTP请求响应的封装,其中主要存储了HTTP的几个重要元素,比如HTTP请求状态码的枚举对象HttpStatus(也就是我们常说的400,404这些错误码),在它的父类HttpEntity中还存储着HTTP请求的头信息对象HttpHeaders以及泛型类型的请求体对象。


getForObject函数,我们可以理解为它是对getForEntity的进一步封装,它通过HttpMessageConverterExtractor对HTTP的请求响应体body内容进行对象转换,实现请求直接返回包装好的对象内容。


在了解了两种调用方式的不同的后,我们再来看看他们的参数:


  • String url:get请求访问地址,例如:“http://USER_SERVICE/user?name=zhangsan”


  • Class responseType: ResponseEntity对象的泛型类型,也就是我们返回数据的实例类型


  • Map urlVariables:get请求携带的参数,如果以这种方式携带参数,则调用方式如下:


 RestTemplate restTemplate = new RestTemplate();
        Map<String,String> param = new HashMap<>();
        param.put("name","zhangsan");
        restTemplate.getForEntity("http://USER_SERVICE/user?name={name}", User.class,param);
  • Object… urlVariables:get请求携带的参数,如果以这种方式携带参数,则调用方式如下:
        RestTemplate restTemplate = new RestTemplate();
        String [] param = {"zhangsan","11"};
        restTemplate.getForEntity("http://USER_SERVICE/user?name={1}&age={2}", User.class,param);
  • URI url: get请求访问地址,这里使用URI对象来替代之前的url和urlVariables参数来指定访问地址和参数绑定。URI是JDK中java.net包下的一个类,它表示一个统一资源标识符引用。调用方式如下:
 RestTemplate restTemplate = new RestTemplate();
        UriComponents uriComponents = UriComponentsBuilder
                .fromUriString("http://USER_SERVICE/user?name={name}")
                .build()
                .expand("zhangsan").encode();
        URI uri = uriComponents.toUri();
        restTemplate.getForEntity(uri, User.class);

或者:

 RestTemplate restTemplate = new RestTemplate();
        UriComponentsBuilder uriComponents = UriComponentsBuilder
                .fromUriString("http://USER_SERVICE/user");
        uriComponents.queryParam("name","zhangsan");
        restTemplate.getForEntity(uriComponents.build().toUri(), User.class);

采用上面的这种调用方式,并使用get请求的话,没办法携带头信息(反正我没找到)。但是post方法是可以的,我们看下post方法的调用方式:

image.png

可以看到跟get请求相比,post方法多携带了一个参数Object request,在这个参数中,我们可以携带我们的头信息,调用方式如下:

RestTemplate restTemplate = new RestTemplate();
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        HttpEntity entity = new HttpEntity(httpHeaders);
        UriComponentsBuilder uriComponents = UriComponentsBuilder
                .fromUriString("http://USER_SERVICE/user");
        uriComponents.queryParam("name", "zhangsan");
        restTemplate.postForEntity(uriComponents.build().toUri(), entity, User.class);

用上面这种方式调用的话,参数是拼接在url后面的,只不过会在头信息中携带我们定义的信息

http://USER_SERVICE/user?name=zhangsan

如果我们要以请求体的方式携带我们的参数,需要这样调用

        RestTemplate restTemplate = new RestTemplate();
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
    // 换成对象也可以
        Map<String,Object> param = new HashMap<>();
        param.put("name","zhangsan");
        HttpEntity entity = new HttpEntity(param,httpHeaders);
        UriComponentsBuilder uriComponents = UriComponentsBuilder
                .fromUriString("http://USER_SERVICE/user");
        restTemplate.postForEntity(uriComponents.build().toUri(), entity, User.class);

问题来了,如果我要以get请求的方式访问,并且还要携带头信息该怎么办呢?这就要说第二种调用方式了


exchange:


exchange方法可以在发送个服务器端的请求中设置头信息。

image.png

我们观察其参数可以发现


HttpMethod method:一个枚举类,主要列举了Http请求的各种请求方式。如get:HttpMethod.GET

HttpEntity requestEntity:请求参数封装,跟我们之前的用法一样

其余的参数之前已经分析过了就不讲了


execute源码分析:


如果我们去跟踪exchange方法就会发现,它其实就是调用了execute方法,不只是exchange,xxxForEntity/xxxForObjext其实底层都调用了execute方法。这里,为了对RestTemplate有更深的理解,我们现在来分析这个方法。


我们追踪这个方法,会发现,它调用了doExecute这个方法

protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback,
      @Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {
    Assert.notNull(url, "URI is required");
    Assert.notNull(method, "HttpMethod is required");
    ClientHttpResponse response = null;
    try {
            // 1.构建一个request对象
      ClientHttpRequest request = createRequest(url, method);
      if (requestCallback != null) {
                // 2. 发送请求前对request对象进行一些处理
        requestCallback.doWithRequest(request);
      }
            // 3.发送请求,得到响应
      response = request.execute();
            // 4.处理一些错误信息
      handleResponse(url, method, response);
            // 5.解析请求数据
      return (responseExtractor != null ? responseExtractor.extractData(response) : null);
    }
    catch (IOException ex) {
      String resource = url.toString();
      String query = url.getRawQuery();
      resource = (query != null ? resource.substring(0, resource.indexOf('?')) : resource);
      throw new ResourceAccessException("I/O error on " + method.name() +
          " request for \"" + resource + "\": " + ex.getMessage(), ex);
    }
    finally {
      if (response != null) {
        response.close();
      }
    }
  }

ClientHttpRequest request = createRequest(url, method);

这句话的主要目的是构建一个ClientHttpRequest 对象,我们跟踪下代码:

  protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException {
        ClientHttpRequest request = this.getRequestFactory().createRequest(url, method);
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("HTTP " + method.name() + " " + url);
        }
        return request;
    }

它会获取一个ClientHttpRequestFactory,然后构建一个ClientHttpRequest对象,我们可以看到默认会采用SimpleClientHttpRequestFactory,代码如下:

 private ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();

如果我们要配置不同的工厂要怎么配置呢?我们可以通过org.springframework.boot.web.client.RestTemplateBuilder对象,例如,我们要以okHttpClient作为RestTemplate的底层实现

 RestTemplateBuilder builder = new RestTemplateBuilder();
        ClientHttpRequestFactory okHttp3ClientHttpRequestFactory = new OkHttp3ClientHttpRequestFactory();
        RestTemplate build = builder
                .requestFactory(() -> okHttp3ClientHttpRequestFactory).build();
  1. requestCallback.doWithRequest(request);这里主要是对头信息做了一下处理,具体源码大家可以自己看下
  2. response = request.execute();发送一个http请求
  3. handleResponse(url, method, response);处理错误信息
  4. responseExtractor.extractData(response),按我们定义的ResponseEntity中的泛型跟HttpMessageConverter解析数据类型

由于Ribbon中,采用了RestTemplate所以花时间系统的了解了一下RestTemplate,接下来,我们就要系统的学习一下Ribbon。


相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
相关文章
|
4月前
|
监控 Java API
Spring Boot 3.2 结合 Spring Cloud 微服务架构实操指南 现代分布式应用系统构建实战教程
Spring Boot 3.2 + Spring Cloud 2023.0 微服务架构实践摘要 本文基于Spring Boot 3.2.5和Spring Cloud 2023.0.1最新稳定版本,演示现代微服务架构的构建过程。主要内容包括: 技术栈选择:采用Spring Cloud Netflix Eureka 4.1.0作为服务注册中心,Resilience4j 2.1.0替代Hystrix实现熔断机制,配合OpenFeign和Gateway等组件。 核心实操步骤: 搭建Eureka注册中心服务 构建商品
779 3
|
2月前
|
负载均衡 Java API
《深入理解Spring》Spring Cloud 构建分布式系统的微服务全家桶
Spring Cloud为微服务架构提供一站式解决方案,涵盖服务注册、配置管理、负载均衡、熔断限流等核心功能,助力开发者构建高可用、易扩展的分布式系统,并持续向云原生演进。
|
9月前
|
负载均衡 Dubbo Java
Spring Cloud Alibaba与Spring Cloud区别和联系?
Spring Cloud Alibaba与Spring Cloud区别和联系?
|
10月前
|
前端开发 Java Nacos
🛡️Spring Boot 3 整合 Spring Cloud Gateway 工程实践
本文介绍了如何使用Spring Cloud Alibaba 2023.0.0.0技术栈构建微服务网关,以应对微服务架构中流量治理与安全管控的复杂性。通过一个包含鉴权服务、文件服务和主服务的项目,详细讲解了网关的整合与功能开发。首先,通过统一路由配置,将所有请求集中到网关进行管理;其次,实现了限流防刷功能,防止恶意刷接口;最后,添加了登录鉴权机制,确保用户身份验证。整个过程结合Nacos注册中心,确保服务注册与配置管理的高效性。通过这些实践,帮助开发者更好地理解和应用微服务网关。
1808 0
🛡️Spring Boot 3 整合 Spring Cloud Gateway 工程实践
|
负载均衡 算法 Java
除了 Ribbon,Spring Cloud 中还有哪些负载均衡组件?
这些负载均衡组件各有特点,在不同的场景和需求下,可以根据项目的具体情况选择合适的负载均衡组件来实现高效、稳定的服务调用。
1110 61
|
11月前
|
人工智能 安全 Java
AI 时代:从 Spring Cloud Alibaba 到 Spring AI Alibaba
本次分享由阿里云智能集团云原生微服务技术负责人李艳林主讲,主题为“AI时代:从Spring Cloud Alibaba到Spring AI Alibaba”。内容涵盖应用架构演进、AI agent框架发展趋势及Spring AI Alibaba的重磅发布。分享介绍了AI原生架构与传统架构的融合,强调了API优先、事件驱动和AI运维的重要性。同时,详细解析了Spring AI Alibaba的三层抽象设计,包括模型支持、工作流智能体编排及生产可用性构建能力,确保安全合规、高效部署与可观测性。最后,结合实际案例展示了如何利用私域数据优化AI应用,提升业务价值。
1068 4
|
负载均衡 Java 开发者
深入探索Spring Cloud与Spring Boot:构建微服务架构的实践经验
深入探索Spring Cloud与Spring Boot:构建微服务架构的实践经验
739 5
|
Dubbo Java 应用服务中间件
Dubbo学习圣经:从入门到精通 Dubbo3.0 + SpringCloud Alibaba 微服务基础框架
尼恩团队的15大技术圣经,旨在帮助开发者系统化、体系化地掌握核心技术,提升技术实力,从而在面试和工作中脱颖而出。本文介绍了如何使用Dubbo3.0与Spring Cloud Gateway进行整合,解决传统Dubbo架构缺乏HTTP入口的问题,实现高性能的微服务网关。
|
负载均衡 Java Nacos
常见的Ribbon/Spring LoadBalancer的负载均衡策略
自SpringCloud 2020版起,Ribbon被弃用,转而使用Spring Cloud LoadBalancer。Ribbon支持轮询、随机、加权响应时间和重试等负载均衡策略;而Spring Cloud LoadBalancer则提供轮询、随机及Nacos负载均衡策略,基于Reactor实现,更高效灵活。
765 0