Spring Cloud完整组件搭建之路 第二篇 -OpenFeign、Ribbon整合
书接上回【Spring Cloud完整组件搭建之路 第一篇-Eureka】,对于OpenFeignn和Ribbon的配合使用还是很简单的,OpenFeign默认集成了Ribbon。
SpringCloud配置系列目录:
【Spring Cloud完整组件搭建之路 第一篇-Eureka】
【Spring Cloud完整组件搭建之路 第二篇 -OpenFeign、Ribbon整合】
【Spring Cloud完整组件搭建之路 第三篇 -OpenFeign、Hystrix整合】
【Spring Cloud完整组件搭建之路 第四篇 -Zuul】
【Spring Cloud完整组件搭建之路 第五篇 -Spring Cloud Config】
【Spring Cloud完整组件搭建之路 总结篇】
前置知识
Feign和OpenFeign
- Feign本身不支持Spring MVC的注解,它有一套自己的注解
- OpenFeign是Spring Cloud 在Feign的基础上支持了Spring MVC的注解,如@RequesMapping等等。
- OpenFeign的
@FeignClient
可以解析SpringMVC的@RequestMapping注解下的接口,
并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。
两种负载均衡
客户端负载均衡和服务端负载均衡最大的区别在于 服务端地址列表的存储位置,以及负载算法在哪里。
客户端负载均衡(Nginx)
在客户端负载均衡中,所有的客户端节点都有一份自己要访问的服务端地址列表,这些列表统统都是从服务注册中心获取的;
服务端负载均衡(Ribbon)
在服务端负载均衡中,客户端节点只知道单一服务代理的地址,服务代理则知道所有服务端的地址。
负载均衡算法
默认实现
ZoneAvoidanceRule(区域权衡策略):
复合判断Server所在区域的性能和Server的可用性,轮询选择服务器。
其他规则:
BestAvailableRule(最低并发策略):
会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务。逐个找服务,如果断路器打开,则忽略。
RoundRobinRule(轮询策略):
以简单轮询选择一个服务器。按顺序循环选择一个server。
RandomRule(随机策略):
随机选择一个服务器。
AvailabilityFilteringRule(可用过滤策略):
会先过滤掉多次访问故障而处于断路器跳闸状态的服务和过滤并发的连接数量超过阀值得服务,然后对剩余的服务列表安装轮询策略进行访问。
WeightedResponseTimeRule(响应时间加权策略):
据平均响应时间计算所有的服务的权重,响应时间越快服务权重越大,容易被选中的概率就越高。刚启动时,如果统计信息不中,则使用RoundRobinRule(轮询)策略,等统计的信息足够了会自动的切换到WeightedResponseTimeRule。响应时间长,权重低,被选择的概率低。反之,同样道理。此策略综合了各种因素(网络,磁盘,IO等),这些因素直接影响响应时间。
RetryRule(重试策略):
先按照RoundRobinRule(轮询)的策略获取服务,如果获取的服务失败则在指定的时间会进行重试,进行获取可用的服务。如多次获取某个服务失败,就不会再次获取该服务。主要是在一个时间段内,如果选择一个服务不成功,就继续找可用的服务,直到超时。
引入OpenFeign依赖
以【Spring Cloud完整组件搭建之路-Eureka】博客中Eurek客户端的配置为基础,引入openfeign和spring-web依赖。
<!--引入openfeign依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--引入spring-web依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--
引入可指定post/get传输方式的feign所需依赖。
openfeign默认所有带参数的请求都是Post,想要使用指定的提交方式需引入以下依赖
-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
<version>11.8</version>
</dependency>
添加启动类openfeign注解
那么,在【Spring Cloud完整组件搭建之路-Eureka】博客中我们创建了三个Eureka客户端节点,要在哪个客户端中添加openfeign注解呢?
openfeign是为了实现:调用其他服务的方法就行调用本地方法一样容易。
请求会先进入消费者客户端,然后,消费者客户端调用生产者客户端方法来响应请求,所以是【消费者客户端调用生产者客户端】,那生产者客户端只提供方法就好了,由消费者客户端使用openfeign来调用即可。
所以在【Spring Cloud完整组件搭建之路-Eureka】博客中创建的USER-CONSUMER服务中的启动类添加注解:
@EnableFeignClients
添加测试方法
在添加测试方法之前,我们要明白:
- Openfeign实现了一个服务调用另一个服务的方法。
- Ribbon实现了生产者的负载均衡,属于客户端的负载均衡。
生产者客户端
在用于负载均衡的生产者集群客户端中创建同样的方法即可。
新增UserProviderController测试类
import org.apache.commons.lang.builder.ToStringBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
public class UserProviderController {
@Value("${server.port}")
private String port;
/**
* 用于为consumer提供feign带参的get访问
* @param id
* @return
*/
@RequestMapping(value = "/aliveGet",method = RequestMethod.GET)
public String aliveGet(@RequestParam("id") Integer id){
return id + "---"+ port + "aliveGet";
}
/**
* 用于为consumer提供feign带参的get访问
* @return
*/
@RequestMapping(value = "/noParamGet",method = RequestMethod.GET)
public String noParamGet(){
return port + "aliveGet";
}
/**
* 用于为consumer提供feign带参的post访问
* @param map
* @return
*/
@RequestMapping(value = "/alivePost",method = RequestMethod.POST)
public String alivePost(@RequestBody Map<String,Object> map){
return ToStringBuilder.reflectionToString(map) + "---"+ port + "alivePost";
}
/**
* 用于为consumer提供feign带参的post访问
* @param map
* @return
*/
@RequestMapping(value = "/alivePostParam",method = RequestMethod.POST)
public String alivePostParam(@RequestParam Map<String,Object> map){
return ToStringBuilder.reflectionToString(map) + "---"+ port + "alivePostParam";
}
}
生产者客户端就是按照平常我们实现逻辑那种写法就好,没有需要注意的。
消费者客户端
需实现接受请求的方法和指定请求到生产者客户端的路由。
新增UserConsumerController测试Controller类
import com.hepai.learn.userconsumer.service.ConsumerInterface;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
// 接收请求
@RestController
public class UserConsumerController {
@Autowired
private ConsumerInterface consumerInterface;
@GetMapping("/aliveGet")
public String aliveGet(@RequestParam("id") Integer id){
return consumerInterface.aliveGet(id);
}
@GetMapping("/noParamGet")
public String noParamGet(){
return consumerInterface.noParamGet();
}
@PostMapping("/alivePost")
public String alivePost(@RequestBody Map<String,Object> map){
return consumerInterface.alivePost(map);
}
@PostMapping("/alivePostParam")
public String alivePostParam(@RequestParam Map<String, Object> map){
return consumerInterface.alivePostParam(map);
}
}
新增访问生产者客户端的ConsumerInterface接口类
这个接口类只是指定了要访问的生产者客户端地址,其他交给OpenFeign访问远端即可。
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
/**
* 第一行的@FeignClient注解是写死url进行远程服务调用
* 第二行的@FeignClient注解是通过eureka的app名称进行远程服务调用
* 根据第一行注解,得知Feign可以脱离Eureka独立实现远程访问
*/
//@FeignClient(name="user-service", url = "http://localhost:7300")
@FeignClient(name="USER-PROVIDER")
public interface ConsumerInterface{
// 访问远端的/aliveGet地址
@RequestMapping(value = "/aliveGet",method = RequestMethod.GET)
public String aliveGet(@RequestParam("id") Integer id);
@RequestMapping(value = "/noParamGet",method = RequestMethod.GET)
public String noParamGet();
// 访问远端的/alivePost地址
@PostMapping("/alivePost")
public String alivePost(@RequestBody Map<String, Object> map);
// 访问远端的/alivePost地址
@PostMapping("/alivePostParam")
public String alivePostParam(@RequestParam Map<String, Object> map);
}
OpenFeign/Ribbon整合测试
有上图可见,虽然访问的都是http://localhost:7400/aliveGet?id=1
,但是最终响应却来自不同的端口,说明,在Openfeign调用远端的同时,Ribbon也完成了对客户端的负载均衡,二者都起了作用,但是注意,OpenFeign和Ribbon是有先后的:
根据@FeignClient注解中的name属性值,Ribbon先将客户端服务在Eureka服务端拉取下来的服务列表进行一定规则的负载均衡算法,拿到一个有效的客户端地址,然后,Feign进行请求地址的组装,最终访问到指定客户端的指定方法。
OpenFeign通过Ribbon的负载均衡算法结果,拿到客户端地址,然后进行远程访问。