目录
Ribbon
简介
负载均衡
简介
负载均衡方式
服务端负载均衡
工作原理
特点
客户端负载均衡
工作原理
特点
对比
实现
负载均衡策略
切换负载均衡策略
定制负载均衡策略
超时与重试
单个服务配置
全局配置
服务调用
示例
Ribbon
简介
Ribbon 是 Netflix 公司发布的开源组件,其主要功能是提供客户端的负载均衡算法和服务调用;通过它,我们可以将面向服务的 REST 模板请求转换为客户端负载均衡的服务调用。微服务之间的调用,API 网关的请求转发等内容,实际上都是通过 Ribbon 来实现的。
负载均衡
简介
在任何一个系统中,负载均衡都是一个十分重要且不得不去实施的内容,它是系统处理高并发、缓解网络压力和服务端扩容的重要手段之一。
负载均衡,简单点说就是将用户的请求平摊分配到多个服务器上运行,以达到扩展服务器带宽、增强数据处理能力、增加吞吐量、提高网络的可用性和灵活性的目的。
负载均衡方式
常见的负载均衡方式有两种:服务端负载均衡、客户端负载均衡
服务端负载均衡
工作原理
服务端负载均衡是在客户端和服务端之间建立一个独立的负载均衡服务器,该服务器既可以是硬件设备(例如 F5),也可以是软件(例如 Nginx)。这个负载均衡服务器维护了一份可用服务端清单,然后通过心跳机制来删除故障的服务端节点,以保证清单中的所有服务节点都是可以正常访问的。
当客户端发送请求时,该请求不会直接发送到服务端进行处理,而是全部交给负载均衡服务器,由负载均衡服务器按照某种算法(例如轮询、随机等),从其维护的可用服务清单中选择一个服务端,然后进行转发。
特点
1.需要建立一个独立的负载均衡服务器
2.负载均衡是在客户端发送请求后进行的,因此客户端并不知道到底是哪个服务端提供的服务
3.可用服务端清单存储在负载均衡服务器上
客户端负载均衡
相较于服务端负载均衡,客户端服务在均衡则是一个比较小众的概念
工作原理
客户端负载均衡是将负载均衡逻辑以代码的形式封装到客户端上,即负载均衡器位于客户端。客户端通过服务注册中心(例如 Eureka Server)获取到一份服务端提供的可用服务清单。有了服务清单后,负载均衡器会在客户端发送请求前通过负载均衡算法选择一个服务端实例再进行访问,以达到负载均衡的目的;
客户端负载均衡也需要心跳机制去维护服务端清单的有效性,这个过程需要配合服务注册中心一起完成
特点
1.负载均衡器位于客户端,不需要单独搭建一个负载均衡服务器
2.负载均衡是在客户端发送请求前进行的,因此客户端清楚地知道是哪个服务端提供的服务
3.客户端都维护了一份可用服务清单,而这份清单都是从服务注册中心获取的
Ribbon 就是一个基于 HTTP 和 TCP 的客户端负载均衡器,当我们将 Ribbon 和 Eureka 一起使用时,Ribbon 会从 Eureka Server(服务注册中心)中获取服务端列表,然后通过负载均衡策略将请求分摊给多个服务提供者,从而达到负载均衡的目的
对比
实现
Ribbon 是一个客户端的负载均衡器,它可以与 Eureka 配合使用轻松地实现客户端的负载均衡。Ribbon 会先从 Eureka Server(服务注册中心)去获取服务端列表,然后通过负载均衡策略将请求分摊给多个服务端,从而达到负载均衡的目的
负载均衡策略
序号 | 实现类 | 负载均衡策略 |
---|---|---|
1 | RoundRobinRule | 轮询策略,即按照一定的顺序依次选取服务实例 |
2 | RandomRule | 随机选取一个服务实例 |
3 | RetryRule | 按照轮询的策略来获取服务,如果获取的服务实例为 null 或已经失效,则在指定的时间之内不断地进行重试,如果超过指定时间依然没获取到服务实例则返回 null |
4 | WeightedResponseTimeRule | 根据平均响应时间,来计算所有服务实例的权重,响应时间越短的服务实例权重越高,被选中的概率越大 |
5 | BestAvailableRule | 先过滤点故障或失效的服务实例,然后再选择并发量最小的服务实例 |
6 | AvailabilityFilteringRule | 先过滤掉故障或失效的服务实例,然后再选择并发量较小的服务实例 |
7 | ZoneAvoidanceRule | 默认的负载均衡策略,综合判断服务所在区域的性能和服务的可用性,来选择服务实例 |
切换负载均衡策略
Ribbon 默认使用轮询策略选取服务实例,我们也可以根据自身的需求切换负载均衡策略,只需要在服务消费者(客户端)的配置类中,将 IRule 的其他实现类注入到容器中即可
在配置类 ConfigBean 中添加以下代码,将负载均衡策略切换为 RandomRule(随机)
@Bean public IRule myRule() { // RandomRule 为随机策略 return new RandomRule(); }
定制负载均衡策略
1.创建一个名为 MyRandomRule 的类
package net.biancheng.myrule;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import java.util.List;
/**
* 定制 Ribbon 负载均衡策略
*/
public class MyRandomRule extends AbstractLoadBalancerRule {
private int total = 0; // 总共被调用的次数,目前要求每台被调用5次
private int currentIndex = 0; // 当前提供服务的机器号
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
return null;
}
Server server = null;
while (server == null) {
if (Thread.interrupted()) {
return null;
}
//获取所有有效的服务实例列表
List<Server> upList = lb.getReachableServers();
//获取所有的服务实例的列表
List<Server> allList = lb.getAllServers();
//如果没有任何的服务实例则返回 null
int serverCount = allList.size();
if (serverCount == 0) {
return null;
}
//与随机策略相似,但每个服务实例只有在调用 3 次之后,才会调用其他的服务实例
if (total < 3) {
server = upList.get(currentIndex);
total++;
} else {
total = 0;
currentIndex++;
if (currentIndex >= upList.size()) {
currentIndex = 0;
}
}
if (server == null) {
Thread.yield();
continue;
}
if (server.isAlive()) {
return (server);
}
server = null;
Thread.yield();
}
return server;
}
@Override
public Server choose(Object key) {
return choose(getLoadBalancer(), key);
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
// TODO Auto-generated method stub
}
}
2.创建一个名为 MySelfRibbonRuleConfig 的配置类,将我们定制的负载均衡策略实现类注入到容器中
/**
* 定制 Ribbon 负载均衡策略的配置类
* 该自定义 Ribbon 负载均衡策略配置类 不能在 net.biancheng.c 包及其子包下
* 否则所有的 Ribbon 客户端都会采用该策略,无法达到特殊化定制的目的
*/
@Configuration
public class MySelfRibbonRuleConfig {
@Bean
public IRule myRule() {
//自定义 Ribbon 负载均衡策略
return new MyRandomRule(); //自定义,随机选择某一个微服务,执行五次
}
}
3.修改位于启动类,在该类上使用 @RibbonClient 注解让我们定制的负载均衡策略生效
@SpringBootApplication
@EnableEurekaClient
//自定义 Ribbon 负载均衡策略在主启动类上使用 RibbonClient 注解,在该微服务启动时,就能自动去加载我们自定义的 Ribbon 配置类,从而是配置生效
// name 为需要定制负载均衡策略的微服务名称(application name)
// configuration 为定制的负载均衡策略的配置类,
// 且官方文档中明确提出,该配置类不能在 ComponentScan 注解(SpringBootApplication 注解中包含了该注解)下的包或其子包中,即自定义负载均衡配置类不能在 net.biancheng.c 包及其子包下
@RibbonClient(name = "MICROSERVICECLOUDPROVIDERDEPT", configuration = MySelfRibbonRuleConfig.class)
public class MicroServiceCloudConsumerDept80Application {
public static void main(String[] args) {
SpringApplication.run(MicroServiceCloudConsumerDept80Application.class, args);
}
}
超时与重试
使用HTTP发起请求难免会发生问题,在F版开始Ribbon的重试机制默认是开启的,需要添加对超时时间与重试策略的配置;
单个服务配置
RIBBON-SERVICE-B:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
ConnectTimeout: 3000
ReadTimeout: 60000
MaxAutoRetries: 3 #对第一次请求的服务的重试次数
MaxAutoRetriesNextServer: 1 #要重试的下一个服务的最大数量(不包括第一个服务)
OkToRetryOnAllOperations: true
全局配置
ribbon:
ConnectTimeout: 3000
ReadTimeout: 60000
MaxAutoRetries: 3 #对第一次请求的服务的重试次数
MaxAutoRetriesNextServer: 1 #要重试的下一个服务的最大数量(不包括第一个服务)
OkToRetryOnAllOperations: true
服务调用
Ribbon 可以与 RestTemplate(Rest 模板)配合使用,以实现微服务之间的调用
RestTemplate 是 Spring 家族中的一个用于消费第三方 REST 服务的请求框架。RestTemplate 实现了对 HTTP 请求的封装,提供了一套模板化的服务调用方法。通过它,Spring 应用可以很方便地对各种类型的 HTTP 请求进行访问。
示例
1.引入依赖
<!--Spring Cloud Ribbon 依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
2.创建请求,调用服务端提供的服务
@RestController
public class DeptController_Consumer {
//private static final String REST_URL_PROVIDER_PREFIX = "http://localhost:8001/"; 这种方式是直调用服务方的方法,根本没有用到 Spring Cloud
//面向微服务编程,即通过微服务的名称来获取调用地址
private static final String REST_URL_PROVIDER_PREFIX = "http://MICROSERVICECLOUDPROVIDERDEPT"; // 使用注册到 Spring Cloud Eureka 服务注册中心中的服务,即 application.name
@Autowired
private RestTemplate restTemplate; //RestTemplate 是一种简单便捷的访问 restful 服务模板类,是 Spring 提供的用于访问 Rest 服务的客户端模板工具集,提供了多种便捷访问远程 HTTP 服务的方法
//获取指定部门信息
@RequestMapping(value = "/consumer/dept/get/{id}")
public Dept get(@PathVariable("id") Integer id) {
return restTemplate.getForObject(REST_URL_PROVIDER_PREFIX + "/dept/get/" + id, Dept.class);
}
//获取部门列表
@RequestMapping(value = "/consumer/dept/list")
public List<Dept> list() {
return restTemplate.getForObject(REST_URL_PROVIDER_PREFIX + "/dept/list", List.class);
}
}
3.访问