背景介绍:
目前主要流行的负载均衡分为两种:一种是集中式负载均衡,在消费者和提供者中间使用独立的代理方式进行负载,有硬件也有软件,另外一种就是客户端自己进行负载均衡,ribbon就是属于客户端负载均衡。
ribbon是一个基于http和tcp的客户端负载均衡工具,基于Netfix Ribbon实现,通过Cloud的封装,可以让我们轻松的实现,他不像注册中心,配置中心,API网关那样需要单独部署,他存在于所有需要负载均衡的服务中。
快速入门:
首先新建一个springboot项目,命名为:ribbon-service
pom.xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ribbon</groupId>
<artifactId>ribbon</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>ribbon</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
配置文件:
spring.application.name=ribbon-service
server.port=9000
eureka.client.serviceUrl.defaultZone=http://127.0.0.1:8000/eureka/
eureka.instance.preferIpAddress=true
package com.ribbon;
import com.ribbon.MyInterceptor.MyLoadBalanced;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
启动类:
启动类里加一个RestTemplate的bean,这里的RestTemplate添加了@LoadBalanced注解就有了负载均衡的功能了,后面再细说
@SpringBootApplication
@EnableDiscoveryClient
public class RibbonApplication {
@Bean
// @LoadBalanced //客户端负载均衡
@MyLoadBalanced
RestTemplate restTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(RibbonApplication.class, args);
}
}
然后写一个controller,
@RestController
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@RequestMapping(value = "/ribbon",method = RequestMethod.GET)
public String helloConsumer(){
return restTemplate.getForEntity(
"http://hello-service/hello",String.class).getBody();
}
}
访问这个接口,就可以通过注册中心访问到hello-service的接口,我们启动两个hello-service,端口分别为8800,8801,多访问几次就会看到两个hello-service都有访问到了。
RestTemplate简介:
RestTemplate是spring自带的一个访问接口的类,并不是ribbon的
get请求:第一种getForEntity函数,返回的是ResponseEntity对象,第二种getForObject函数,这个是进一步封装,直接得到返回体
post请求:第一种postForEntity函数,返回的是整个ResponseEntity<T>对象,第二种postForObject函数,也是直接返回返回体,第三种getForLocation函数,该方法实现了post请求提交资源,返回新的资源的url。
RestTemplate只是请求接口的一种方法,由spring提供。还有put,delete等方法,我们现在用的最多的就是post,get。
@LoadBalanced注解:
这个注解就是开启负载均衡的一个关键,主要的逻辑就是给RestTemplate增加拦截器,在请求之前对请求的地址进行替换或者根据具体的负载策略选择服务地址,然后再去调用,默认策略是轮询。下面自己实现一个简单的@LoadBalanced来理解下原理。
首先新建一个拦截器:
package com.ribbon.MyInterceptor;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import java.io.IOException;
import java.net.URI;
/**
* 自定义一个负载均衡的拦截器
* Created by qhe on 2018/8/16.
*/
public class MyLoadBalancerInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
final URI originalUri = httpRequest.getURI();
String servicename = originalUri.getHost();
System.out.println("进入自定义的请求拦截器中:"+servicename);
return clientHttpRequestExecution.execute(httpRequest,bytes);
}
}
然后实现一个注解,这里我们复制 @LoadBalanced的源码就行,修改下名字
package com.ribbon.MyInterceptor;
import org.springframework.beans.factory.annotation.Qualifier;
import java.lang.annotation.*;
/**
* 自定义一个负载均衡
* Created by qhe on 2018/8/16.
*/
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface MyLoadBalanced {
}
再写一个配置类:
package com.ribbon.MyInterceptor;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.web.client.RestTemplate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Created by qhe on 2018/8/16.
*/
@Configuration
public class MyLoadBalancedAutoConfiguration {
@MyLoadBalanced //这是我们自己定义的注解
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList(); //这是restTemplates集合
@Bean //把拦截器注入进来
public MyLoadBalancerInterceptor myLoadBalancerInterceptor(){
return new MyLoadBalancerInterceptor();
}
//维护一个restTemplates列表,在SmartInitializingSingleton 里对restTemplate进行拦截设置。
@Bean
public SmartInitializingSingleton myLoadBalancedRestTemplataInitializer(){
return new SmartInitializingSingleton() {
@Override
public void afterSingletonsInstantiated() {
for (RestTemplate restTemplate:MyLoadBalancedAutoConfiguration.this.restTemplates){
List<ClientHttpRequestInterceptor> list = new ArrayList<>(restTemplate.getInterceptors());
list.add(myLoadBalancerInterceptor());
restTemplate.setInterceptors(list);
}
}
};
}
}
然后在启动类里改成我们自定义的注解:
@Bean
// @LoadBalanced //客户端负载均衡
@MyLoadBalanced//自定义的负载均衡
RestTemplate restTemplate(){
return new RestTemplate();
}
启动服务,访问接口,我们就可以看到控制台上有自定义的输出了
源码在spring-cloud-commons.jar中的org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration类中。
负载均衡策略:
ribbon作为一款客户端的负载均衡框架,默认的是轮询策略,同时提供了其他很多策略。在IRule中。
BestAvailabl:选择一个最小的并发请求的Server,逐个考察server,如果servcer标记错了则跳过,然后在选择最小的server.
AvailabilityFilteringRule:过滤掉那些一直连接失败且被标记为circuit tripped的server,并过滤那些高并发的server,或者使用一个AvailabilityPredicate来包含过滤逻辑。
ZoneAvoidanceRule:判断一个zone的运行性能是否可用,剔除不可用的zone,或者过滤掉连接数过多的server。
RandomRule:随机选择一个server。
RoundRobinRule:轮询选择
RetryRule:对选定的负载均衡策略上重试机制,就是说如果根据策略选择的server请求不成功,就重试。
WeightedResponseTimeRule:根据相应时间分配一个权重,相应时间越长,权重越小,被选中的可能性就越低。
当然还可以自定义均衡策略。实现IRule接口就行。
学习资料:《springcloud微服务实战》《springcloud 微服务全栈技术与案例解析》