客户端负载均衡组件之Ribbon

Ribbon整体架构

RibbonNetflix开源的客户端负载均衡组件,基于HTTPTCPSpring Cloud Ribbon基于Ribbon实现,提供轮询、随机等负载均衡策略进行服务调用,也可自定义负载均衡算法。配合Eureka Server使用时的架构图如下:

eureka-ribbon-architecture

使用了RibbonEureka Client服务消费者在发送请求之前,会从Eureka Server注册中心获取服务提供者列表,然后按照配置的负载均衡策略去发起请求,从而实现客户端的负载均衡。

Ribbon本身也会维护一份服务提供者地址列表。如果它发现服务提供者不可用,则会重新从Eureka Server注册中心获取有效的服务提供者地址列表进行更新。

为服务消费者配置Ribbon

IDEA中创建一个名叫ribbonmaven工程,然后在pom.xml中引入以下依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<!-- version -->
<properties>
<spring.boot.version>2.2.0.RELEASE</spring.boot.version>
<spring.cloud.version>Hoxton.SR9</spring.cloud.version>
</properties>

<!-- 预定义依赖 -->
<dependencyManagement>
<dependencies>
<!-- spring boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- spring cloud -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<!-- eureka-client依赖引入,其中包含了ribbon依赖 -->
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>

由于spring-cloud-starter-netflix-eureka-client依赖中已经包含了spring-cloud-starter-netflix-ribbon依赖,所以无需单独引入。

src/main/resources目录下创建application.properties配置文件,并添加以下配置项:

1
2
3
server.port=9002
spring.application.name=ribbon-service-consumer
eureka.client.service-url.defaultZone=http://localhost:520/eureka/,http://localhost:521/eureka/

编写启动类RibbonApplication

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.sunchaser.sparrow.microservice.springcloud.ribbon;

import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

/**
* @author sunchaser admin@lilu.org.cn
* @since JDK8 2021/2/8
*/
@SpringBootApplication
public class RibbonApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(RibbonApplication.class)
.web(WebApplicationType.SERVLET)
.run(args);
}

@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}

eureka-client-service-consumer微服务相比,我们只需要在RestTemplate上加入@LoadBalanced注解即可开启ribbon的客户端负载均衡功能。

下面我们使用带有负载均衡功能的RestTemplate来消费eureka-client-service-provider提供的服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.sunchaser.sparrow.microservice.springcloud.ribbon.controller;

import lombok.extern.slf4j.Slf4j;
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.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

/**
* @author sunchaser admin@lilu.org.cn
* @since JDK8 2021/2/6
*/
@RestController
@Slf4j
public class ConsumerController {

@Autowired
private RestTemplate restTemplate;

@Autowired
private LoadBalancerClient loadBalancerClient;

@GetMapping("/consumer/{id}")
public String consume(@PathVariable String id) {
return restTemplate.getForObject("http://eureka-client-service-provider/provider?id=" + id, String.class);
}

@GetMapping("/print/provider/instance")
public String getProviderInstanceList() {
ServiceInstance serviceInstance = this.loadBalancerClient.choose("eureka-client-service-provider");
log.info("service instance:{}", serviceInstance);
return serviceInstance.toString();
}
}

运行启动类RibbonApplication,即可将该服务消费者注册至注册中心。

由于我们在consume方法中将http请求的地址指定为了http://eureka-client-service-provider,这意味着我们要将服务提供者的虚拟主机名改为eureka-client-service-provider,当RibbonEureka配合使用时,会自动将虚拟主机名映射成微服务的网络地址。

修改eureka-client-provider微服务的src/main/resources/application.properties文件,添加以下配置项:

1
eureka.instance.virtual-host-name=eureka-client-service-provider

同样地方式启动两个服务提供者实例,先修改Run/Debug Configurations配置:点击Edit Configurations...,找到EurekaClientProviderApplication的启动配置,勾选Allow parallel run后点击OK保存。

低版本IDEA对应的勾选项是Single instance only

启动步骤:

1、将application.properties配置文件中的server.port端口号配置项设置为9000后启动项目。

2、修改application.properties配置文件中的server.port端口号配置项设置为9003后启动项目。

随后即可在http://localhost:520http://localhost:521注册中心页面上看到两个provider服务提供者实例:

two_eureka_client_service_provider

客户端的负载均衡消费

发送GET请求:GET http://127.0.0.1:9002/consumer/1,可看到输出为:provide:1。同时可在其中一个服务提供者实例的控制台上看见被调用的日志信息(另一个实例的控制台无相应日志输出):

1
2021-02-22 21:36:46.065  INFO 46900 --- [nio-9001-exec-5] c.s.s.m.s.e.c.p.c.ProviderController     : provide invoked: id=1

再次请求,即可在另一个实例的控制台上看到同样的日志输出。这就是客户端的负载均衡调用。

我们还可以通过getProviderInstanceList方法查看本次负载均衡策略选择的服务提供者的实例信息。发送GET请求:GEThttp://127.0.0.1:9002/print/provider/instance,可看到输出为:

1
RibbonServer{serviceId='eureka-client-service-provider', server=10.32.43.59:9001, secure=false, metadata={management.port=9001}}

再次请求,可看到输出为:

1
RibbonServer{serviceId='eureka-client-service-provider', server=10.32.43.59:9003, secure=false, metadata={management.port=9003}}

两次请求客户端选择的服务提供者实例不同,这就是客户端的负载均衡调用。