Ribbon

Spring Cloud Ribbon 是一套 客户端负载均衡和服务调用工具 Netflix Ribbon 是 Netflix 公司发布的开源组件,其主要功能是提供客户端的负载均衡算法和服务调用。Spring Cloud 将其与 Netflix 中的其他开源服务组件(例如 Eureka、Feign 以及 Hystrix 等)一起整合进 Spring Cloud Netflix 模块中,整合后全称为 Spring Cloud Netflix Ribbon。Spring Cloud Netflix停更后Spring Cloud 对 Netflix Ribbon 的二次封装。通过它,我们可以将面向服务的 REST 模板(RestTemplate)请求转换为客户端负载均衡的服务调用。 Ribbon 是 Spring Cloud 体系中最核心、最重要的组件之一。它虽然只是一个工具类型的框架,并不像 Eureka Server(服务注册中心)那样需要独立部署,但它几乎存在于每一个使用 Spring Cloud 构建的微服务中。 Spring Cloud 微服务之间的调用,API 网关的请求转发等内容,实际上都是通过 Spring Cloud Ribbon 来实现的,包括后续我们要介绍的 OpenFeign 也是基于它实现的。

负载均衡

负载均衡(Load Balance) ,负载均衡都是一个十分重要且不得不去实施的内容,它是系统处理高并发、缓解网络压力和服务端扩容的重要手段之一。 简单点说就是将用户的请求平摊分配到多个服务器上运行,以达到扩展服务器带宽、增强数据处理能力、增加吞吐量、提高网络的可用性和灵活性的目的。 常见的负载均衡方式有两种:
  • 服务端负载均衡
  • 客户端负载均衡

服务端负载均衡

画板

服务端负载均衡是在客户端和服务端之间建立一个独立的负载均衡服务器(例如 Nginx)。这个负载均衡服务器维护了一份可用服务端清单,然后通过心跳机制来删除故障的服务端节点,以保证清单中的所有服务节点都是可以正常访问的。 当客户端发送请求时,该请求不会直接发送到服务端进行处理,而是全部交给负载均衡服务器,由负载均衡服务器按照某种算法(例如轮询、随机等),从其维护的可用服务清单中选择一个服务端,然后进行转发。 服务端负载均衡具有以下特点:
  • 需要建立一个独立的负载均衡服务器。
  • 负载均衡是在客户端发送请求后进行的,因此客户端并不知道到底是哪个服务端提供的服务。
  • 可用服务端清单存储在负载均衡服务器上。

客户端负载均衡器

客户端负载均衡是将负载均衡逻辑以代码的形式封装到客户端上,即负载均衡器位于客户端。客户端通过服务注册中心(例如 Eureka Server)获取到一份服务端提供的可用服务清单。有了服务清单后,负载均衡器会在客户端发送请求前通过负载均衡算法选择一个服务端实例再进行访问,以达到负载均衡的目的;

画板

客户端负载均衡也需要心跳机制去维护服务端清单的有效性,这个过程需要配合服务注册中心一起完成。 客户端负载均衡具有以下特点:
  • 负载均衡器位于客户端,不需要单独搭建一个负载均衡服务器。
  • 负载均衡是在客户端发送请求前进行的,因此客户端清楚地知道是哪个服务端提供的服务。
  • 客户端都维护了一份可用服务清单,而这份清单都是从服务注册中心获取的。
Ribbon 就是一个基于 HTTP 和 TCP 的客户端负载均衡器,当我们将 Ribbon 和 Eureka 一起使用时,Ribbon 会从 Eureka Server(服务注册中心)中获取服务端列表,然后通过负载均衡策略将请求分摊给多个服务提供者,从而达到负载均衡的目的。

Ribbon使用

RestTemplate简介

Ribbon 可以与 RestTemplate(Rest 模板)配合使用,以实现微服务之间的调用。RestTemplate 官方文档参考 RestTemplate 是 Spring 家族中的一个用于消费第三方 REST 服务的请求框架。RestTemplate 实现了对 HTTP 请求的封装,提供了一套模板化的服务调用方法。通过它,Spring 应用可以很方便地对各种类型的 HTTP 请求进行访问。 RestTemplate 针对各种类型的 HTTP 请求都提供了相应的方法进行处理,例如 HEAD、GET、POST、PUT、DELETE 等类型的 HTTP 请求,分别对应 RestTemplate 中的 headForHeaders()、getForObject()、postForObject()、put() 以及 delete() 方法。

版本依赖说明

这里有一个点,在 老版本 的项目中,需要单独引入 Ribbon 的包,后来新版本 中引入 Eureka Server 就会默认集成 Ribbon。但是最新的版本(暂时为 3.1.4 )已经看不到 Ribbon了,只能看到 LoadBalancer

  1. <!--Spring Cloud Eureka 客户端依赖-->
  2. <dependency>
  3. <groupId>org.springframework.cloud</groupId>
  4. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  5. </dependency>
  6. <!--Spring Cloud Ribbon 依赖、老版本需要引入-->
  7. <dependency>
  8. <groupId>org.springframework.cloud</groupId>
  9. <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
  10. </dependency>

Spring Cloud 2020版本的Eureka Starter已经不在集成Ribbon。~~~~如下Eureka 3.1.4版本已经不在有Ribbon,取而代之的是 <font style="color:rgb(77, 77, 77);">LoadBalanced 3.1.4</font>去官网查看后,发现2020版本所导入的eureka版本以及把<font style="color:rgb(77, 77, 77);">Ribbon</font>移除了,所以以前的,往容器中加入一个<font style="color:rgb(77, 77, 77);">IRule</font>的实例(比如:RandomRule)就不实用了

Spring Cloud Ribbon - 图3

使用<font style="color:rgb(77, 77, 77);">@LoadBalancerClient</font>来进行对<font style="color:rgb(77, 77, 77);">@LoadBalanced</font>的配置修改

Ribbon 实现服务调用

我们通过一个简单的实例,演示 Ribbon 是如何实现服务调用的。学完如何调用接着在学习调用中如何实现负载均衡。先来介绍些下面要用到的模块 1、 Eureka Server使用前面集群完成的模块
  • spring-cloud-eureka-server-8999
  • spring-cloud-eureka-server-9000
  • spring-cloud-eureka-server-9001

2、服务提供者

  • spring-cloud-eureka-provider-book-9002

3、服务消费者搭建

  • spring-cloud-eureka-consumer-ribbon-order-9004

Spring Cloud Ribbon - 图4

消费者模块搭建

Eureka Server和服务提供者前面学习章节中有搭建,这里不在叙述,可参考前面章节学习搭建。 新建config目录,放置下方<font style="color:rgb(68, 68, 68);">1</font><font style="color:rgb(68, 68, 68);">2</font>
  1. 新建<font style="color:rgb(77, 77, 77);">CustomLoadBalancerConfiguration</font>
  1. package com.chen.config;
  2. import org.springframework.cloud.client.ServiceInstance;
  3. import org.springframework.cloud.loadbalancer.core.RandomLoadBalancer;
  4. import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
  5. import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
  6. import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
  7. import org.springframework.context.annotation.Bean;
  8. import org.springframework.core.env.Environment;
  9. public class CustomLoadBalancerConfiguration {
  10. @Bean
  11. public ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {
  12. String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
  13. return new RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
  14. }
  15. }
  1. 新建一个<font style="color:rgb(77, 77, 77);">ApplicationContextConfig</font>类,将修改配置后的RestTemplate放入容器中
  1. package com.chen.config;
  2. import org.springframework.cloud.client.loadbalancer.LoadBalanced;
  3. import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
  4. import org.springframework.context.annotation.Bean;
  5. import org.springframework.context.annotation.Configuration;
  6. import org.springframework.web.client.RestTemplate;
  7. @Configuration
  8. // 配置负载均衡算法类,如果需要自定义算法,则需要实现ReactorServiceInstanceLoadBalancer接口
  9. @LoadBalancerClient(name = "spring-cloud-eureka-consumer-ribbon-order-9004", configuration = CustomLoadBalancerConfiguration.class)
  10. public class ApplicationContextConfig {
  11. @Bean
  12. @LoadBalanced//使用@LoadBalanced注解赋予RestTemplate负载均衡的能力
  13. public RestTemplate getRestTemplate() {
  14. return new RestTemplate();
  15. }
  16. }
  1. eureka客户端yaml配置
  1. server:
  2. port: 9004
  3. spring:
  4. application:
  5. name: spring-cloud-eureka-consumer-ribbon-order-9004 //当前模块名称
  6. eureka:
  7. instance:
  8. #eureka服务端的实例名称
  9. hostname: 127.0.0.1
  10. client:
  11. # 表示是否将自己注册到EurekaServer默认为true
  12. register-with-eureka: true
  13. # 是否从EurekaServer抓取已有的注册信息,默认为true,单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
  14. fetch-registry: true
  15. service-url:
  16. # 设置与Eureka server交互的地址查询服务和注册服务都需要依赖这个地址。
  17. defaultZone: http://eureka8999.com:8999/eureka/,http://eureka9000.com:9000/eureka/,http://eureka9001.com:9001/eureka/
  1. 创建Controller
package com.chen.springcloudeurekaconsumerribbonorder9004.controller;

import com.chen.springcloudeurekaconsumerribbonorder9004.vo.Book;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@Slf4j
public class OrderRibbonController {
    //将服务注册进eureka就不需要使用127.0.0.1或者loaclhost来进行访问了。这种方式是直调用服务方的方法,根本没有用到 Spring Cloud
    //public static final String PAYMENT_URL="http://localhost:9002/";
    public static final String ORDER_URL = "http://SPRING-CLOUD-EUREKA-PROVIDER-BOOK-9002";// 服务名提供者模块名,面向微服务编程,即通过微服务的名称来获取调用地址

    //RestTemplate 是一种简单便捷的访问 restful 服务模板类,是 Spring 提供的用于访问 Rest 服务的客户端模板工具集,提供了多种便捷访问远程 HTTP 服务的方法
    @Autowired
    private RestTemplate restTemplate;

    @GetMapping(value = "/book/get/{id}")
    public Book createOrder(@PathVariable("id") Long id){
        return restTemplate.getForObject(ORDER_URL+"/book/get/"+id, Book.class);
    }
}

创建vo目录和vo实体。用于映射返回的实体对象

package com.chen.springcloudeurekaconsumerribbonorder9004.vo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Book {
    private String id;
    private String name;
    private String author;
    private String price;
    private String publishDate;
    private Integer count;
    private String dbSources;
}
  1. 启动类修改,使用 @EnableEurekaClient 注解来开启 Eureka 客户端功能
package com.chen.springcloudeurekaconsumerribbonorder9004;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient
public class SpringCloudEurekaConsumerRibbonOrder9004Application {

    public static void main(String[] args) {
        SpringApplication.run(SpringCloudEurekaConsumerRibbonOrder9004Application.class, args);
    }

}

测试

依次启动EurekaServer三个集群,服务提供者和服务消费者。然后浏览器访问服务消费者地址 http://127.0.0.1:9004/book/get/1,最终返回结果

{
  "id": "1",
  "name": "《西游记》",
  "author": "吴承恩",
  "price": "55",
  "publishDate": "2022-09-26 00:00:00",
  "count": 10,
  "dbSources": "spring-cloud-eureka-provider-book-9002"
}

Ribbon 实现负载均衡

上方讲解的 实现服务调用。其实搭建下来就已经实现负载均衡了。那我们这里为了方便我们理解我们在搭建一套服务提供者。参照spring-cloud-eureka-provider-book-9002搭建spring-cloud-eureka-provider-book-8002。内容几乎一致我们只需要修改下配置文件,是两套服务同时对应注册中心的一个服务地址。就可以实现负载均衡调用。

修改内容如下:

server:
  port: 8002 #服务端口号

spring:
  application:
    name: spring-cloud-eureka-provider-book-9002  #微服务名称,不做修改,与 spring-cloud-eureka-provider-book-9002 的配置保持一致(重要,否则无法实现负载均衡)
  datasource:
    username: root        #数据库登陆用户名
    password: cj123456789        #数据库登陆密码
    url: jdbc:mysql://127.0.0.1:3306/spring-cloud?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC       #数据库url
    driver-class-name: com.mysql.cj.jdbc.Driver #数据库驱动,如果是8.0以下使用 com.mysql.jdbc.Driver

mybatis:
  # 指定 mapper.xml 的位置
  mapper-locations: classpath:mybatis/mapper/*.xml
  #扫描实体类的位置,在此处指明扫描实体类的包,在 mapper.xml 中就可以不写实体类的全路径名
  type-aliases-package: com.chen.springcloudeurekaproviderbook90021.entity
  configuration:
    #默认开启驼峰命名法,可以不用设置该属性
    map-underscore-to-camel-case: true

eureka:
  client: #将客户端注册到 eureka 服务列表内
    service-url:
      #defaultZone: http://127.0.0.1:9001/eureka  #这个地址是9001注册中心在 application.yml 中暴露出来额注册地址 (单机版)
      defaultZone: http://eureka8999.com:8999/eureka/,http://eureka9000.com:9000/eureka/,http://eureka9001.com:9001/eureka/  #将服务注册到 Eureka Server 集群
  instance:
    instance-id: spring-cloud-eureka-provider-book-8002 #要修改自定义服务名称信息
    prefer-ip-address: true  #显示访问路径的 ip 地址

修改Book实体类:

这里主要的是source(来源字段)。分别对应当前模块名称,后续测试轮训调用可通过这个字段查看

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Book {
    private String id;
    private String name;
    private String author;
    private String price;
    private String publishDate;
    private Integer count;
    private String source = "spring-cloud-eureka-provider-book-8002";
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Book {
    private String id;
    private String name;
    private String author;
    private String price;
    private String publishDate;
    private Integer count;
    private String source = "spring-cloud-eureka-provider-book-9002";
}

测试:访问通同一个地址:http://127.0.0.1:9004/book/get/1,可以根据source区分是否负载均衡

Spring Cloud Ribbon - 图5Spring Cloud Ribbon - 图6