我的其他文章spring cloud系列
一、Feign简介
Feign是一个声明式的Web服务客户端,使用Feign可使得Web服务客户端的写入更加方便。
Spring cloud已经使用Ribbon在客户端负载均衡,为什么还会有feign方式呢?Ribbon调用是基于url来调用的,而java是面向对象的语言,所以ribbon显得有点格格不入了,这也就是feign会出现的原因
二、spring cloud结合Feign使用
2.1、pom文件
<!--feign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
2.2、yml文件
server:
port: 80
feign:
hystrix:
enabled: true
ribbon:
ReadTimeout: 120000
ConnectTimeout: 30000
MICROSERVICECLOUD-DEPT:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://localhost:7001/eureka/
#defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
2.3、启动类
package com.atguigu.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@SpringBootApplication
@EnableSwagger2
@EnableEurekaClient
@EnableFeignClients(basePackages= {"com.atguigu.springcloud"})//开启feign
@ComponentScan("com.atguigu.springcloud")
public class DeptConsumer80_Feign_App
{
public static void main(String[] args)
{
SpringApplication.run(DeptConsumer80_Feign_App.class, args);
}
}
2.4、调用实例
package com.atguigu.springcloud.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.atguigu.springcloud.entities.Dept;
import com.atguigu.springcloud.service.DeptClientService;
@RestController
public class DeptController_Consumer
{
/**feign声明的接口*/
@Autowired
private DeptClientService service;
@RequestMapping(value = "/consumer/dept/get/{id}",method = {RequestMethod.POST})
public Dept get(@PathVariable("id") Long id)
{
return this.service.get(id);
}
@RequestMapping(value = "/consumer/dept/list",method = {RequestMethod.GET})
public List<Dept> list()
{
return this.service.list();
}
@RequestMapping(value = "/consumer/dept/add",method = {RequestMethod.GET})
public Object add(Dept dept)
{
return this.service.add(dept);
}
}
2.4、feign声明的接口
package com.atguigu.springcloud.service;
import java.util.List;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.atguigu.springcloud.entities.Dept;
/**
*该注解声明一个接口,并且使用了微服务 "MICROSERVICECLOUD-DEPT"
*这个接口可以是开发接口的人维护。调用者只需要引入即可
*相比较ribbon方式,客户端必须知道url才能调用确实方便了不少
*/
@FeignClient(value = "MICROSERVICECLOUD-DEPT")
public interface DeptClientService
{
@RequestMapping(value = "/dept/get/{id}", method = RequestMethod.GET)
public Dept get(@PathVariable("id") long id);
@RequestMapping(value = "/dept/list", method = RequestMethod.GET)
public List<Dept> list();
@RequestMapping(value = "/dept/add", method = RequestMethod.POST)
public boolean add(Dept dept);
}
2.5、feign负载均衡
版本声明
- springcloud版本Dalston.SR1
- springboot版本1.5.9.RELEASE
其实这部分是通用的。
首先上结论:
- feign负载均衡默认是通过ribbon实现的。
所以通过配置ribbon就可以实现feign的负载均衡,ribbon如何配置
- 通过javaBean类配置,比如:
@Bean
public IRule myRule()
{
return new WeightedResponseTimeRule();
//return new RandomRule()。
//return new RetryRule();
}
- 通过yml或者properties配置
MICROSERVICECLOUD-DEPT:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
其中“MICROSERVICECLOUD-DEPT"是你自己的微服务消费者名字
上面两种方式都是可以的,如果同时配置,那么以javaBean配置为主
2.5.1 feign调用简单源码分析
首先看一下feign所在的包类的大致结构
关于feign中负载均衡的类很明显的在feign.ribbon子包下,这也可猜测feign的负载均衡是通过ribbon实现的。
下面我通过调试来一步一步的说明
第一步:包装DeptClientService。
DeptClientService是feign接口,该接口的实现类是一个代理对象,它主要包含
target对象: 主要记录微服务的name和微服务的url
fallbackFactory: 这个是关于熔断回调的类
第二步:通过LoadBalancerFeignClient执行该方法
LoadBalancerFeignClient是一个非常重要的类,该类主要是维护负载均衡器,通过负载均衡器里面的IRule(负载均衡策略)来寻找具体的微服务来完成url调用
下面是它的核心方法
@Override
public Response execute(Request request, Request.Options options) throws IOException {
try {
URI asUri = URI.create(request.url());
String clientName = asUri.getHost();
URI uriWithoutHost = cleanUrl(request.url(), clientName);
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
this.delegate, request, uriWithoutHost);
IClientConfig requestConfig = getClientConfig(options, clientName);
return lbClient(clientName).executeWithLoadBalancer(ribbonRequest,
requestConfig).toResponse(); //这句话是核心,下面有具体分析
}
catch (ClientException e) {
IOException io = findIOException(e);
if (io != null) {
throw io;
}
throw new RuntimeException(e);
}
}
分析:
在这个方法中url还是没有解析完成的url:http://MICROSERVICECLOUD-DEPT/dept/list 如果使用ribbon,这里会非常熟悉,此时微服务MICROSERVICECLOUD-DEPT还没有解析成IP+端口
lbClient(clientName): 该方法是获取负载均衡器,简单来说他就是一个类,该类里面持有负载均衡策略,你定义的负载均衡策略会实例化,然后动态的注入该类中。其中有一篇博客对ribbon的负载均衡源码和负载均衡器分析很透彻戳这里,本篇主要feign调用简单源码分析
executeWithLoadBalancer: 该方法看名字都能看出意思,这个就是负载均衡的入口方法了
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
RequestSpecificRetryHandler handler = getRequestSpecificRetryHandler(request, requestConfig);
LoadBalancerCommand<T> command = LoadBalancerCommand.<T>builder()
.withLoadBalancerContext(this)
.withRetryHandler(handler)
.withLoadBalancerURI(request.getUri())
.build();
try {
return command.submit( //核心方法submit,采用rxjava,理解成java的观察者模式即可
new ServerOperation<T>() {
@Override
public Observable<T> call(Server server) {
URI finalUri = reconstructURIWithServer(server, request.getUri());
S requestForServer = (S) request.replaceUri(finalUri);
try {
return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
}
catch (Exception e) {
return Observable.error(e);
}
}
})
.toBlocking()
.single();
} catch (Exception e) {
Throwable t = e.getCause();
if (t instanceof ClientException) {
throw (ClientException) t;
} else {
throw new ClientException(e);
}
}
}
分析:
里面主要是新建了一个类LoadBalancerCommand,这个类的核心方法submit采用了RxJava的响应式编程,理解成java中的观察者模式即可。下面我把submit的核心代码整理出来
public Observable<T> submit(final ServerOperation<T> operation) {
final ExecutionInfoContext context = new ExecutionInfoContext();
final int maxRetrysSame = retryHandler.getMaxRetriesOnSameServer();
final int maxRetrysNext = retryHandler.getMaxRetriesOnNextServer();
// Use the load balancer
Observable<T> o =
(server == null ? selectServer() : Observable.just(server)) //selectServer就是根据负载均衡规则选取微服务
.concatMap(new Func1<Server, Observable<T>>() {
@Override
// Called for each server being selected
public Observable<T> call(Server server) {
context.setServer(server);
final ServerStats stats = loadBalancerContext.getServerStats(server);
// Called for each attempt and retry
Observable<T> o = Observable
.just(server)
.concatMap(new Func1<Server, Observable<T>>() {
// do other ……
}
});
});
}
分析:
selectServer():该方法就是根据负载均衡策略来选取合适的消费者微服务。根据负载均衡器的上下文调用getServerFromLoadBalancer方法获取消费者微服务
public Server getServerFromLoadBalancer(@Nullable URI original, @Nullable Object loadBalancerKey) throws ClientException {
String host = null;
int port = -1;
ILoadBalancer lb = getLoadBalancer();
if (host == null) {
if (lb != null){
Server svc = lb.chooseServer(loadBalancerKey); //负载均衡器根据负载均衡算法来寻找消费者微服务,然后获取具体的端口和ip
if (svc == null){
throw new ClientException(ClientException.ErrorType.GENERAL,
"Load balancer does not have available server for client: "
+ clientName);
}
host = svc.getHost();
if (host == null){
throw new ClientException(ClientException.ErrorType.GENERAL,
"Invalid Server for :" + svc);
}
logger.debug("{} using LB returned Server: {} for request {}", new Object[]{clientName, svc, original});
return svc;
} else{
…………
……………
}
return new Server(host, port);
}
ILoadBalancer lb = getLoadBalancer(): 这句就是获取具体的负载均衡器。我截图说明一下
可以看到rule属性是我们定义的WeightedResponseTimeRule负载均衡策略。
Server svc = lb.chooseServer(loadBalancerKey); 负载均衡器根据WeightedResponseTimeRule策略来寻找消费者微服务 Server svc,该svc中包含host和port,这样我们就可以利用httpclient愉快的调用了。