SpringCloud - Ribbon客户端负载均衡
SpringCloud - Ribbon负载均衡
0. Ribbon概述
① LB负载均衡(Load Balance)是什么?
负载均衡的表现就是将用户的请求分摊到多个服务器上,从而达到高可用的目的。常见的负载均衡软件有:Nginx、LVS、硬件F5等。
② 目前主流的负载方案分为以下两种:
- 集中式负载均衡,在消费者和服务提供方中间使用独立的代理方式进行负载,有硬件的(比如 F5),也有软件的(比如 Nginx)。
- 客户端自己做负载均衡,根据自己的请求情况做负载,Ribbon 就属于客户端自己做负载。
③ Ribbon负载均衡 VS Nginx负载均衡:
-
Nginx是服务器端负载均衡 ,客户端的请求都发给Nginx,Nginx实现分发,将请求发送到不同的服务器上。
-
Ribbon是客户端负载均衡 ,在调用微服务接口的时候,会在注册中心拿到注册信息服务列表缓存到本地JVM,从而在本地实现RPC远程服务调用技术。
④ 集中式LB和进程内LB:
-
集中式LoadBalancer:在服务的消费方和提供方之间使用的独立LoadBalancer设备(可以是硬件,如F5,也可以是软件,如Nginx),由该设施负责把请求通过某种策略转发至服务的提供方。
-
进程内LoadBalancer:将LoadBalancer集成到消费方,消费者从服务注册中心获取到可用地址,自己再从这些地址中,选择要访问的服务器(如这个微服务器名称下有多个生产者的微服务,端口8001、8002、8003,进行轮循)。Ribbon就属于进程内LoadBalancer,它只是一个类库,集成消费者进程,消费者通过它获取服务提供方的地址。
Spring Cloud Ribbon 是一个基于 HTTP 和 TCP 的客户端负载均衡工具,它基于 Netflix Ribbon 实现。通过 Spring Cloud 的封装,可以让我们轻松地将面向服务的 REST 模版请求自动转换成客户端负载均衡的服务调用。
Spring Cloud Ribbon 虽然只是一个工具类框架,它不像服务注册中心、配置中心、API 网关那样需要独立部署,但是它几乎存在于每一个 Spring Cloud 构建的微服务和基础设施中。因为微服务间的调用,API 网关的请求转发等内容,实际上都是通过 Ribbon 来实现的。
⑤ ribbon其实就是一个负载均衡的客户端组件,他可以和其他所需请求的客户端组件结合使用,和Eureka结合只是其中的一个实例。ribbon其实就是负载均衡加RestTemplate
ribbon模块:
名 称 | 说 明 |
---|---|
ribbon-loadbalancer | 负载均衡模块,可独立使用,也可以和别的模块一起使用。 |
Ribbon | 内置的负载均衡算法都实现在其中。 |
ribbon-eureka | 基于 Eureka 封装的模块,能够快速、方便地集成 Eureka。 |
ribbon-transport | 基于 Netty 实现多协议的支持,比如 HTTP、Tcp、Udp 等。 |
ribbon-httpclient | 基于 Apache HttpClient 封装的 REST 客户端,集成了负载均衡模块,可以直接在项目中使用来调用接口。 |
ribbon-example | Ribbon 使用代码示例,通过这些示例能够让你的学习事半功倍。 |
ribbon-core | 一些比较核心且具有通用性的代码,客户端 API 的一些配置和其他 API 的定义。 |
⑥ Ribbon在工作时分为两步:
第一步先选择EurekaServer,它优先选择在同一个区域内负载较少的Server
第二步再根据用户指定的策略,再从server取到的服务注册列表中选择一个地址
其中Ribbon提供了多中策略:比如轮询,随机和根据时间响应加权。
1. Ribbon的使用
① Euraka的最新版已经集成了ribbon:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
② 也可以手动的导入ribbon:放到order消费者模块中,因为只有order访问pay生产者时需要负载均衡
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
③ RestTemplate类:
在spring-cloud-starter-netflix-eureka-client
坐标下,是引入了spring-cloud-starter-netflix-ribbon
的,所以,我们仅仅只需要添加一个@LoadBalanced就可以实现负载均衡。
RestTemplate常见的方法有getForObject()、getForEntity()、postForObject()、postForEntity()方法。其中
-
ForObject()方法返回对象为响应体中数据转换成的对象,基本理解为JSON。
-
ForEntity()方法返回对象是ResponseEntity对象,包含了响应中的信息,比如响应头,响应状态码,响应体等。
④ 运行环境和测试:
将cloud-eureka-server7001、cloud-eureka-server7002的配置文件改成集群模式,让7001和7002互相注册,将cloud-provider-payment8001、cloud-provider-payment8002的配置文件改成集群模式,将cloud-consumer-order80的配置文件改成集群模式。
在cloud-consumer-order80模块中的OrderController里,添加两个方法,分别调用getForEntity()和postForEntity()方法。
@GetMapping("/consumer/payment/getForEntity/{id}")
public CommonResult getPaymentById2(@PathVariable("id") Long id) {
ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
//打印响应状态码
System.out.println("status code=" + entity.getStatusCode());
//打印响应头
System.out.println("headers=" + entity.getHeaders());
//打印响应体
if (entity.getStatusCode().is2xxSuccessful()) {
return entity.getBody();
} else {
return new CommonResult(404, "查找失败");
}
}
@GetMapping("/consumer/payment/create2")
public CommonResult create2(Payment payment) {
return restTemplate.postForEntity(PAYMENT_URL + "/payment/create", payment, CommonResult.class).getBody();
}
先启动Eureka注册中心,再启动两个生产者,最后启动消费者,浏览器发送请求http://localhost//consumer/payment/getForEntity/1来调用ForEntity()方法进行测试。在浏览器端,可以看到port的值,不断在8001和8002之前进行切换。
2. 替换Ribbon的负载均衡算法
IRule接口,Riboon使用该接口,根据特定算法从所有服务中,选择一个服务,Rule接口有7个实现类,每个实现类代表一个负载均衡算法。
① 官方文档说明:如果想要替换Ribbon的轮询算法,这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下,否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的了,也就是不能放在主启动类所在的包及子包下,因此新建一个包并定义一个配置类将轮询算法换为随机算法:
package com.atguigu.myrule;
@Configuration
public class MySelfRule {
@Bean
public IRule myRule(){
return new RandomRule();//定义为随机算法
}
}
② 在80主启动类上加一个注解(使用指定的算法)
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration = MySelfRule.class)
//name是服务提供者名称(地址),表明负载均衡用在服务提供者上
//configuration = MySelfRule.class是调用自定义的负载均衡配置类(使用自定义的随机算法)
public class OrderMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderMain80.class,args);
}
}
表示访问CLOUD_PAYMENT_SERVICE的服务时,使用我们自定义的负载均衡算法
3. 手写轮询算法
轮询算法:rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标,每次重启后rest接口从1计数。
① payment服务提供者模块(8001,8002)的Controller层加入一个方法返回服务的端口号:
@Value("${server.port}")
private String serverPort;
@GetMapping(value = "payment/lb")
public String getPaymentLB(){
return serverPort;
}
② 修改order 80消费者模块:
在配置类config中去掉@LoadBalanced注解(自带的负载均衡机制)
@Configuration
public class ApplicationContextConfig {
@Bean
//@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
在80模块中自定义接口并编写实现类:
package com.atguigu.springcloud.lb;
public interface LoadBalance {
ServiceInstance instances(List<ServiceInstance> serviceInstances);
}
public class MyLB implements LoadBalance{
private AtomicInteger atomicInteger = new AtomicInteger(0);
//获取下一个要调用的服务的id
public final int getAndIncrement(){
int current;
int next;
do{
current = this.atomicInteger.get();
next = current >= 2147483647 ? 0: current+1;
}while (!this.atomicInteger.compareAndSet(current,next));
System.out.println("next : "+next);
return next;
}
//根据所有的服务,选择一个本次调用的服务
@Override
public ServiceInstance instances(List<ServiceInstance> serviceInstances) {
//拿到id后,进行取余运算,得到真正要调用的服务
int index = getAndIncrement() % serviceInstances.size();
return serviceInstances.get(index);
}
}
在Controller层方法添加自定义的负载均衡算法:
@RestController
@Slf4j
public class OrderController {
//将访问地址改为服务名称
private static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";
@Resource
private RestTemplate restTemplate;
@Resource
private DiscoveryClient discoveryClient;
@Resource
private LoadBalance loadBalance;
@GetMapping(value = "/consumer/payment/lb")
public String getPaymentLB(){
//拿到 CLOUD-PAYMENT-SERVICE下的所有服务
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
if(instances==null || instances.size()<=0){
return null;
}
ServiceInstance serviceInstance = loadBalance.instances(instances);
return restTemplate.getForObject(PAYMENT_URL+"/payment/lb",String.class);
}
}
浏览器访问http://localhost/consumer/payment/lb,发现8001和8002轮询访问。
推荐阅读
-
Spring Cloud Ribbon实现客户端负载均衡的示例
-
详解Spring Cloud负载均衡重要组件Ribbon中重要类的用法
-
java架构之-负载均衡-Ribbon 的使用
-
Eureka源码探索(一)-客户端服务端的启动和负载均衡
-
撸一撸Spring Cloud Ribbon的原理-负载均衡策略
-
SpringCloud学习系列之二 ----- 服务消费者(Feign)和负载均衡(Ribbon)
-
springcloud的负载均衡两种实现方式
-
SpringCloud微服务(二)Fegin负载均衡遇到的问题和解决方案
-
SpringCloud-客户端的负载均衡Ribbon
-
spring cloud系列学习(SpringCloud之服务注册之Ribbon负载均衡)