SpringCloud Ribbon是一个基于Http和Tcp的客户端负载均衡工具,它基于Netflix Ribbon实现。通过SpringCloud的封装,可以让我们轻松的将面向服务的REST模板请求自动转换成客户端负载均衡的服务调用。SpringCloud Ribbon虽然只是一个工具类框架,它不像服务注册中心、配置中心、API网关那样需要独立部署,但是它几乎存在于每一个SpringCloud构建的微服务和基础设施中。

因为微服务间的调用,API网关的请求转发等内容,实际上都是通过Ribbon来实现的,包括后续将要学习的Feign,它也是基于Ribbon实现的工具。所以对于SpringCloud Ribbon的理解和使用,对于我们使用SpringCloud来构建微服务非常重要。

负载均衡

负载均衡在系统架构中是一个非常重要的角色,因为它是应对系统高可用、网络压力缓冲和处理能力扩容的重要手段之一。

服务端负载均衡

一般情况下我们所说的负载均衡通常都是指服务端负载均衡,服务端负载均衡又分为两种,一种是硬件负载均衡,还有一种是软件负载均衡。

硬件负载均衡主要通过在服务器节点之间安装专门用于负载均衡的设备,如F5;

而软件负载均衡则通过在服务器上安装一些具有负载均衡功能或模块的软件来完成请求分发工作,如常用的Nginx。

不论是采用硬件负载均衡还是软件负载均衡,只要是服务端的负载均衡都可以使用下面类似的架构方式构建起来:

无论是硬件负载均衡的设备还是软件负载均衡的软件模块都会维护一个可用的服务端清单,然后通过心跳机制来剔除故障的服务端节点以保证清单中都是可以正常访问的服务端节点。当客户端的请求到达负载均衡服务器时,负载均衡服务器会按照某种配置好的规则(如线性轮询、按权重负载、按流量负载等)从维护的可用服务端清单中选出一台服务器去处理客户端的请求,然后进行转发,这就是服务端负载均衡。

客户端负载均衡

在前面学习Eureka服务发现与消费的过程中,介绍了客户端负载均衡,在那里我们说过这么一段话:

Ribbon是一个基于HTTP和TCP的客户端负载均衡器,当我们将Ribbon和Eureka一起使用时,Ribbon会从Eureka注册中心去获取服务端列表,然后进行轮询访问以到达负载均衡的作用,客户端负载均衡中也需要心跳机制去维护服务端清单的有效性,当然这个过程需要配合服务注册中心一起完成。

从上面的描述我们可以看出,客户端负载均衡和服务端负载均衡的最大不同点在于服务清单所存储的位置

在客户端负载均衡中,所有客户端节点都维护着自己要访问的服务端清单,而这些服务端的清单来自于服务注册中心Eureka Server。

同服务端负载均衡的架构类似,在客户端负载均衡中也需要心跳检查去维护服务端清单的健康性,只是这个步骤需要与服务注册中心配合来完成。

在SpringCloud实现的服务治理框架中,默认会创建针对各个服务治理框架的Ribbon自动化整合配置,如Eureka中的org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration,或者Consul中的org.springframework.cloud.consul.discovery.RibbonConsulAutoConfiguration。在实际使用的时候,可以通过查看这两个类的实现,以找到它们的配置详情来帮助我们更好的使用它。

通过SpringCloud Ribbon的封装,开发者在微服务架构中使用客户端负载均衡调用非常简单,只需要以下两个步骤:
(1)服务提供者需要启动多个服务实例并注册到一个注册中心或是多个相关联的服务注册中心。
(2)服务消费者直接通过调用被@LoadBalanced注解修饰过的RestTemplate来实现面向服务的接口调用。

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

在前面介绍服务发现与消费实现的时候,就使用到了RestTemplate,该对象会使用Ribbon的自动化配置,同时通过配置@LoadBalanced注解来开启客户端负载均衡功能。(前面也说过Ribbon实现了服务消费者的客户端负载均衡功能。)前面的例子演示了通过RestTemplate来发起一个GET或者POST请求,其实是实现对HELLO-SERVICE服务提供的/hello接口进行调用。

接下来介绍RestTemplate针对几种不同请求类型和参数类型的服务调用实现,同时为了加深印象,笔者会通过一个例子来进行验证。

环境搭建

首先服务注册中心依然使用之前创建的eureka-server项目,其次为了便于代码调用和真实还原实际的生产环境,这里新建一个Maven工程,名称为ribbon-rest-template,注意笔者将服务提供者和服务消费者均放在该Maven工程内。下面是创建成功的Maven项目目录截图:

其中commons是一个公共模块,它是一个普通的JavaSE工程,后续会将实体类写在改模块中;provider和consumer是两个SpringBoot工程,其中provider实服务提供者,而consumer则是服务消费者。请注意服务提供者和服务消费者均需添加如下依赖:

第一步,在provider和consumer这两个工程内添加对commons的依赖:

1
2
3
4
5
<dependency>
<groupId>com.envy</groupId>
<artifactId>commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>

第二步,接下来需要在commons工程内依次新建com.envy.entity包,并在里面新建Movie类,注意需要实现Serializable接口,因为后续是以RESTful风格的API进行访问,因此需要将对象进行序列化:

1
2
3
4
5
6
7
8
9
10
public class Movie implements Serializable {
private String name;
private double price;
private String author;
private Date onTime;

public Movie(){};

/**getter、setter、toString和有参构造方法**/
}

如下图所示:

第三步,针对provider这个服务提供者来说,需要进行如下配置:(a)在项目入口类上添加@EnableDiscoveryClient注解;(b)在application.yml配置文件中新增如下配置:

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
spring:
application:
name: restTemplate-provider
---
# 服务提供者pr1注册到服务注册中心peer1
server:
port: 8081

spring:
profiles: pr1

eureka:
client:
service-url:
defaultZone: http://peer1:1111/eureka

---
# 服务提供者pr2注册到服务注册中心peer1
server:
port: 8082

spring:
profiles: pr2

eureka:
client:
service-url:
defaultZone: http://peer1:1111/eureka/

(c)在项目目录新建Controller包,并在里面新建ProviderController类,接着定义一个provider方法,并对外提供provider接口,其中的代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RestController
public class ProviderController {
private static final Logger logger = LoggerFactory.getLogger(ProviderController.class);

@Autowired
DiscoveryClient client;

@GetMapping(value = "/provider")
public String provider(){
List<ServiceInstance> instances =client.getInstances("restTemplate-provider");
for(int i=0;i<instances.size();i++){
logger.info("/provider,host: "+instances.get(i).getHost()+
",service_id: "+instances.get(i).getServiceId());
}
return "this is service provider!";
}
}

请注意上述DiscoveryClient必须是与之同名的接口,而不是类,类中没有getInstances方法,这一点需要引起格外注意。

第四步,针对consumer这个服务提供者来说,需要进行如下配置:(a)在项目入口类上添@EnableDiscoveryClient注解;(b)在项目入口类中提供RestTemplate的Bean并开启客户端负载均衡功能,相应的代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
@SpringBootApplication
@EnableDiscoveryClient
public class ConsumerApplication {
@Bean
@LoadBalanced
RestTemplate restTemplate(){
return new RestTemplate();
}

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

(c)在application.yml配置文件中新增如下代码用于配置服务注册中心:

1
2
3
4
5
6
7
8
9
10
11
12
13
spring:
application:
name: restTemplate-consumer
server:
port: 9000
eureka:
client:
service-url:
defaultZone: http://peer1:1111/eureka/
fetch-registry: true # 开启检索服务
register-with-eureka: true
instance:
hostname: consumer

第五步,在consumer子模块中的项目目录里新建一个controller包,并在里面新建一个ConsumerController类。

这样就完成了项目测试环境的搭建,后续开始介绍RestTemplate的相关知识。