简介
Spring cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它是基于Netflix的Riboon实现的。Ribbon是客户端负载均衡器,这有别语例如Nginx服务端负载均衡器。Ribbon本身提供了不通负载均衡策略使用不通的应用场景。
客户端负载均衡和服务端负载均衡的区别
客户端负载均衡和服务端负载均衡最大的不同在于服务清单所在的位置。客户端负载均衡中,客户端中都维护着自己要访问的服务段清单,而这些清单都来源于服务注册中心,但是服务端负载均衡的服务清单是无法自己来维护的。
负载均衡器分类
- BaseLoadBalancer
BaseLoadBalancer类时Ribbon服务均衡器的基础实现类,在该类中定义了很多关于负载均衡器光宇的基础内容。
- DynamicServiceListLoadBalancer
DynamicServiceListLoadBalancer负载均衡器时对BaseLoadBalancer的扩展。
- ZoneAwareLoadBalancer
ZoneAwareLoadBalancer负载均衡器时对DynamicServiceListLoadBalancer的扩展。
负载均衡策略
IRule时负载均衡策略的接口,AbstractLoadBalancerRule是负载均衡策略的抽象类。下面我们看一下几个具体的实现类:
- RandomRule:
实现了从服务实例清单中随机选择一个服务实例的功能
- RoundRobinRule:
实现了按照线性轮询的方式一次选择每个服务实例的功能
- RetryTule:
实现了一个具备重试机制的实例选择功能
- WeightedRespinseTimeRule:
该策略是对RoundRobinRule的扩展,增加了根据实例的运行情况来计算权重,并根据权重来挑选实例,已达到更优的分配效果。
- ClientConfigEnabledRoundRobinRule
该策略较为特殊,我们一般不直接使用它。因为它本身并没有实现什么特殊的处理逻辑,真如代码中所示,在他的内部定义了一个RoundRobinRule策略,而choose函数的实现也正是使用了RoundRobinRule的线性轮询机制,所以它实现的功能实际上RoundeRobinRule相同。
虽然不能直接使用该策略,但是可以通过继承该策略,默认的choose就实现了线性轮询机制,但是可以在子类中实现更高级的策略
- BestAvailableRule
该策略通过遍历负载均衡器中维护的所有实例,会过滤调故障的实例,并找出并发请求数最小的一个,所以该策略的特征是选择出最空闲的实例
- PredicateBaseRule
先通过子类中实现的Predicate逻辑来过滤一部分服务实例,然后再以线性轮询的方式从过滤后的实例清单中选出一个。至于如何过滤,需要我们在AbstractServerPredicate的子类中实现apply方法来确定具体的实现策略。
- ZoneAvoidanceRule
它是PredicateBaseRule的具体实现类,从它的源码中可以看到它是通过CompositePredicate来进行服务实例清单额过滤的。这是一个组合过滤条件,在其构造函数中,ZoneAvoidancePredicate为主过滤条件,AvailabilityPredicate为次过滤条件初始化了组合过滤条件的实例。
ZoneAvoidanceRule在实现的时候并没有像AvailabilityFilteringRule那样重写choose函数来优化,所以它完全遵循了弗雷的过滤主逻辑:“先过滤清单,再轮询选择”。请中国过滤清单的条件就是我们上面提到的以ZoneAvoidancepredicate为主过滤条件,AvailabilityPredicate为次过滤条件的组合过滤条件CompositePredicate.从CompositePredicate的代码片段中,我们可以看到它定义的一个主过滤条件AbstraServicePredicate Delegate以及一组次过滤条件列表listfallbacks,所以它的此过滤列表是可以拥有多个的,并且由于它采用了List存储所以次过滤条件是按顺序执行的。
在获取过滤结果的实现函数getEligibleServiers中,它的处理逻辑如下所示:
1.使用主过滤条件对所有的实例过滤并返回过滤后的实例清单
2.一次使用此过滤条件列表中的过滤条件对主过滤的结果进行过滤
3.每次过滤之后(包括著过滤条件和此过滤条件),都需要判断下面两个条件,只要有一个符合就不再进行过滤,将当前结果返回供线性轮询算法选择:过滤后的实例总数>=最小过滤实例数(minimalFiltereServers,默认为1);过滤后的实例比例>最小过滤百分比(minimalFilteredPercentage,默认为0)。
RestTemple详解
要使用ribbon实现负载均衡策略,restTemple是必不可少的,我们使用它实现对服务的消费访问和负载均衡。因为我们是使用restful接口访问的,所以这里主要介绍restful接口的几种使用:
- GET
第一种:getForEntity
1.getForEntity(String url,Class responseType,Object... urlVariables):
此方法有三个参数:其中url为请求地址,responseType为响应body的包装类型,urlVariables为url中的参数绑定。主要做法是:在url中使用占位符并配合urlVariables参数实现GET请求的参数绑定。
示例:
RestTemplate restTemplate=new RestTemplate(); ResponseEntity<User> responseEntity=restTemplate.getForEntity("http://user-service/user?name={1}",User.class,"zhangsan"); User body=responseEntity.getBody();
2.getForEntity(String url,Class responseType,Map urlVariables):
此方法和上面的方法很像,只是参数类型不一样,使用了Map类型,所以在使用该方法绑定参数是需要在占位符中指定map中参数的key值。
示例:
RestTemplate restTemplate=new RestTemplate(); Map<String,String> params=new HashMap<>(); params.put("name","zhangsan"); params.put("age",2); ResponseEntity<User> responseEntity=restTemplate.getForEntity("http://user-service/user?name={name}?age={age}",User.class,params); User body=responseEntity.getBody();
3.getForEntity(URI url,Class responseType)
此方法用url对象替代了上面的url和urlVariables参数来指定访问地址和参数的绑定。
示例:
RestTemplate restTemplate=new RestTemplate(); UriComponents uriComponents=UriComponentsBuilder.fromUriString("http://user_service/user?name={name}").build().expand("zhangsan").encode(); URI uri=uriComponents.toUri(); ResponseEntity<User> responseEntity=restTemplate.getForEntity(uri,User.class,"zhangsan"); User body=responseEntity.getBody();
第二种:getForObject:
此方法是和不需要关注出body外的其他内容时使用,因为之方法会直接返回响应体的body内容并进行对象封装,实现请求直接返回包装好的对象内容弄个。
1.getForObject(String url,Class responseType,Object... urlVariables)
2.getForObject(String url,Class responseType,Map urlVariables):
3.getForObject(URI url,Class responseType)
这三种使用方法和getForEntity类似,只举一个例子:
RestTemplate restTemplate=new RestTemplate(); User result=restTemplate.getForObject(uri,User.class);
- POST
此类型的请求参数中和getForEntity大部分一致,只是多了一个参数request。request参数可以是个普通对象,也可以是一个HttpEntity对象。如果时一个普通对象,而非HttpEntity对象的时候,RestTemplate会将请求对象转换为一个HttpEntity对象来处理,其中Object就是request的类型,request内容会被事为完整的body来处理;而如果request是HttpEntity对象,那么就是被当作一个完成的HTTP请求对象来处理,这个request中不仅包含了body的内容,也包含了header的内容。
第一种:postForEntity
1.postForEntity(String url,Object request,Class responseType,Object... urlVariables)
2.postForEntity(String url,Object request,Class responseType,Map urlVariables)
3.postForEntity(URI url,Object request,Class responseType)
示例:
RestTemplate restTemplate=new RestTemplate(); User user=new User("zhangsan",30); ResponseEntity<User> responseEntity=restTemplate.postForEntity("http://user-service/user",user,User.class); User body=responseEntity.getBody();
第二种:postForObject
1.postForEntity(String url,Object request,Class responseType,Object... urlVariables)
2.postForEntity(String url,Object request,Class responseType,Map urlVariables)
3.postForEntity(URI url,Object request,Class responseType)
示例:
RestTemplate restTemplate=new RestTemplate(); User user=new User("zhangsan",30); User responseEntity=restTemplate.postForObject("http://user-service/user",user,User.class);
- PUT
在RestTemplate中,对PUT请求可以通过put方法进行调用实现。
1.put(String url,Object request,Object... urlVariables)
2.put(String url,Object request,Map urlVariables)
3.put(URI url,Object request)
示例:
RestTemplate restTemplate=new RestTemplate(); Long id=10001L; User user=new User("zhangsan",30); restTemplate.put("http://user-service/user/{1}",user,id);
- DELETE
在RestTemplate中,对DELTTE请求可以通过delete方法进行调用实现。
1.put(String url,Object request,Object... urlVariables)
2.put(String url,Object request,Map urlVariables)
3.put(URI url,Object request)
示例:
RestTemplate restTemplate=new RestTemplate(); Long id=10001L; restTemplate.put("http://user-service/user/{1}",id);
示例
通过Spring cloud Ribbon的封装,我们在微服务架构中使用客户端负载均衡调用时非常简单的,只需要两步:
1.服务提供者只需要将服务注册到注册中心或者多个相关联的服务注册中心
2.服务消费者直接通过调用被@LoandBalanced注解修饰过的RestTemlate来实现面向服务的接口调用即可
- pom,xml配置
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.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>
<spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- <dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.0.2.RELEASE</version>
</dependency> -->
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.SR5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
- 应用主类修改
@EnableDiscoveryClient @SpringBootApplication public class RibbonApplicate{ /** * 实例化ribbon使用的RestTemplate * @return */ @Bean @LoadBalanced public RestTemplate rebbionRestTemplate(){ return new RestTemplate(); } public static void main (String[] args){ SpringApplication.run(RibbonApplication.class,args); } }
- 测试类
public class RibbonController{ @autowired RestTemplate restTemplate; @Getmapping("/helloRibbon") public String helloRibbon(){ //访问hello-service服务的hello接口 return restTemplate.getForEntity("http://hello-service/hello",String.class).getBody(); } }
- application.properties配置