三、Feign负载均衡

我的其他文章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如何配置

  1. 通过javaBean类配置,比如:
	@Bean
	public IRule myRule()
	{
		return new WeightedResponseTimeRule();
		//return new RandomRule()。
		//return new RetryRule();
	}
  1. 通过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愉快的调用了。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值