《深入理解 Spring Cloud 与微服务构建》第七章 负载均衡 Ribbon
文章目录
一、RestTemplate 简介
1.简介
RestTemplate 是 Spring Resources 中一个访问第三方 RESTful API 接口的网络请求框架。RestTemplate 的设计原则和其它 Spring Template(例如 JdbcTemplate、JmsTemplate)类似,都是为执行复杂任务提供了一个具有默认行为的简单方法
RestTemplate 是用来消费 REST 服务的,所以 RestTemplate 的主要方法都与 REST 的 HTTP 协议的一些方法紧密相连,例如 HEAD、GET、POST、PUT、DELETE 和 OPTIONS 等方法,这些方法在 RestTemplate 类对应的方法为 headForHeaders()、getForObject()、postForObject()、put() 和 delete() 等
2.案例
写一个 RestTestController 类,获取 https://www.baidu.com/ 的网页 HTML 代码
通过 RestTemplate 的 getForObject() 方法就可以获取 https://www.baidu.com/ 的网页 HTML 代码,并在 API 接口 “/testRest” 返回该网页的 HTML 字符串。代码如下:
package com.sisyphus.controller;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class RestTestController {
public String testRest(){
RestTemplate restTemplate = new RestTemplate();
return restTemplate.getForObject("https://www.baidu.com/", String.class);
}
}
RestTemplate 支持常见的 HTTP 协议的请求方法,例如 GET、POST、PUT 和 DELETE 等,所以用 RestTemplate 很容易构建 RESTful API。在上面的 RestTestController 中,RestTemplate 用 Get 方法获取 https://ww.baidu.com 网页的 HTML 字符串。RestTemplate 的使用很简单,它支持 XML 和 JSON 数据格式,默认实现了序列化,可以自动将 JSON 字符串转换为实体。例如以下代码可以将返回的 JSON 字符串转换成一个 User 对象
User user = restTemplate.getForObject("https://wwww.~~~.com/", User.class);
二、Ribbon 简介
负载均衡是指将负载分摊到多个执行单元上,常见的负载均衡有两种方式:一种是独立进程单元,通过负载均衡策略,将请求转发到不同的执行单元上,例如 Nginx;另一种是将负载均衡逻辑以代码的形式封装到服务消费者的客户端上,服务消费者客户端维护了一份服务提供者的信息列表,有了信息列表,通过负载均衡策略将请求分摊给多个服务提供者,从而达到负载均衡的目的
Ribbon 是 Netflix 公司开源的一个负载均衡的组件,属于上述的第二种方式,是将负载均衡逻辑封装在服务消费者的客户端中,并且运行在客户端的进程里。Ribbon 是一个经过了云端测试的 IPC 库,可以很好地控制 HTTP 和 TCP 客户端的负载均衡行为
在 Spring Cloud 构建的微服务系统中,Ribbon 作为服务消费者的负载均衡器,有两种使用方式,一种是和 RestTemplate 相结合,另一种是和 Feign 相结合,Feign 已经默认集成了 Ribon
Ribbon 有很多子模块,但很多模块没有用于生产环境,目前 Netflix 公司用于生产环境的 Ribbon 子模块如下:
- ribbon-loadbalancer:可以独立使用或与其它模块一起使用的负载均衡器 API
- ribbon-eureka:Ribbon 结合 Eureka 客户端的 API,为负载均衡器提供动态服务注册列表信息
- ribbon-core:Ribbon 的核心 API
三、使用 RestTemplate 和 Ribbon 来消费服务
本案例是在第六章 Eureka 案例的基础上进行改造的,先回顾一下 Eureka 案例的代码结构,它包括一个服务注册中心 eureka-server、一个服务提供者 eureka-client。eureka-client 向 eureka-server 注册服务,并且 eureka-client 提供了一个 “/hi” API 接口用于提供服务
启动 eureka-server,端口为 8761.启动两个 eureka-client 实例,端口分别为 8762 和 8763.启动完成后,在浏览器上访问 http://localhost:8761/,浏览器显示 eureka-client 的两个实例已经成功向服务注册中心注册,它们的端口分别为 8762 和 8763
再创建一个 Module 工程,取名为 eureka-ribbon-client,其作为服务消费者,通过 RestTemplate 来远程调用 eureka-client 服务 API 接口的 “/hi”,即消费服务
创建完成 eureka-ribbon-client 的 Module 工程之后,在其 pom 文件中引入相关的依赖,包括继承了主 Maven 工程的 pom 文件,引入了 Eureka Client 的起步依赖 spring-cloud-starter-eureka、Ribbon 的起步依赖 spring-cloud-starter-ribbon,以及 WEB 的起步依赖 spring-boot-starter-web,代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>Eureka</artifactId>
<groupId>org.sisyphus</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>eureka-ribbon-client</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
在工程的配置文件 application.yml 做程序的相关配置,包括指定程序名为 eureka-ribbon-client,程序的端口号为 8764,服务的注册地址为 http://localhost:8761/eureka/,代码如下:
spring:
application:
name: eureka-ribbon-client
server:
port: 8764
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
另外,作为 Eureka Client 需要在程序的入口类加上注解 @EnableEurekaClient 开启 Eureka Client 功能,代码如下:
package com.sisyphus;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class EurekaRibbonClientApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaRibbonClientApplication.class, args);
}
}
写一个 RESTful API 接口,在该 API 接口内部需要调用 eureka-client 的 API 接口 “/hi”,即服务消费。由于 eureka-client 为两个实例,它们的端口为 8762 和 8763。在调用 eureka-client 的 API 接口 “/hi” 时希望做到轮流访问这两个实例,它们的端口为 8762 和 8763。在调用 eureka-client 的 API 接口 “/hi” 时希望做到轮流访问这两个实例,这时就需要将 RestTemplate 和 Ribbon 相结合进行负载均衡
通过查阅官方文档,可以知道如何将它们结合在一起,只需要在程序的 IoC 容器中注入一个 restTemplate 的 Bean。并在这个 Bean 上加上 @LoadBalanced 注解,此时 RestTemplate 就结合了 Ribbon 开启了负载均衡功能,代码如下:
package com.sisyphus.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RibbonConfig {
@Bean
@LoadBalanced
RestTemplate restTemplate(){
return new RestTemplate();
}
}
写一个 RibbonService 类,在该类的 hi() 方法用 restTemplate 调用 eureka-client 的 API 解耦,此时 Uri 上不需要使用硬编码(例如 IP 地址),只需要写服务命 eureka-client 即可,代码如下:
package com.sisyphus.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Service
public class RibbonService {
@Autowired
RestTemplate restTemplate;
public String hi(String name){
return restTemplate.getForObject("http://eureka-client/hi?name=" + name, String.class);
}
}
写一个 RibbonController 类,为该类加上 @RestController 注解,开启 RestController 的功能,写一个 “/hi” Get 方法的接口,调用 RibbonService 类的 hi() 方法,代码如下:
package com.sisyphus.controller;
import com.sisyphus.service.RibbonService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class RibbonController {
@Autowired
RibbonService ribbonService;
@GetMapping("/hi")
public String hi(@RequestParam(required = false, defaultValue = "sisyphus") String name){
return ribbonService.hi(name);
}
}
启动 eureka-ribbon-client 工程,在浏览器上访问 http://localhost:8761,显示的 Eureka Server 的主界面如下图所示。在主界面上发现有两个服务被注册,分别为 eureka-client 和 eureka-ribbon-client,其中 eureka-client 有两个实例,端口为 8762 和 8763,而 eureka-ribbon-client 的端口为 8764
在浏览器上多次访问 http://localhost:8764/hi?name=sisyphus,浏览器会轮流显示两个端口
这时可以发现,当访问 http://localhost:8764/hi?name=sisyphus 的 API 接口时,负载均衡起了作用,负载均衡器会轮流地请求 eureka-client 的两个实例中的 “/hi” API 接口
四、LoadBalancerClient 简介
负载均衡器的核心类为 LoadBalancerClient,LoadBalancerClient 可以获取负载均衡的服务提供者的实例信息。为了演示,在 RibbonController 重新写一个接口 “/testRibbon”,通过 LoadBalancerClient 去选择一个 eureka-client 的服务实例的信息,并将该信息返回,代码如下:
package com.sisyphus.controller;
import com.sisyphus.service.RibbonService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class RibbonController {
@Autowired
RibbonService ribbonService;
private LoadBalancerClient loadBalancer;
@GetMapping("/hi")
public String hi(@RequestParam(required = false, defaultValue = "sisyphus") String name){
return ribbonService.hi(name);
}
@GetMapping("/testRibbon")
public String testRibbon(){
ServiceInstance instance = loadBalancer.choose("eureka-client");
return instance.getHost()+":"+instance.getPort();
}
}
重新启动工程,在浏览器上多次访问 http://localhost:8764/testRibbon,浏览器会轮流显示如下内容:
localhost:8762
localhost:8763
可见,LoadBalancerClient 的 choose(“eureka-client”) 方法可以轮流地到 eureka-client 的两个服务实例的信息
那么负载均衡器是怎么获取到这些客户端的信息的呢?查看官方文档可以知道,负载均衡器 LoadBalancerClient 是从 Eureka Client 获取服务注册列表信息的,并将服务注册列表信息缓存了一份。在 LoadBalancerClient 调用 choose() 方法时,根据负载均衡策略选择一个服务实例的信息,从而进行了负载均衡。LoadBalancerClient 也可以不从 Eureka Client 获取注册列表信息,这时需要自己维护一份服务注册列表信息
我们再创建一个工程,取名为 ribbon-client。ribbon-client 工程的 pom 文件同 eureka-ribbon-client 工程的类似
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>Eureka</artifactId>
<groupId>org.sisyphus</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ribbon-client</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
在配置文件 application.yml 中,通过配置 ribbon.eureka.enable 为 false 来禁止调用 Eureka Client 获取注册列表。在配置文件 application.yml 中有一个程序名为 stores 的服务,有两个不同 Url 地址(例如 example.com 和 google.com)的服务实例,通过 stores.ribbon.listOfServers 来配置这些服务实例的 URL,代码如下:
stores:
ribbon:
listOfServers: example.com,google.com
ribbon:
eureka:
enabled: false
server:
port: 8769
新建一个 RerstController 类,创建一个 API 接口 “/testRibbon”。在 RestController 类注入 LoadBalancerClient,通过 LoadBalancerClient 的 choose() 方法获取服务实例的信息,代码如下:
package com.sisyphus.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class RibbonController {
@Autowired
private LoadBalancerClient loadBalancer;
@GetMapping("/testRibbon")
public String testRibbon(){
ServiceInstance instance = loadBalancer.choose("stores");
return instance.getHost()+":"+instance.getPort();
}
}
启动工程,在浏览器上多次访问 http://localhost:8769/testRibbon,浏览器会交替出现如下内容:
example.com:80
google.com:80
现在我们可以知道,在 Ribbon 中的负载均衡客户端为 LoadBalancerClient。在 Spring Cloud 项目中,负载均衡器 Ribbon 会默认从 Eureka Client 的服务注册列表中获取服务的信息,并缓存一份。根据缓存的服务注册列表信息,可以通过 LoadBalancerClient 来选择不同的服务实例,从而实现负载均衡。如果禁止 Ribbon 从 Eureka 获取注册列表信息,则需要自己去维护一份服务注册列表信息。根据自己维护服务注册列表的信息,Ribbon 也可以实现负载均衡