一、前言

Ribbon 是基于 Netflix Ribbon 实现的一套客户端负载均衡器,它本身不属于 Spring Cloud Alibaba 提供的组件,而是 Spring Cloud 将其封装成 starter 供微服务使用。另外,笔者在之前的 文章 中也做过 Ribbon相关的知识介绍,故本篇章只作为对 Ribbon 内容的补充。

二、介绍

2.1 RestTemplete 请求模板

Spring Cloud 底层对 Ribbon 做了二次封装,可以让我们使用 RestTemplate 的服务请求,同时搭配 @LoadBalanced 注解使用,从而实现客户端负载均衡的服务调用。

RestTemplate 提供两种方法 getForObjectgetForEntity 去请求调用服务端的数据,接下来笔者将介绍 RestTemplate 基于 REST 的常用的 2 种请求方式。

2.1.1 GET 请求

无参情况:

1
2
3
4
5
6
7
## 第一个参数:微服务接口地址,第二个参数:返回值类型
ResponseEntity<User> responseEntity = restTemplate.getForEntity("xxx", User.class);

User user = responseEntity.getBody();
HttpStatus statusCode = responseEntity.getStatusCode();
int statusCodeValue = responseEntity.getStatusCodeValue();
HttpHeaders headers = responseEntity.getHeaders();

有参情况:

1
2
3
4
5
6
7
8
9
10
11
12
String[] paramArr = {"1000", "张三"};

## 第一个参数:服务接口地址,第二个参数:返回值类型,第三个参数:入参数组
ResponseEntity<User> responseEntity = restTemplate.getForEntity("xxx?id={0}&name={1}", User.class, paramArr);


Map<String, Object> paramMap = new HashMap<>();
paramMap.put("id", 1000);
paramMap.put("name", "张三");

## 第一个参数:服务接口地址,第二个参数:返回值类型,第三个参数:入参 Map
ResponseEntity<User> responseEntity = restTemplate.getForEntity("xxx?id={id}&name={name}", User.class, paramMap);

2.1.2 POST 请求

方式一:使用 Map 传参

1
2
3
4
5
6
7
# 注意,使用的是 MultiValueMap 类型
MultiValueMap<String, Object> dataMap = new LinkedMultiValueMap<>();
dataMap.add("id", "1000");
dataMap.add("name", "张三");

# 第一个参数:服务接口地址,第二个参数:入参,第三个参数:返回值类型
ResponseEntity<User> responseEntity = restTemplate.postForEntity("xxx", dataMap, User.class);

注意:如果使用上边的方式传参,接口提供方使用 @RequestParam("id") Integer id 形式接收参数。

方式二:使用实体传参

1
2
3
4
5
6
User user = new User();
user.setId("1000");
user.setName("张三");

# 第一个参数:服务接口地址,第二个参数:入参,第三个参数:返回值类型
ResponseEntity<User> responseEntity = restTemplate.postForEntity("xxx", user, User.class);

注意:如果使用上边的方式传参,接口提供方使用 @RequestBody User user 形式接收参数。

方式三:使用 JSON 传参

1
2
3
4
5
6
7
String userJson = "{\"id\": 4, \"name\": \"张三\"}";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<>(userJson, headers);

# 第一个参数:服务接口地址,第二个参数:入参,第三个参数:返回值类型
ResponseEntity<User> responseEntity = restTemplate.postForEntity("xxx", entity, User.class);

注意:如果使用上边的方式传参,接口提供方使用 @RequestBody User user 形式接收参数。

方式四:已封装参数,URL 仍需添加额外参数

1
2
3
4
String token = "abc123";

# 第一个参数:服务接口地址,第二个参数:入参,第三个参数:返回值类型,第四个参数:配对 url 后边的参数
ResponseEntity<User> responseEntity = restTemplate.postForEntity("xxx?token={token}", user, User.class, token);

2.2 负载均衡

要使用 Ribbon 的负载均衡,只需要在 RestTemplateBean 上添加 @LoadBalanced 即可,如下:

使用 Ribbon 需要添加依赖:

1
2
3
4
5
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
1
2
3
4
5
6
7
8
9
@Configuration
public class RestConfiguration {

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

2.2.1 常规负载均衡策略

Ribbon 的负载均衡策略是由 IRule 接口定义, 该接口有如下实现:

从图中可知,Ribbon 已经默认实现了多种负载均衡策略,我们可以根据自己的实际情况替换这些策略:

负载均衡实现类策略
RandomRule随机
RoundRobinRule轮询
AvailabilityFilteringRule先过滤掉由于多次访问故障的服务,以及并发连接数超过阈值的服务,然后对剩下的服务按照轮询策略进行访问
WeightedResponseTimeRule根据平均响应时间计算所有服务的权重,响应时间越快服务权重就越大被选中的概率即 越高,如果服务刚启动时统计信息不足,则使用RoundRobinRule策略,待统计信息足够会切换到该WeightedResponseTimeRule策略
RetryRule先按照RoundRobinRule策略分发,如果分发到的服务不能访问,则在指定时间内进行重 试,然后分发其他可用的服务
BestAvailableRule先过滤掉由于多次访问故障的服务,然后选择一个并发量最小的服务
ZoneAvoidanceRule (默认)综合判断服务节点所在区域的性能和服务节点的可用性,来决定选择哪个服务

如果想替换默认的负载均衡策略,操作非常简单,只需重新创建 IRule 接口的实现即可,我们拿 RoundRobinRule 举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
public class RestConfiguration {

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

@Bean
public IRule testRule() {
return new RoundRobinRule();
}
}

这样,启动项目后,Ribbon 就会使用该策略进行接口调用了。

2.2.2 自定义负载均衡策略

如果上述的策略不满足自身要求,我们还可以自定义负载均衡策略,需要操作 2 个步骤:

第一步,实现 AbstractLoadBalancerRule 接口,例如:

1
2
3
4
5
6
7
8
9
10
11
12
public class MyNacosRule extends AbstractLoadBalancerRule {

@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
//基本上不需要实现
}

@Override
public Server choose(Object key) {
//实现该方法
}
}

第二步,配置文件(application.properties 或 application.yml)配置负载均衡策略:

1
xxx.ribbon.NFLoadBalancerRuleClassName=com.light.ribbon.MyNacosRule

其中,xxx表示远程服务的名称。