微服务学习之Consul与Feign

发布时间:2023-03-30 08:30

微服务学习之Consul&&Feign

1、Consul

CAP原理:

​ Consistency(一致性) :

​ Availability(可用性):

​ Partition Tolerance(分区容错):

Eureka : 保证 AP Consul:则是 CP

1、各个注册中心的比较

Feature euerka Consul zookeeper etcd
服务健康检查 可配支持 服务状态,内存,硬盘等 (弱)长连接,keepalive 连接心跳
多数据中心 支持
kv 存储服务 支持 支持 支持
一致性 raft paxos raft
cap ap cp cp cp
使用接口(多语言能力) http(sidecar) 支持 http 和 dns 客户端 http/grpc
watch 支持 支持 long polling/大部分增量 全量/支持long polling 支持 支持 long polling
自身监控 metrics metrics metrics
安全 acl /https acl https 支持(弱)
spring cloud 集成 已支持 已支持 已支持 已支持

2、Consul介绍

Consul 是 HashiCorp 公司推出的开源工具,用于实现分布式系统的服务发现与配置。与其它分布式服务注册与发现的方案,Consul 的方案更“一站式”,内置了服务注册与发现框 架、分布一致性协议实现、健康检查、Key/Value 存储、多数据中心方案,不再需要依赖其它工具(比如 ZooKeeper 等)。使用起来也较 为简单。Consul 使用 Go 语言编写,因此具有天然可移植性(支持Linux、windows和Mac OS X);安装包仅包含一个可执行文件,方便部署,与 Docker 等轻量级容器可无缝配合。

Consul 的优势:

  • 使用 Raft 算法来保证一致性, 比复杂的 Paxos 算法更直接. 相比较而言, zookeeper 采用的是 Paxos, 而 etcd 使用的则是 Raft。
  • 支持多数据中心,内外网的服务采用不同的端口进行监听。 多数据中心集群可以避免单数据中心的单点故障,而其部署则需要考虑网络延迟, 分片等情况等。 zookeeper 和 etcd 均不提供多数据中心功能的支持。
  • 支持健康检查。 etcd 不提供此功能。
  • 支持 http 和 dns 协议接口。 zookeeper 的集成较为复杂, etcd 只支持 http 协议。
  • 官方提供 web 管理界面, etcd 无此功能。
  • 综合比较, Consul 作为服务注册和配置管理的新星, 比较值得关注和研究。

特性:

  • 服务发现
  • 健康检查
  • Key/Value 存储
  • 多数据中心

Consul 角色

  • client: 客户端, 无状态, 将 HTTP 和 DNS 接口请求转发给局域网内的服务端集群。
  • server: 服务端, 保存配置信息, 高可用集群, 在局域网内与本地客户端通讯, 通过广域网与其它数据中心通讯。 每个数据中心的 server 数量推荐为 3 个或是 5 个。
  • 数据中心:一套客户端和服务端的集合,两个数据中心可以通过广域网交互。

Consul 客户端、服务端还支持夸中心的使用,更加提高了它的高可用性。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PbpgoCJU-1612753902092)(D:\zt学习\ifar云远\consul数据流图.png)]

Consul 工作原理:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZM0r32ip-1612753902095)(D:\zt学习\ifar云远\consul工作原理图.png)]

  • 当producer(也就是服务提供者)启动的时候,会向 Consul 发送一个 post 请求,告诉 Consul 自己的 IP 和 Port。可以理解为注册信号。
  • Consul 接收到 Producer 的注册后,每隔10s(默认)会向 Producer 发送一个健康检查的请求,检验Producer是否健康。为了实现Availability;
  • 3、当 Consumer 发送 GET 方式请求 /api/address 到 Producer 时,会先从 Consul 中拿到一个存储服务 IP 和 Port 的临时表,从表中拿到 Producer 的 IP 和 Port 后再发送 GET 方式请求 /api/address;
  • 4、该临时表每隔10s会更新,只包含有通过了健康检查的 Producer;

Consul 强一致性©带来的是:

服务注册相比 Eureka 会稍慢一些。因为 Consul 的 raft 协议要求必须过半数的节点都写入成功才认为注册成功 Leader 挂掉时,重新选举期间整个 Consul 不可用。保证了强一致性但牺牲了可用性。

Eureka 保证高可用(A)和最终一致性:

服务注册相对要快,因为不需要等注册信息 replicate 到其它节点,也不保证注册信息是否 replicate 成功 当数据出现不一致时,虽然 A, B 上的注册信息不完全相同,但每个 Eureka 节点依然能够正常对外提供服务,这会出现查询服务信息时如果请求 A 查不到,但请求 B 就能查到。如此保证了可用性但牺牲了一致性。

其它方面,eureka 就是个 servlet 程序,跑在 servlet 容器中; Consul 则是 go 编写而成。

2、Feign

1、原生Feign

Feign是Netflix开源的一个REST客户端,通过定义接口,使用注解的方式描述接口的信息,就可以发起接口调用。

我们先看一下原生的Feign的调用方式;

//定义接口
interface GitHub {
    //Feign的原生注解
  @RequestLine("GET /repos/{owner}/{repo}/contributors")
  List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);

  @RequestLine("POST /repos/{owner}/{repo}/issues")
  void createIssue(Issue issue, @Param("owner") String owner, @Param("repo") String repo);

}

public static class Contributor {
  String login;
  int contributions;
}

public static class Issue {
  String title;
  String body;
  List<String> assignees;
  int milestone;
  List<String> labels;
}

public class MyApp {
  public static void main(String... args) {
      //Feign调用,具体操作方法,builder.decoder.target();
    GitHub github = Feign.builder()
                         .decoder(new GsonDecoder())
                         .target(GitHub.class, "https://api.github.com");
  
    // Fetch and print a list of the contributors to this library.
    List<Contributor> contributors = github.contributors("OpenFeign", "feign");
    for (Contributor contributor : contributors) {
      System.out.println(contributor.login + " (" + contributor.contributions + ")");
    }
  }
}

上面的方式可以看到一些问题,就是如果我们利用原生的注解和利用Feign默认的创建方式放到Spring中,不是很方便,而且我觉得和使用restTemplate方式没什么大区别,Spring当然也想到了这件事,所以Spring Cloud OpenFeign就诞生了。

2、Spring Cloud OpenFeign

还是先看官方示例;

@FeignClient("stores") // 注册的服务名称,从而获取ip:host
public interface StoreClient {
    @RequestMapping(method = RequestMethod.GET, value = "/stores") //服务提供端的url
    List<Store> getStores();

    @RequestMapping(method = RequestMethod.POST, value = "/stores/{storeId}", consumes = "application/json")
    Store update(@PathVariable("storeId") Long storeId, Store store);
}

3、FeignClient 注解的使用和介绍

value, name

value 和 name的作用一样,如果没有配置url那么配置的值将作为服务名称,用于服务发现。如果配置了,那么就是一个名称;

@FeignClient(name = "demo1-web1",value = "demo1-web1")

contextId

比如我们有个user服务,但是user服务的接口是分不同的类的,我们不想将所有的接口都放在一个FeignClient中。

如下:

@FeignClient(name = "optimization-user")
public interface UserRemoteClient {
	@GetMapping("/user/get")
	public User getUser(@RequestParam("id") int id);
}
@FeignClient(name = "optimization-user")
public interface UserRemoteClient2 {
	@GetMapping("/user2/get")
	public User getUser(@RequestParam("id") int id);
}

这样项目启动会报错的,原因是因为Bean的名称冲突了。

解决方案:

运行spring容器中重写一个beanName的BeanDefinition

spring.main.allow-bean-definition-overriding=true

另一种解决方案就是为每个Client手动指定不同的contextId,这样就不冲突了。

@FeignClient(name = "optimization-user",contextId="user1")
public interface UserRemoteClient {
	@GetMapping("/user/get")
	public User getUser(@RequestParam("id") int id);
}
@FeignClient(name = "optimization-user", contextId="user2")
public interface UserRemoteClient2 {
	@GetMapping("/user2/get")
	public User getUser(@RequestParam("id") int id);
}

url

url用于配置指定服务的地址,相当于直接请求这个服务,而不是通过Ribbon的负载均衡的服务选择。我们可以在调式某一个特定场景的时候使用。

@FeignClient(name = "optimization-user", url = "http://localhost:8085")
public interface UserRemoteClient {
	
	@GetMapping("/user/get")
	public User getUser(@RequestParam("id") int id);
}

configuration

configuration就是指定Feign的配置类,可以通过自定义配置了的方式来改变Feign的Encoder、Decoder、LogLevel、Contract etc

configuration定义

public class FeignConfiguration {
	@Bean
	public Logger.Level getLoggerLevel() {
		return Logger.Level.FULL;
	}
	@Bean
	public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
		return new BasicAuthRequestInterceptor("user", "password");
	}
	
	@Bean
	public CustomRequestInterceptor customRequestInterceptor() {
		return new CustomRequestInterceptor();
	}
	// Contract,feignDecoder,feignEncoder.....
}
@FeignClient(value = "optimization-user", configuration = FeignConfiguration.class)
public interface UserRemoteClient {
	
	@GetMapping("/user/get")
	public User getUser(@RequestParam("id")int id);
	
}

fallback(熔断处理)

定义容错的处理类,也就是回退逻辑,fallback的类必须实现Feign Client接口,但是它会当发生异常的时候执行异常函数,而不知道具体什么异常信息。

fallback定义

@Component
public class UserRemoteClientFallback implements UserRemoteClient {
	@Override
	public User getUser(int id) {
		return new User(0, "默认fallback");
	}
	
}

使用示例

@FeignClient(value = "optimization-user", fallback = UserRemoteClientFallback.class)
public interface UserRemoteClient {
	
	@GetMapping("/user/get")
	public User getUser(@RequestParam("id")int id);
	
}

fallbackFactory

也是容错的处理,但是它可以知道熔断的异常信息。

fallbackFactory定义

/**
 * 

* * @author zhuteng 2020/5/11 15:27 * @version V1.0 */
@Component public class HelloServiceFallbackFactory implements FallbackFactory<HelloService> { private Logger logger = LoggerFactory.getLogger(HelloServiceFallbackFactory.class); @Override public HelloService create(Throwable throwable) { return new HelloService() { @Override public String hi(String name) { logger.error("HelloService#hi 异常,",throwable); return "fall back "; } }; } }

使用示例

@FeignClient(value = "optimization-user", fallbackFactory = HelloServiceFallbackFactory.class)
public interface UserRemoteClient {
	
	@GetMapping("/user/get")
	public User getUser(@RequestParam("id")int id);
	
}

path

path定义当前FeignClient访问接口时的统一前缀,比如我们每一个controller可能需要编写ui/v1/user,这些通用的uri就可以直接定义在path中。

@FeignClient(name = "optimization-user", path="user")
public interface UserRemoteClient {
	
	@GetMapping("/get")
	public User getUser(@RequestParam("id") int id);
}

4、Feign原理

1、一切从注解开始

@EnableFeignClients(basePackages = {"com.hikvision.sspd.cp.demo1.reference"})

复合注解,如下:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {

看到这里我们就清楚事利用@Import注解,将FeignClientsRegistrar.class 注入到容器中;

FeignClientsRegistrar.class 源码

class FeignClientsRegistrar
		implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
		//看到它实现的接口,我们就可以知道它是要将一些组件注册到容器中,并初始化的时候就通过Aware接口获取到了容器的资源加载器,和环境对象,所以接下来这俩对象肯定会被用到。
	// patterned after Spring Integration IntegrationComponentScanRegistrar
	// and RibbonClientsConfigurationRegistgrar

	private ResourceLoader resourceLoader; //接收获取的资源加载器

	private Environment environment;  //接收获取的环境对象

	FeignClientsRegistrar() {
	}

	@Override
	public void setResourceLoader(ResourceLoader resourceLoader) {
		this.resourceLoader = resourceLoader;
	}

	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
        //根据@EnableFeignClients,获取其元数据并设置FeignClientSpecification的name 和configuration,然后注册FeignClient配置类到容器。
		registerDefaultConfiguration(metadata, registry);
        //扫描并注册所有标记@FeignClient的类到容器
		registerFeignClients(metadata, registry);
	}

	private void registerDefaultConfiguration(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
        //获取@EableFeignClents注解上面的元数据
		Map<String, Object> defaultAttrs = metadata
				.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
		//设置name
		if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
			String name;
			if (metadata.hasEnclosingClass()) {
				name = "default." + metadata.getEnclosingClassName();
			}
			else {
				name = "default." + metadata.getClassName();
			}
            //根据name 和注解中的defaultConfigurantion注册配置
			registerClientConfiguration(registry, name,
					defaultAttrs.get("defaultConfiguration"));
		}
	}
    private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
			Object configuration) {
        //构造BeanDefinition
		BeanDefinitionBuilder builder = BeanDefinitionBuilder
				.genericBeanDefinition(FeignClientSpecification.class);
        //添加name到构造器参数中
		builder.addConstructorArgValue(name);
        //添加configuration到构造器参数
		builder.addConstructorArgValue(configuration);
        //注册FeignClientSpecification(就是FeignClient说明书,也就是@EnableFeign上面的配置项)到容器中,注册说明书后,我们之后在注册FeignClient就可以照着说明书上面的内容来做。 说明书中包括:FeignClient需要的重试策略,超时策略,日志等配置,如果某个服务没有设置,则读取默认的配置。
		registry.registerBeanDefinition(
				name + "." + FeignClientSpecification.class.getSimpleName(),
				builder.getBeanDefinition());
	}

	public void registerFeignClients(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
        //根据获取的resourceLocader创建类路径组建扫描器
		ClassPathScanningCandidateComponentProvider scanner = getScanner();
		scanner.setResourceLoader(this.resourceLoader);

		Set<String> basePackages;
		//获取指定扫描FeignClient的包和需要扫描的是带有@FeignClient注解的类
		Map<String, Object> attrs = metadata
				.getAnnotationAttributes(EnableFeignClients.class.getName());
		AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
				FeignClient.class);
		final Class<?>[] clients = attrs == null ? null
				: (Class<?>[]) attrs.get("clients");
		if (clients == null || clients.length == 0) {
			scanner.addIncludeFilter(annotationTypeFilter);
			basePackages = getBasePackages(metadata);
		}
		else {
			final Set<String> clientClasses = new HashSet<>();
			basePackages = new HashSet<>();
			for (Class<?> clazz : clients) {
				basePackages.add(ClassUtils.getPackageName(clazz));
				clientClasses.add(clazz.getCanonicalName());
			}
			AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
				@Override
				protected boolean match(ClassMetadata metadata) {
					String cleaned = metadata.getClassName().replaceAll("\\$", ".");
					return clientClasses.contains(cleaned);
				}
			};
			scanner.addIncludeFilter(
					new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
		}
		//遍历扫描包,遍历每一个BeanDefinition来注册
		for (String basePackage : basePackages) {
			Set<BeanDefinition> candidateComponents = scanner
					.findCandidateComponents(basePackage);
			for (BeanDefinition candidateComponent : candidateComponents) {
				if (candidateComponent instanceof AnnotatedBeanDefinition) {
					// verify annotated class is an interface
					AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
					AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
					Assert.isTrue(annotationMetadata.isInterface(),
							"@FeignClient can only be specified on an interface");

					Map<String, Object> attributes = annotationMetadata
							.getAnnotationAttributes(
									FeignClient.class.getCanonicalName());

					String name = getClientName(attributes);
                    //将FeignClient的配置类注册
					registerClientConfiguration(registry, name,
							attributes.get("configuration"));
					//注册具体的FeignClient
					registerFeignClient(registry, annotationMetadata, attributes);
				}
			}
		}
	}

	private void registerFeignClient(BeanDefinitionRegistry registry,
			AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
		String className = annotationMetadata.getClassName();
        //重点,创建了FeignClientFactoryBean
		BeanDefinitionBuilder definition = BeanDefinitionBuilder
				.genericBeanDefinition(FeignClientFactoryBean.class);
		validate(attributes);
		definition.addPropertyValue("url", getUrl(attributes));
		definition.addPropertyValue("path", getPath(attributes));
		String name = getName(attributes);
		definition.addPropertyValue("name", name);
		String contextId = getContextId(attributes);
		definition.addPropertyValue("contextId", contextId);
		definition.addPropertyValue("type", className);
		definition.addPropertyValue("decode404", attributes.get("decode404"));
		definition.addPropertyValue("fallback", attributes.get("fallback"));
		definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
		definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

		String alias = contextId + "FeignClient";
		AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();

		boolean primary = (Boolean) attributes.get("primary"); // has a default, won't be
																// null

		beanDefinition.setPrimary(primary);

		String qualifier = getQualifier(attributes);
		if (StringUtils.hasText(qualifier)) {
			alias = qualifier;
		}

		BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
				new String[] { alias });
        //加入到spring 容器
		BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
	}
 //到这里就结束了,是不是还没明白具体的FeignClient是怎么创建的? 连个配置对象在哪里应用的?
 // springCloud FeignClient其实是利用了spring的代理工厂来生成代理类,所以这里将所有的 feignClient的描述信息 BeanDefinition设定为 FeignClientFactoryBean类型,该类又继承 FactoryBean,很明显,这是一个代理类。 在spring中, FactoryBean是一个工厂bean,用作创建代理bean,所以得出结论,feign将所有的feignClient bean包装成 FeignClientFactoryBean。扫描方法到此结束。

//代理类什么时候会触发生成呢? 在spring刷新容器时,当实例化我们的业务service时,如果发现注册了FeignClient,spring就会去实例化该FeignClient,同时会进行判断是否是代理bean,如果为代理bean,则调用 FeignClientFactoryBean的 T getObject() throws Exception;方法生成代理bean。   
}

2、 FeignClientFactoryBean

源码走起

class FeignClientFactoryBean
		implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
    //先看实现类就知道它有什么功能,首先是一个FactoryBean,所以它是一个bean的代理工厂,通过getObject()方法生成指定类型的代理类。其次,实现了InitializingBean接口,它可以提供bean初始化时自定义参数的配置,最后ApplicationContextAware可以获取到容器,从而进行进一步操作。
    
    //实现FactoryBean的接口
    @Override
	public Object getObject() throws Exception {
		return getTarget();
	}
    //创建Feign的实现
    <T> T getTarget() {
        //创建Feign上下文?我们之前也没创建啊?哪里来的?
		FeignContext context = this.applicationContext.getBean(FeignContext.class);
        //根据上下文来生成builder对象(构造器模式),用来生成FeignClient
		Feign.Builder builder = feign(context);
		//根据url name 和path拼装路径(没配置url就走负载均衡,如果配置了不走负载均衡)
		if (!StringUtils.hasText(this.url)) {
			if (!this.name.startsWith("http")) {
				this.url = "http://" + this.name;
			}
			else {
				this.url = this.name;
			}
			this.url += cleanPath();
            //生成负载均衡的代理对象
			return (T) loadBalance(builder, context,
					new HardCodedTarget<>(this.type, this.name, this.url));
		}
		if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
			this.url = "http://" + this.url;
		}
		String url = this.url + cleanPath();
		Client client = getOptional(context, Client.class);
		if (client != null) {
			if (client instanceof LoadBalancerFeignClient) {
				// not load balancing because we have a url,
				// but ribbon is on the classpath, so unwrap
				client = ((LoadBalancerFeignClient) client).getDelegate();
			}
			if (client instanceof FeignBlockingLoadBalancerClient) {
				// not load balancing because we have a url,
				// but Spring Cloud LoadBalancer is on the classpath, so unwrap
				client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
			}
			builder.client(client);
		}
        //生成默认的代理对象
		Targeter targeter = get(context, Targeter.class);
		return (T) targeter.target(this, builder, context,
				new HardCodedTarget<>(this.type, this.name, url));
	}
    
    

3、FeignContext 的由来

第一步注解类分析完成之后,发现我们并没有创建这样一个类,那么它是怎么创建的? 既然我们没创建,而它又存在那么就想到了自动配置啊,

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({ FeignClientProperties.class,
		FeignHttpClientProperties.class })
@Import(DefaultGzipDecoderConfiguration.class)
public class FeignAutoConfiguration {
	//这里获取的FeignClientSpecification,就是我们第一步注解中放入到容器的配置。
	@Autowired(required = false)
	private List<FeignClientSpecification> configurations = new ArrayList<>();

	@Bean
	public HasFeatures feignFeature() {
		return HasFeatures.namedFeature("Feign", Feign.class);
	}
	//创建FeignContext,并把我们的配置表放到当中。
	@Bean
	public FeignContext feignContext() {
        //创建了FeignContext,
        //public FeignContext() {
		//super(FeignClientsConfiguration.class, "feign", "feign.client.name");
	//} 利用FeignClientsConfiguration.class 来创建。
		FeignContext context = new FeignContext();
		context.setConfigurations(this.configurations);
		return context;
	}

4、构造 FeignBuilder

Feign.Builder builder = feign(context); //该方法产生了Feign.Builder 
//进入方法内分析
protected Feign.Builder feign(FeignContext context) {
		FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
		Logger logger = loggerFactory.create(this.type);
		//创建Builder
		// @formatter:off
		Feign.Builder builder = get(context, Feign.Builder.class)
				// required values
				.logger(logger)
				.encoder(get(context, Encoder.class))
				.decoder(get(context, Decoder.class))
				.contract(get(context, Contract.class));
		// @formatter:on
		//配置Feign(下面跟进)
		configureFeign(context, builder);

		return builder;
	}

展开get(context, Feign.Builder.class),我们来看一下是如何获取builder的。

protected <T> T get(FeignContext context, Class<T> type) {
    //可以看出来,这个builder是从FeignContext中获取的,那么这个builder一定在context创建的时候被创建了出来。
    //如何创建的呢?通过FeignClientsConfiguration类创建,就是@bean注解。
		T instance = context.getInstance(this.contextId, type);
		if (instance == null) {
			throw new IllegalStateException(
					"No bean found of type " + type + " for " + this.contextId);
		}
		return instance;
	}
protected void configureFeign(FeignContext context, Feign.Builder builder) {
    	//获取配置文件生成的Properties类。
		FeignClientProperties properties = this.applicationContext
				.getBean(FeignClientProperties.class);
    	//如果配置文件不为null
		if (properties != null) {
            //判断配置文件是否配置了isDefaultToProperties()属性,根据下面逻辑推测出该属性的作用是是否根据properties里面的配置内容为准。
            //如果是,则先调用context里面的配置(也就是我们上面分析的@EnableFeignClient 和@FeignClient注解上面配置的属性来配置Builder,具体配置什么下面会说)
           //然后利用Properties就是配置文件里面的值覆盖它,这里注意,配置文件还分了统一配置 和 指定FeginClient的contextId的配置。
            //如果isDefaultToProperties()为false,则根据注解上面的配置为准。
			if (properties.isDefaultToProperties()) {
				configureUsingConfiguration(context, builder);
				configureUsingProperties(
						properties.getConfig().get(properties.getDefaultConfig()),
						builder);
				configureUsingProperties(properties.getConfig().get(this.contextId),
						builder);
			}
			else {
				configureUsingProperties(
						properties.getConfig().get(properties.getDefaultConfig()),
						builder);
				configureUsingProperties(properties.getConfig().get(this.contextId),
						builder);
				configureUsingConfiguration(context, builder);
			}
		}
		else {
            //如果properties没有配置,也是根据注解上面的配置稳准
			configureUsingConfiguration(context, builder);
		}
	}
//接下来看看Feign.Builder如何配置的

这里拿Properties来说明一下,和Configuration的逻辑差不多

protected void configureUsingProperties(
			FeignClientProperties.FeignClientConfiguration config,
			Feign.Builder builder) {
		if (config == null) {
			return;
		}
		//配置日志级别
		if (config.getLoggerLevel() != null) {
			builder.logLevel(config.getLoggerLevel());
		}
		//配置链接超时时间和读取超时时间
		if (config.getConnectTimeout() != null && config.getReadTimeout() != null) {
			builder.options(new Request.Options(config.getConnectTimeout(),
					config.getReadTimeout()));
		}
		//设置重试机制
		if (config.getRetryer() != null) {
			Retryer retryer = getOrInstantiate(config.getRetryer());
			builder.retryer(retryer);
		}
		//feign的错误code解析接口
		if (config.getErrorDecoder() != null) {
			ErrorDecoder errorDecoder = getOrInstantiate(config.getErrorDecoder());
			builder.errorDecoder(errorDecoder);
		}
		 //拦截器设置,可以看出拦截器也是可以针对单独的feignClient设置
		if (config.getRequestInterceptors() != null
				&& !config.getRequestInterceptors().isEmpty()) {
			// this will add request interceptor to builder, not replace existing
			for (Class<RequestInterceptor> bean : config.getRequestInterceptors()) {
				RequestInterceptor interceptor = getOrInstantiate(bean);
				builder.requestInterceptor(interceptor);
			}
		}
		//返回404 之后,解码策略
		if (config.getDecode404() != null) {
			if (config.getDecode404()) {
				builder.decode404();
			}
		}
		//编码策略配置		
		if (Objects.nonNull(config.getEncoder())) {
			builder.encoder(getOrInstantiate(config.getEncoder()));
		}
		//解码策略配置
		if (Objects.nonNull(config.getDecoder())) {
			builder.decoder(getOrInstantiate(config.getDecoder()));
		}
		//连接器----springmvc 类似于适配器
		if (Objects.nonNull(config.getContract())) {
			builder.contract(getOrInstantiate(config.getContract()));
		}
		//异常传播规则
		if (Objects.nonNull(config.getExceptionPropagationPolicy())) {
			builder.exceptionPropagationPolicy(config.getExceptionPropagationPolicy());
		}
	}

5、负载均衡客户端分析

if (!StringUtils.hasText(this.url)) {
			if (!this.name.startsWith("http")) {
				this.url = "http://" + this.name;
			}
			else {
				this.url = this.name;
			}
			this.url += cleanPath();
    //loadBalance方法返回FeignClient
			return (T) loadBalance(builder, context,
					new HardCodedTarget<>(this.type, this.name, this.url));
		}

进入#loadBalance

protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
			HardCodedTarget<T> target) {
    //这里获取到了Client,这里的Client存在三种实现类,会根据你项目整合ribbon的情况生成不同的client
    //@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
    //会创建apacheClient或者okhttpClient
    //如果集成了openFeign。, FeignRibbonClientAutoConfiguration 自动配置就生成了LoadBalancerFeignClient()
		Client client = getOptional(context, Client.class);
		if (client != null) {
			builder.client(client);
			Targeter targeter = get(context, Targeter.class);
			return targeter.target(this, builder, context, target);
		}

		throw new IllegalStateException(
				"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
	}

6、Targeter 分析

Targeter targeter = get(context, Targeter.class);//获取自动配置创建的Targeter

FeignAutoConfiguration 里面创建了Targeter,根据有没有引入Hystrix创建不同的Targeter
@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(name = "feign.hystrix.HystrixFeign")
	protected static class HystrixFeignTargeterConfiguration {

		@Bean
		@ConditionalOnMissingBean
		public Targeter feignTargeter() {
			return new HystrixTargeter();
		}

	}

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
	protected static class DefaultFeignTargeterConfiguration {

		@Bean
		@ConditionalOnMissingBean
		public Targeter feignTargeter() {
			return new DefaultTargeter();
		}

	}
//最后通过该方法返回FeignClient,进入HystrixTargeter内部继续跟进
return targeter.target(this, builder, context, target);

HystrixTargeter#target()

public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
			FeignContext context, Target.HardCodedTarget<T> target) {
    	//如果不是HystrixFeign 就是没有熔断器的话直接调用builder.target()创建一个FeginClient
		if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
			return feign.target(target);
		}
		feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
		String name = StringUtils.isEmpty(factory.getContextId()) ? factory.getName()
				: factory.getContextId();
    //根据Feign Client的名字去context中获取setter工厂
		SetterFactory setterFactory = getOptional(name, context, SetterFactory.class);
		if (setterFactory != null) {
			builder.setterFactory(setterFactory);
		}
    //返回具有fallback函数的FeignClient
		Class<?> fallback = factory.getFallback();
		if (fallback != void.class) {
			return targetWithFallback(name, context, target, builder, fallback);
		}
   	//返回具有fallbackFactory的FeignClient
		Class<?> fallbackFactory = factory.getFallbackFactory();
		if (fallbackFactory != void.class) {
			return targetWithFallbackFactory(name, context, target, builder,
					fallbackFactory);
		}

		return feign.target(target);
	}

targetWithFallback(name, context, target, builder, fallback)源码

private <T> T targetWithFallback(String feignClientName, FeignContext context,
			Target.HardCodedTarget<T> target, HystrixFeign.Builder builder,
			Class<?> fallback) {
    //获取fallbakcInstance,从FeignContext中。
		T fallbackInstance = getFromContext("fallback", feignClientName, context,
				fallback, target.type());
    //还是调用Builder.target方法来创建代理对象
		return builder.target(target, fallbackInstance);
	}

feign.hystrix.HystrixFeign.Builder#target(feign.Target, T)

public <T> T target(Target<T> target, T fallback) {
      return build(fallback != null ? new FallbackFactory.Default<T>(fallback) : null)
          .newInstance(target);
    }

builid 最终会调用Feign的build();

public Feign build() {
    //MethodHandlerFactory,为我们@feign标记的接口的方法创建对应的Handler
      SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
          new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
              logLevel, decode404, closeAfterDecode, propagationPolicy, forceDecoding);
      ParseHandlersByName handlersByName =
          new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
              errorDecoder, synchronousMethodHandlerFactory);
    //可以看到这里真正返回的是ReflectiveFeifn,代理类的工具类,在通过其newInstance()创建代理对象
      return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
    }
  }

public <T> T newInstance(Target<T> target) {
    //target 也就是我们目标的FeignClient接口,生成名称和方法holder对应的Map
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    //初始化方法和对应方法Handler对象映射的map
    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
	//遍历target(目标接口)的方法,为目标接口的每一个方法生成对应的MethodHandler对象
    for (Method method : target.type().getMethods()) {
      if (method.getDeclaringClass() == Object.class) {
        continue;
      } else if (Util.isDefault(method)) {
        DefaultMethodHandler handler = new DefaultMethodHandler(method);
        defaultMethodHandlers.add(handler);
        methodToHandler.put(method, handler);
      } else {
        methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
      }
    }
    //通过InvocationHandlerFactory 创建 InvocationHandler(实现类为FeignInvocationHandler),这个handler里面有一个method---handler 的map,存储每一个method对应的handler,也就是每一个client请求。
    InvocationHandler handler = factory.create(target, methodToHandler);
    //创建对应FeignClient的代理对象,
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
        new Class<?>[] {target.type()}, handler);

    for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
      defaultMethodHandler.bindTo(proxy);
    }
    return proxy;
  }

总结:由以上分析可知,FeignClientFactoryBean.getObject()具体返回的是一个代理类,具体为FeignInvocationHandler

接下来分析一下FeignInvocationHandler接收请求方法过程

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if ("equals".equals(method.getName())) {
        try {
            Object
                otherHandler =
                args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
            return equals(otherHandler);
        } catch (IllegalArgumentException e) {
            return false;
        }
    } else if ("hashCode".equals(method.getName())) {
        return hashCode();
    } else if ("toString".equals(method.getName())) {
        return toString();
    }
    // 非Object方法,则默认执行该句
    // dispatch为map,方法的实现类为SynchronousMethodHandler
    // 我们来分析SynchronousMethodHandler.invoke()方法
    return dispatch.get(method).invoke(args);
}

SynchronousMethodHandler.invoke()

public Object invoke(Object[] argv) throws Throwable {
    // 1.根据请求参数创建一个feign.RequestTemplate
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    // 2.用户定义的重试策略
    Retryer retryer = this.retryer.clone();
    while (true) {
        try {
            // 重要方法在这里
            return executeAndDecode(template);
        } catch (RetryableException e) {
            retryer.continueOrPropagate(e);
            if (logLevel != Logger.Level.NONE) {
                logger.logRetry(metadata.configKey(), logLevel);
            }
            continue;
        }
    }
}

executeAndDecode(template)执行请求

Object executeAndDecode(RequestTemplate template) throws Throwable {
    // 1.封装请求信息,feign.Request,会将请求封装为以下信息
    // GET http://part-1-sms-interface/sms/test HTTP/1.1
    //相当于我们原生Feign的@requestLine注解
    Request request = targetRequest(template);
 
    if (logLevel != Logger.Level.NONE) {
        logger.logRequest(metadata.configKey(), logLevel, request);
    }
 
    Response response;
    long start = System.nanoTime();
    try {
        // 2.真正的执行在这里
        // client为LoadBalancerFeignClient
        // 继续在3)中详细分析
        response = client.execute(request, options);
        // ensure the request is set. TODO: remove in Feign 10
        response.toBuilder().request(request).build();
    } catch (IOException e) {
        if (logLevel != Logger.Level.NONE) {
            logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
        }
        throw errorExecuting(request, e);
    }
    long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
 
    // 响应处理
    ...
}

LoadBalancerFeignClient.execute(request, options)请求负载均衡

public Response execute(Request request, Request.Options options) throws IOException {
    try {
        // 1.获取URI
        URI asUri = URI.create(request.url());
        String clientName = asUri.getHost();
        URI uriWithoutHost = cleanUrl(request.url(), clientName);
        
        // 2.封装成RibbonRequest请求
        // 可以看到这里把host剔除的uri传入进去,我们猜测就是让Ribbon去注册中心拿host,在拼接进去,就可以实现客户端的负载了
        FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
            this.delegate, request, uriWithoutHost);
 
        // 3.封装请求参数信息
        IClientConfig requestConfig = getClientConfig(options, clientName);
        
        // 4.执行请求,并进行负载均衡
        // 本方法可分为三步:
        // 1)lbClient(clientName)获取执行类,本例中为FeignLoadBalancer
        // 2)FeignLoadBalancer.executeWithLoadBalancer()执行请求
        // 3)toResponse()获取响应
        return lbClient(clientName).executeWithLoadBalancer(ribbonRequest,
                                                            requestConfig).toResponse();
    }
    catch (ClientException e) {
        IOException io = findIOException(e);
        if (io != null) {
            throw io;
        }
        throw new RuntimeException(e);
    }
}

FeignLoadBalancer.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 {
        // 在这里可以看到Hystrix的相关代码,
        return command.submit(
            new ServerOperation<T>() {
                @Override
                public Observable<T> call(Server server) {
                    URI finalUri = reconstructURIWithServer(server, request.getUri());
                    S requestForServer = (S) request.replaceUri(finalUri);
                    try {
                        // 执行ribbon负载均衡请求
                        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);
        }
    }
 
}

总结:

1.@EnableFeignClients注解将所有带有@FeignClient的类或接口注册到Spring中,注册类为FeignClientFactoryBean

2.FeignClientFactoryBean.getObject()方法返回的是一个代理类,InvocationHandler中包含类中每个方法对应的MethodHandler,也就是SynchronousMethodHandler,方法真正执行就是SynchronousMethodHandler.invoke()方法

3.LoadBalancerFeignClient.execute()方法进行业务的处理,在这一步操作中就用到了ribbon和Hystrix功能

整个流程就这样结束了,总结一下:

​ Feign调用方发起请求,发送至hystrixInvocationHandler(代理类).invoke(),通过服务名称,找到方法对应的methodHandler,而methodhandler中封装了loadBalanceClient、retryer、RequestInterceptor等组件。method Handler.invoke()-----> excuteDecode()-------> client.excute()----->loadBanlanceClient.excute()-----FeignLoadBalancer.executeWithLoadBalancer()-------->得到resonse Decode 为 Object并返回

7、生成默认代理类

不管是哪种代理类,最终发起请求还是由Fegin.Default.execute方法完成,默认使用的是HttpUrlConnecttion实现。

8、注入Spring 容器

通过ApplicationContext.refresh(),触发FeignClientFactoryBean.getObject()方法获得了代理类,然后完成注入spring容器的过程,

``Throwable t = e.getCause();
if (t instanceof ClientException) {
throw (ClientException) t;
} else {
throw new ClientException(e);
}
}

}``
总结:

1.@EnableFeignClients注解将所有带有@FeignClient的类或接口注册到Spring中,注册类为FeignClientFactoryBean

2.FeignClientFactoryBean.getObject()方法返回的是一个代理类,InvocationHandler中包含类中每个方法对应的MethodHandler,也就是SynchronousMethodHandler,方法真正执行就是SynchronousMethodHandler.invoke()方法

3.LoadBalancerFeignClient.execute()方法进行业务的处理,在这一步操作中就用到了ribbon和Hystrix功能

整个流程就这样结束了,总结一下:

​ Feign调用方发起请求,发送至hystrixInvocationHandler(代理类).invoke(),通过服务名称,找到方法对应的methodHandler,而methodhandler中封装了loadBalanceClient、retryer、RequestInterceptor等组件。method Handler.invoke()-----> excuteDecode()-------> client.excute()----->loadBanlanceClient.excute()-----FeignLoadBalancer.executeWithLoadBalancer()-------->得到resonse Decode 为 Object并返回

7、生成默认代理类

不管是哪种代理类,最终发起请求还是由Fegin.Default.execute方法完成,默认使用的是HttpUrlConnecttion实现。

8、注入Spring 容器

通过ApplicationContext.refresh(),触发FeignClientFactoryBean.getObject()方法获得了代理类,然后完成注入spring容器的过程,

ItVuer - 免责声明 - 关于我们 - 联系我们

本网站信息来源于互联网,如有侵权请联系:561261067@qq.com

桂ICP备16001015号