微服务学习之Consul与Feign

微服务学习之Consul&&Feign

1、Consul

CAP原理:

​ Consistency(一致性) :

​ Availability(可用性):

​ Partition Tolerance(分区容错):

Eureka : 保证 AP Consul:则是 CP

1、各个注册中心的比较

FeatureeuerkaConsulzookeeperetcd
服务健康检查可配支持服务状态,内存,硬盘等(弱)长连接,keepalive连接心跳
多数据中心支持
kv 存储服务支持支持支持
一致性raftpaxosraft
capapcpcpcp
使用接口(多语言能力)http(sidecar)支持 http 和 dns客户端http/grpc
watch 支持支持 long polling/大部分增量全量/支持long polling支持支持 long polling
自身监控metricsmetricsmetrics
安全acl /httpsaclhttps 支持(弱)
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定义

/**
 * <p></p>
 *
 * @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容器的过程,

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值