feign-ribbon-hystrix解码
Posted 永辉技术
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了feign-ribbon-hystrix解码相关的知识,希望对你有一定的参考价值。
[TOC]
前言
在永辉内部我们大量使用spring cloud开发微服务应用,历史背景这里不做赘述,我们在其的实践也一直在进阶的路上。
在我们内部服务提供方大多都是以HTTP接口的形势对外提供服务,因此在服务消费者调用其服务时,底层都是通过HTTP Client的方式访问。通常情况下,可以直接使用JDK原生的URLConnection
、Apache的HTTP Client、Netty的异步HTTP Client、OkHttp以及Spring的RestTemplate
去实现服务的调用,而spring cloud的Open Feign是市场上使用最广、最方便、最优雅的客户端消费工具,优雅在于一如既往的弘扬元数据编程。
对于Feign-Hystrix-Ribbon,其实很多人并不熟悉它的底层实现原理,究其原因大概有以下几种:
•spring cloud大量耦合spring boot(也可以叫融合)体系更复杂,学习成本陡峭•很多一手英文资料无法参透,无法准确理解其实现的背后意义•基本功不足(J2EE规范、RPC等)
接下来,笔者本将从另一个视角去探讨Feign-Hystrix-Ribbon的特性及其实现原理.......
1.what is Feign?
spring-cloud-openfeign 在 Github 描述了其特性[1]:
Declarative REST Client: Feign creates a dynamic implementation of an interface decorated with JAX-RS or Spring MVC annotations.
Feign是一个声明式的HTTP客户端,它可以做到使用HTTP请求访问调用远程服务,就像调用本地方法一样的,开发者完全感知不到在调用远程方法,更感知不到访问HTTP请求。它包含以下特性:
•可插拔的注解支持,包含Feign注解和spring mvc的注解,二者可以并存•支持可插拔的HTTP编码器和解码器•支持Ribbon的负载均衡•支持Hystrix和它的Fallback•支持HTTP请求和响应的压缩
用法
如果你是spring-boot项目这样就可以了,将会变得非常简单!
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
<version>1.4.4.RELEASE</version>
</dependency>
@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
@FeignClient(name = "USER", fallbackFactory = UserServiceFallback.class)
public interface UserService {//contract
@GetMapping("/users/{id}")//spring mvc
User getUser(@PathVariable("id") String id);
}
@FeignClient(name = "USER", url = "http://xxxx.xx")
public interface UserService2 {
@RequestLine("POST /pay/getStatus")//feign原生
User getUser(String id);
}
//英文
public @interface FeignClient {
/**
* The name of the service with optional protocol prefix. Synonym for {@link #name()
* name}. A name must be specified for all clients, whether or not a url is provided.
* Can be specified as property key, eg: ${propertyKey}.
*/
@AliasFor("name")
String value() default "";
/**
* The service id with optional protocol prefix. Synonym for {@link #value() value}.
*/
@AliasFor("value")
String name() default "";
/**
* Sets the <code>@Qualifier</code> value for the feign client.
*/
String qualifier() default "";
/**
* An absolute URL or resolvable hostname (the protocol is optional).
*/
String url() default "";
/**
* 当发生404错误时,如果该字段为true,会调用decoder进行解码,否则抛出FeignException
*/
boolean decode404() default false;
/**
* A custom <code>@Configuration</code> for the feign client. Can contain override
* <code>@Bean</code> definition for the pieces that make up the client, for instance
* {@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}.
*
* @see FeignClientsConfiguration for the defaults
*/
Class<?>[] configuration() default {};
/**
* 定义容错的处理类,当调用远程接口失败或者超时时,会调用对应的接口的容错逻辑,fallback指定的类必须实现@FeignClient标记的接口.
*/
Class<?> fallback() default void.class;
/**
* 工厂类,用于生成fallback类实例,通过这个属性可以实现每个接口通用的容错逻辑们介绍重复的代码
*
* @see feign.hystrix.FallbackFactory for details.
*/
Class<?> fallbackFactory() default void.class;
/**
* 定义当前FeignClient的统一前缀. Can be used with or without
* <code>@RibbonClient</code>.
*/
String path() default "";
}
配置
feign的配置不算特别负载,主要有:FeignClientProperties
、FeignClientEncodingProperties
、FeignHttpClientProperties
。
你需要关注的是多层级配置加载顺序~,后续结合代码会谈到
feign.client.config.defalut.connectTimeout=5000
#局部配置
feign.client.config.user.connectTimeout=5000
feign.hystrix.enabled=true
工作原理
1.@EnableFeignClients
注解开启对Feign Client(加@FeignClient
注解的接口)扫描加载处理,并将这些信息注入Spring IoC容器中。2.当Feign接口中的方法被调用时,通过JDK的代理方式,生成代理时,Feign会为每个接口方法创建一个RequestTemplate
对象(它包含HTTP请求需要的全部信息,如请求参数名,请求方法等信息)3.最终RequestTemplate
生成请求并调用Client(JDK原生的URLConnection、Apache的HTTP Client)发送请求。
主要组件
EnableFeignClients
和FeignAutoConfiguration
按照常规经验去解剖源码:
• 第一步:隐式模式(用户不需要做什么,但你要知道),spirng boot会自动加载Feign的配置类FeignAutoConfiguration
(spring-cloud-netflix-core-1.4.4.RELEASE.jar/META-INF/spring.factories),为Feign提供运行所需要的环境(各种相关对象)• 第二步:应用系统启动类中添加@EnableFeignClients
,它的作用是自动扫描注册标记为 @FeignClient
的用户定义的接口,动态创建实现类(准确的应该叫代理类)并注入到Ioc容器中。
在调用接口时,会根据接口上的注解信息来创建RequestTemplate
,结合实际调用时的参数来创建请求,最后完成调用。
至此,你可能不太了解它内部构造,没错!融合了spring boot的feign的确是云里雾里,如果你觉得下面内容有极度的不适应感,那就直接跳过,读完整篇后再回头来看会豁然开朗。
@Import({FeignClientsRegistrar.class})
public @interface EnableFeignClients {
String[] value() default {};
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<?>[] defaultConfiguration() default {};
Class<?>[] clients() default {};
}
//用于处理 FeignClient 的全局配置和被 @FeignClient 标记的接口
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, BeanClassLoaderAware {
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 处理默认配置类(EnableFeignClients.defaultConfiguration属性)
this.registerDefaultConfiguration(metadata, registry);
// 注册被 @FeignClient 标记的接口
this.registerFeignClients(metadata, registry);
}
private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
Map<String, Object> defaultAttrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
String name;
if (metadata.hasEnclosingClass()) {
name = "default." + metadata.getEnclosingClassName();
} else {
name = "default." + metadata.getClassName();
}
this.registerClientConfiguration(registry, name, defaultAttrs.get("defaultConfiguration"));
}
}
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// classpath scan工具
ClassPathScanningCandidateComponentProvider scanner = this.getScanner();
scanner.setResourceLoader(this.resourceLoader);
Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
// 利用FeignClient作为过滤条件
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(FeignClient.class);
Class<?>[] clients = attrs == null ? null : (Class[])((Class[])attrs.get("clients"));
...
// 注册
this.registerFeignClient(registry, annotationMetadata, attributes);
}
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
// 拿到FeignClientFactoryBean的BeanDefinitionBuilder
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
this.validate(attributes);
definition.addPropertyValue("url", this.getUrl(attributes));
definition.addPropertyValue("path", this.getPath(attributes));
String name = this.getName(attributes);
definition.addPropertyValue("name", name);
definition.addPropertyValue("type", className);
definition.addPropertyValue("decode404", attributes.get("decode404"));
definition.addPropertyValue("fallback", attributes.get("fallback"));
definition.setAutowireMode(2);
String alias = name + "FeignClient";
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
beanDefinition.setPrimary(true);
String qualifier = this.getQualifier(attributes);
if (StringUtils.hasText(qualifier)) {
alias = qualifier;
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[]{alias});
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
}
//自动配置类
@Configuration
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({FeignClientProperties.class, FeignHttpClientProperties.class})
public class FeignAutoConfiguration {
@Autowired(required = false)
private List<FeignClientSpecification> configurations = new ArrayList<>();
@Bean
public HasFeatures feignFeature() {
return HasFeatures.namedFeature("Feign", Feign.class);
}
@Bean
public FeignContext feignContext() {
//加载FeignClientsConfiguration配置类
FeignContext context = new FeignContext();
context.setConfigurations(this.configurations);
return context;
}
@Configuration
@ConditionalOnClass(name = "feign.hystrix.HystrixFeign")
protected static class HystrixFeignTargeterConfiguration {
@Bean
@ConditionalOnMissingBean
public Targeter feignTargeter() {
return new HystrixTargeter();
}
}
@Configuration
@ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
protected static class DefaultFeignTargeterConfiguration {
@Bean
@ConditionalOnMissingBean
public Targeter feignTargeter() {
return new DefaultTargeter();
}
}
// the following configuration is for alternate feign clients if
// ribbon is not on the class path.
// see corresponding configurations in FeignRibbonClientAutoConfiguration
// for load balanced ribbon clients.
@Configuration
@ConditionalOnClass(ApacheHttpClient.class)
@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
@ConditionalOnMissingBean(CloseableHttpClient.class)
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
protected static class HttpClientFeignConfiguration {//Client为HttpClient
private final Timer connectionManagerTimer = new Timer(
"FeignApacheHttpClientConfiguration.connectionManagerTimer", true);
@Autowired(required = false)
private RegistryBuilder registryBuilder;
private CloseableHttpClient httpClient;
@Bean
@ConditionalOnMissingBean(HttpClientConnectionManager.class)
public HttpClientConnectionManager connectionManager(
ApacheHttpClientConnectionManagerFactory connectionManagerFactory,
FeignHttpClientProperties httpClientProperties) {
final HttpClientConnectionManager connectionManager = connectionManagerFactory
.newConnectionManager(httpClientProperties.isDisableSslValidation(), httpClientProperties.getMaxConnections(),
httpClientProperties.getMaxConnectionsPerRoute(),
httpClientProperties.getTimeToLive(),
httpClientProperties.getTimeToLiveUnit(), registryBuilder);
this.connectionManagerTimer.schedule(new TimerTask() {
@Override
public void run() {
connectionManager.closeExpiredConnections();
}
}, 30000, httpClientProperties.getConnectionTimerRepeat());
return connectionManager;
}
@Bean
public CloseableHttpClient httpClient(ApacheHttpClientFactory httpClientFactory,
HttpClientConnectionManager httpClientConnectionManager,
FeignHttpClientProperties httpClientProperties) {
RequestConfig defaultRequestConfig = RequestConfig.custom()
.setConnectTimeout(httpClientProperties.getConnectionTimeout())
.setRedirectsEnabled(httpClientProperties.isFollowRedirects())
.build();
this.httpClient = httpClientFactory.createBuilder().
setConnectionManager(httpClientConnectionManager).
setDefaultRequestConfig(defaultRequestConfig).build();
return this.httpClient;
}
@Bean
@ConditionalOnMissingBean(Client.class)
public Client feignClient(HttpClient httpClient) {
return new ApacheHttpClient(httpClient);
}
@PreDestroy
public void destroy() throws Exception {
connectionManagerTimer.cancel();
if(httpClient != null) {
httpClient.close();
}
}
}
@Configuration
@ConditionalOnClass(OkHttpClient.class)
@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
@ConditionalOnMissingBean(okhttp3.OkHttpClient.class)
@ConditionalOnProperty(value = "feign.okhttp.enabled")
protected static class OkHttpFeignConfiguration {//Client为okhttp
private okhttp3.OkHttpClient okHttpClient;
@Bean
@ConditionalOnMissingBean(ConnectionPool.class)
public ConnectionPool httpClientConnectionPool(FeignHttpClientProperties httpClientProperties,
OkHttpClientConnectionPoolFactory connectionPoolFactory) {
Integer maxTotalConnections = httpClientProperties.getMaxConnections();
Long timeToLive = httpClientProperties.getTimeToLive();
TimeUnit ttlUnit = httpClientProperties.getTimeToLiveUnit();
return connectionPoolFactory.create(maxTotalConnections, timeToLive, ttlUnit);
}
@Bean
public okhttp3.OkHttpClient client(OkHttpClientFactory httpClientFactory,
ConnectionPool connectionPool, FeignHttpClientProperties httpClientProperties) {
Boolean followRedirects = httpClientProperties.isFollowRedirects();
Integer connectTimeout = httpClientProperties.getConnectionTimeout();
Boolean disableSslValidation = httpClientProperties.isDisableSslValidation();
this.okHttpClient = httpClientFactory.createBuilder(disableSslValidation).
connectTimeout(connectTimeout, TimeUnit.MILLISECONDS).
followRedirects(followRedirects).
connectionPool(connectionPool).build();
return this.okHttpClient;
}
@PreDestroy
public void destroy() {
if(okHttpClient != null) {
okHttpClient.dispatcher().executorService().shutdown();
okHttpClient.connectionPool().evictAll();
}
}
@Bean
@ConditionalOnMissingBean(Client.class)
public Client feignClient() {
return new OkHttpClient(this.okHttpClient);
}
}
}
@Configuration
public class FeignClientsConfiguration {
@Bean
@ConditionalOnMissingBean
public Decoder feignDecoder() {
return new ResponseEntityDecoder(new SpringDecoder(this.messageConverters));
}
@Bean
@ConditionalOnMissingBean
public Encoder feignEncoder() {
return new SpringEncoder(this.messageConverters);
}
@Bean
@ConditionalOnMissingBean//contract为spring mvc
public Contract feignContract(ConversionService feignConversionService) {
return new SpringMvcContract(this.parameterProcessors, feignConversionService);
}
@Configuration
@ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
protected static class HystrixFeignConfiguration {
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "feign.hystrix.enabled", matchIfMissing = false)
public Feign.Builder feignHystrixBuilder() {
return HystrixFeign.builder();
}
}
@Bean
@ConditionalOnMissingBean
public Retryer feignRetryer() {
return Retryer.NEVER_RETRY;
}
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
public Feign.Builder feignBuilder(Retryer retryer) {
return Feign.builder().retryer(retryer);
}
@Bean
@ConditionalOnMissingBean(FeignLoggerFactory.class)
public FeignLoggerFactory feignLoggerFactory() {
return new DefaultFeignLoggerFactory(logger);
}
}
2.what is Ribbon?
Ribbon是Netflix公司于2013年开放的一个客户端负载均衡组件,它一直是Netflix活跃度较高的项目。为了便于理解这块,拿nginx来谈,nginx是一个高性能的HTTP和反向代理服务器!
ribbon在github中的描述:[2]
Ribbon is a client side IPC library that is battle-tested in cloud. It provides the following features
•Load balancing•Fault tolerance•Multiple protocol (HTTP, TCP, UDP) support in an asynchronous and reactive model•Caching and batching
反向代理 VS 正向代理
何为反向代理,正向代理又是什么鬼?
正向代理
意思是一个位于客户端和原始服务器之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器)。正向代理是为客户端服务的,对我们是透明的,对服务端是非透明的,客户端可以根据正向代理访问到它本身无法访问到的服务器资源,而服务端并不知道自己收到的是来自代理的访问还是来自真实客户端的访问。
你可以简单的理解问题:客户端负载均衡,它适用于企业内部微服务之间(内网)
反向代理
Reverse Proxy 是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,此时代理服务器对外就表现为一个反向代理服务器。反向代理是为服务端服务的,对服务端是透明的,对我们是非透明的,反向代理可以帮助服务器接收来自客户端的请求,帮助服务器做请求转发,负载均衡等。
你可以简单的理解问题:服务端负载均衡,它适用于外网接口
而反向代理服务中,只有负载非常简单
server {
server_name xx.server.com;
listen 80;
location /api {
proxy_pass http://balanceServer;
}
}
//默认轮询,这种策略是可以正常工作的,但是如果其中某一台服务器压力太大,出现延迟,会影响所有分配在这台服务器下的用户
upstream balanceServer {
server 10.1.22.33:12345;
server 10.1.22.34:12345;
server 10.1.22.35:12345;
}
//最小连接数策略,将请求优先分配给压力较小的服务器,它可以平衡每个队列的长度,并避免向压力大的服务器添加更多的请求
upstream balanceServer {
least_conn;
server 10.1.22.33:12345;
server 10.1.22.34:12345;
server 10.1.22.35:12345;
}
//最快响应时间策略,依赖于NGINX Plus,优先分配给响应时间最短的服务器。
upstream balanceServer {
fair;
server 10.1.22.33:12345;
server 10.1.22.34:12345;
server 10.1.22.35:12345;
}
//客户端ip绑定,来自同一个ip的请求永远只分配一台服务器,有效解决了动态网页存在的session共享问题
upstream balanceServer {
ip_hash;
server 10.1.22.33:12345;
server 10.1.22.34:12345;
server 10.1.22.35:12345;
}
对比是更多为了加强认知,对于负载均衡组件而言Ribbon重新定义了动态化,它主要包括如下功能:
•RoundRobinRule,轮训策略,默认策略•RandomRule,随机,使用Random
对象从服务列表中随机选择一个服务•RetryRule,轮询 + 重试•WeightedResponseTimeRule,优先选择响应时间快,此策略会根据平均响应时间计算所有服务的权重,响应时间越快,服务权重越重、被选中的概率越高。此类有个DynamicServerWeightTask
的定时任务,默认情况下每隔30秒会计算一次各个服务实例的权重。刚启动时,如果统计信息不足,则使用RoundRobinRule
策略,等统计信息足够,会切换回来•AvailabilityFilteringRule,可用性过滤,会先过滤掉以下服务:由于多次访问故障而断路器处于打开的服务、并发的连接数量超过阈值,然后对剩余的服务列表按照RoundRobinRule
策略进行访问•BestAvailableRule,优先选择并发请求最小的,刚启动时吗,如果统计信息不足,则使用RoundRobinRule
策略,等统计信息足够,才会切换回来•ZoneAvoidanceRule,可以实现避免可能访问失效的区域(zone)
用法
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
用法上并无差异,要么通过注解RibbonClient
要么通过配置,配置文件方式配置 Ribbon格式一般为:
1.NFLoadBalancerClassName
:配置 ILoadBalancer
的实现类2.NFLoadBalancerRuleClassName
:配置 IRule
的实现类3.NFLoadBalancerPingClassName
:配置IPing
实现类4.NIWSServerListClassName
:配置 ServerList
的实现类5.NIWSServerListFilterClassName
:配置 ServerListFilter
的实现类
user.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
@Configuration
public class RibbonConfiguration {
@Bean
public IRule ribbonRule(){
return new RandomRule();
}
}
@EnableDiscoveryClient
@SpringBootApplication
@RibbonClient(name = "user",configuration = RibbonConfiguration.class)//等价于上面配置
public class DemoApplication {
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
基本原理
通常情况下,ribbon一般情况下是和Eureka结合使用的,它会从Eureka中读取ServerList(服务注册信息列表)来达到动态负载的功能。
在某些存在安全风险(不允许读取Eureka)也可以脱机使用静态ServerList,配置如下:
ribbon.eureka.enabled=false
user.ribbon.listOfServers=http://xxx1:8080,http://xxx2:8081,http://xxx3:8082
主要组件
1.IRule,
2.IPing,接口定义了我们如何“ping”服务器检查是否活着
1.PingUrl ,使用HttpClient调用服务的一个URL,如果调用成功,则认为本次心跳成功,表示此服务活着2.NoOpPing ,永远返回true,即认为服务永远活着
3.ServerList,定义了获取服务器的列表接口,存储服务列表,服务列表分为静态和动态。如果是动态的,后台有个线程会定时刷新和过滤服务列表
1.ConfigurationBasedServerList ,配置文件中获取所有服务列表,比如:yh-oms.ribbon.listOfServers=www.microsoft.com:80,www.yahoo.com:80,www.google.com:80[3]2.DiscoveryEnabledNIWSServerList,从Eureka Client中获取服务列表。此值必须通过属性中的VipAddress来标识服务器集群
4.ServerListFilter,接口允许过滤配置或动态获得的候选列表服务器,可以认为多机房或多中心场景准备的5.ServerListUpdater,接口使用不同的方法来做动态更新服务器列表
1.PollingServerListUpdater,默认的实现策略。此对象会启动一个定时线程池,定时执行更新策略2.EurekaNotificationServerListUpdater,当收到缓存刷新的通知,会更新服务列表
6.IClientConfig,定义了各种api所使用的客户端配置,用来初始化ribbon客户端和负载均衡器,默认实现是DefaultClientConfigImpl1.要结合Netflix Archaius来看
7.ILoadBalancer,接口定义了各种软负载,动态更新一组服务列表及根据指定算法从现有服务器列表中选择一个服务1.DynamicServerListLoadBalancer,它组合Rule、IPing、ServerList、ServerListFilter、ServerListUpdater 实现类,实现动态更新和过滤更新服务列表2.ZoneAwareLoadBalancer,它是上一个的子类,主要加入zone的因素。统计每个zone的平均请求的情况,保证从所有zone选取对当前客户端服务最好的服务组列表
3.what is Hystrix?
Hystrix官方的描述[4]
In a distributed environment, inevitably some of the many service dependencies will fail. Hystrix is a library that helps you control the interactions between these distributed services by adding latency tolerance and fault tolerance logic. Hystrix does this by isolating points of access between the services, stopping cascading failures across them, and providing fallback options, all of which improve your system’s overall resiliency.
简单的讲,微服务中,服务间依赖重重,如何提高每个系统的可用性变得非常棘手!通过隔离,可用很好的控制住风险范围,再结合请求拒绝和超时控制,有效剔除 “老鼠屎”,避免坏了一锅粥。Netflix 将该组件取名为 Hystrix,寓意应该是:当系统受到伤害时,能够像豪猪的棘刺一样保护系统。
关于可用性,例如,对于依赖于30个服务的应用程序,其中每个服务的正常运行时间为99.99%,您可以期望:
99.9930 = 99.7% uptime 0.3% of 1 billion requests = 3,000,000 failures 2+ hours downtime/month even if all dependencies have excellent uptime.
可现实是,即使您没有对整个系统进行弹性设计,即使所有依赖项都能很好地执行,即使宕机0.01%,对数十种服务中的每一项的总体影响也可能相当于每月停机数小时。
当某个依赖不可用是这样的(图片来源于官方):
它工作后是这样的(图片来源于官方):
Fail Fast、Fallback比较好理解,Fail-Silent可参阅wiki[5]
Hystrix主要提供了以下功能点:
1.熔断器(Circuit Breaker)2.隔离(Isolation),提供璧仓模式,实现了线程池隔离和信号量隔离3.回退(fallback),Hystrix会在run()执行过程中出现错误、超时、线程池拒绝、断路器熔断等情况时进行降级处理,有default fallback、单级fallback、多级fallback。4.请求合并(Request Collapsing),@HystrixCollapser
,适用于请求的合并,通过指定时间窗口@HystrixProperty
(name = "timerDelayInMilliseconds", value = "50")及@HystrixProperty
(name = "maxRequestsInBatch", value = "200")来执行批量方法,暂时不展开讲。5.请求缓存(Request Caching)6.仪表盘
用法
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
@SpringCloudApplication
@EnableHystrix
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
@Service
public class UserService {
@HystrixCommand(fallbackMethod = "getUserDefault2")
public User getUserDefault(Long id) {
String a = null;
// 测试服务降级
a.toString();
User user = new User();
user.setId(-1L);
user.setUsername("defaultUser");
return user;
}
public User getUserDefault2(Long id, Throwable e) {
System.out.println(e.getMessage());
User user = new User();
user.setId(-2L);
user.setUsername("defaultUser2");
return user;
}
}
Hystrix有两个请求命令 HystrixCommand
、HystrixObservableCommand
HystrixCommand
用在依赖服务返回单个操作结果的时候:
•execute():同步执行。从依赖的服务返回一个单一的结果对象,或是在发生错误的时候抛出异常。•queue():异步执行。直接返回一个Future
对象,其中包含了服务执行结束时要返回的单一结果对象。
HystrixObservableCommand
用在依赖服务返回多个操作结果的时候:
•observe():返回Obervable
对象,它代表了操作的多个结果,它是一个Hot Observable•toObservable():同样返回Observable
对象,也代表了操作多个结果,但它返回的是一个Cold Observable。
基本原理
有两种:手动自定义command和使用注解,手动自定义这里就不介绍了。当需要完成某项任务时,通过 Hystrix 将任务包裹起来,交由 Hystrix 来完成任务,从而享受 Hystrix 带来保护。这和古代镖局生意有点类似,将任务委托给镖局,以期安全完成任务。
官方 Wiki[6] 中对每一步都做了详细的描述,可以直接参阅。
关于注解需要两步(对破析源码有用):
•第一步:隐式模式(用户不需要做什么,但你要知道),spirng boot会自动加载Feign的配置类HystrixAutoConfiguration
(spring-cloud-netflix-core-1.4.4.RELEASE.jar/META-INF/spring.factories)•第二步:应用系统启动类中添加@EnableHystrix
,它的作用是将spring.cloud.circuit.breaker.enabled设为true。•@HystrixCommandAspect
为执行层面的切入层,命名和原理(MethodInterceptor
)符合spring的一贯套路
其实读Hystrix的源码是需要一定的技术基础的,结合笔者的经验需要重点关注:
• 响应式编程库,特别要非常熟悉rxjava• 重点关注Hystrix的Plugins[7]• 重点关注Metrics and Monitoring[8]• 重点关注Hystrix的Circuit Breaker[9]
自此,你对Hystrix已经有一定的认识了,后面时间比较充足将会展开对源码的讲解。
4.Feign-Hystrix-Ribbon源码
前面提到过,spring-boot中的这个会更复杂,看源码会比较吃力,是否可抛弃这一点呢?当然是可以的,
手动创建步骤
1.
pom引入相关依赖
1.
<dependency>
<groupId>com.netflix.eureka</groupId>
<artifactId>eureka-client</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>com.netflix.ribbon</groupId>
<artifactId>ribbon-eureka</artifactId>
<version>2.2.2</version>
<exclusions>
<exclusion>
<groupId>io.reactivex</groupId>
<artifactId>rxjava</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-ribbon</artifactId>
<version>9.5.1</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-hystrix</artifactId>
<version>9.5.1</version>
</dependency>
2.
resource目录下定义eureka-client.properties
1.
eureka.serviceUrl.default=http://xxxx.cn/eureka
eureka.region=default
eureka.name=github-test
eureka.vipAddress=
eureka.port=8081
eureka.preferSameZone=true
eureka.shouldUseDns=false
eureka.us-east-1.availabilityZones=default
hystrix.command.default.execution.timeout.enabled=false
github-gps.ribbon.NFLoadBalancerClassName=com.netflix.loadbalancer.DynamicServerListLoadBalancer
github-gps.ribbon.NIWSServerListClassName=com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList
github-gps.ribbon.DeploymentContextBasedVipAddresses=github-gps
3.
实例化EurekaClient以及FeignClient
1.
//初始化Eureka Client
private synchronized void initEurekaClient() {
DockerInstanceConfig instanceConfig = new DockerInstanceConfig();
if (this.applicationInfoManager == null) {
InstanceInfo instanceInfo = (new EurekaConfigBasedInstanceInfoProvider(instanceConfig)).get();
this.applicationInfoManager = new ApplicationInfoManager(instanceConfig, instanceInfo);
}
this.applicationInfoManager.setInstanceStatus(InstanceInfo.InstanceStatus.STARTING);
if (this.eurekaClient == null) {
this.eurekaClient = new DiscoveryClient(applicationInfoManager, new DefaultEurekaClientConfig());
}
DiscoveryManager.getInstance().setDiscoveryClient((DiscoveryClient) this.eurekaClient);
this.applicationInfoManager.setInstanceStatus(InstanceInfo.InstanceStatus.UP);
}
//手动创建客户端contract,
private T createObjectNative() {
//return Feign.builder() //此次很重要
return HystrixFeign.builder()
.client(RibbonClient.builder().delegate(new DefaultSupport()).build())
.encoder(new JacksonEncoder(JacksonInstanceManager.getInstance()))
.decoder(new JacksonDecoder(JacksonInstanceManager.getInstance()))
.requestInterceptor(request -> {
.options(new Request.Options(60000, 60000))
.retryer(Retryer.NEVER_RETRY)
.target(contract, "http://" + serverName);
}
组件交互
此时重头看Feign主要组件中的主要类及其层次结构,大概是有两个步骤:
1.生成 Feign 的动态代理2.Feign 执行
/**
* Defines what annotations and values are valid on interfaces.
*/
public interface Contract {
/**
* Called to parse the methods in the class that are linked to HTTP requests.
*
* @param targetType {@link feign.Target#type() type} of the Feign interface.
*/
// TODO: break this and correct spelling at some point
List<MethodMetadata> parseAndValidatateMetadata(Class<?> targetType);
}
public interface InvocationHandlerFactory {
InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch);
/**
* Like {@link InvocationHandler#invoke(Object, java.lang.reflect.Method, Object[])}, except for a
* single method.
*/
interface MethodHandler {
Object invoke(Object[] argv) throws Throwable;
}
//ReflectiveFeign.factory默认生成的代理是FeignInvocationHandler
static final class Default implements InvocationHandlerFactory {
@Override
public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);
}
}
}
//.....待续
动态代理生成
//客户端contract
public abstract class Feign {
public static class Builder {
//调用target,此时客户端Contract已完成构建
public <T> T target(Class<T> apiType, String url) {
return this.target(new HardCodedTarget(apiType, url));
}
public <T> T target(Target<T> target) {
return this.build().newInstance(target);
}
//HystrixFeign.Builder执行逻辑
public Feign build() {
Factory synchronousMethodHandlerFactory = new Factory(this.client, this.retryer, this.requestInterceptors, this.logger, this.logLevel, this.decode404);
ParseHandlersByName handlersByName = new ParseHandlersByName(this.contract, this.options, this.encoder, this.decoder, this.errorDecoder, synchronousMethodHandlerFactory);
return new ReflectiveFeign(handlersByName, this.invocationHandlerFactory);
}
}
}
//集成了Hystrix的客户端contract
public final class HystrixFeign {
//Feign的入口很关键
public static HystrixFeign.Builder builder() {
return new HystrixFeign.Builder();
}
public static final class Builder extends feign.Feign.Builder {
//最终build执行逻辑
Feign build(final FallbackFactory<?> nullableFallbackFactory) {
//与Hystrix的集成,下面专门讲
super.invocationHandlerFactory(new InvocationHandlerFactory() {
public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
return new HystrixInvocationHandler(target, dispatch, Builder.this.setterFactory, nullableFallbackFactory);
}
});//调用父类
super.contract(new HystrixDelegatingContract(this.contract));
return super.build();//调用父类
}
}
}
//客户端contract的真正实现类
public class ReflectiveFeign extends Feign {
private final InvocationHandlerFactory factory;//很重要的(下面会讲)
//通过Feign.Builder.target完成构建
public <T> T newInstance(Target<T> target) {
//基于contract创建一系列SynchronousMethodHandler
Map<String, MethodHandler> nameToHandler = this.targetToHandlersByName.apply(target);
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap();
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList();
Method[] var5 = target.type().getMethods();
int var6 = var5.length;
//存至dispatch
for(int var7 = 0; var7 < var6; ++var7) {
Method method = var5[var7];
if (method.getDeclaringClass() != Object.class) {
if (Util.isDefault(method)) {
DefaultMethodHandler handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {
//从nameToHandler.get获取SynchronousMethodHandler
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
}
//创建HystrixInvocationHandler(下面会讲)
InvocationHandler handler = this.factory.create(target, methodToHandler);
//再次生成代理类
T proxy = Proxy.newProxyInstance(target.type().getClassLoader(), new Class[]{target.type()}, handler);
Iterator var12 = defaultMethodHandlers.iterator();
while(var12.hasNext()) {
DefaultMethodHandler defaultMethodHandler = (DefaultMethodHandler)var12.next();
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
}
//.....待续
Feign 调用过程
//SynchronousMethodHandler的上级代理类(包裹了SynchronousMethodHandler)
public interface InvocationHandlerFactory {
InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch);
/**
* Like {@link InvocationHandler#invoke(Object, java.lang.reflect.Method, Object[])}, except for a
* single method.
*/
interface MethodHandler {
Object invoke(Object[] argv) throws Throwable;
}
//ReflectiveFeign.factory默认生成的代理是FeignInvocationHandler
static final class Default implements InvocationHandlerFactory {
@Override
public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);
}
}
}
与Hystrix的集成
其实ReflectiveFeign.factory
就是HystrixInvocationHandler
,在HystrixFeign.Builder.build
中被构建,HystrixInvocationHandler
其实就是将用户的任务(SynchronousMethodHandler
)嵌入HystrixCommand
中。
public class ReflectiveFeign extends Feign {
//其实是HystrixInvocationHandler
private final InvocationHandlerFactory factory;
}
//ReflectiveFeign.factory的另一个代理类的实现
final class HystrixInvocationHandler implements InvocationHandler {
//源类
private final Target<?> target;
//methodToHandler变量,其实就是method和SynchronousMethodHandler映射关系
private final Map<Method, MethodHandler> dispatch;
//执行调用
public Object invoke(Object proxy, final Method method, final Object[] args) throws Throwable {
if (!"equals".equals(method.getName())) {
if ("hashCode".equals(method.getName())) {
return this.hashCode();
} else if ("toString".equals(method.getName())) {
return this.toString();
} else {
//HystrixCommand就是Hystrix-core里面的,后续文章会讲
HystrixCommand<Object> hystrixCommand = new HystrixCommand<Object>((Setter)this.setterMethodMap.get(method)) {
protected Object run() throws Exception {
try {
return ((MethodHandler)HystrixInvocationHandler.this.dispatch.get(method)).invoke(args);
} catch (Exception var2) {
throw var2;
} catch (Throwable var3) {
throw (Error)var3;
}
}
protected Object getFallback() {
if (HystrixInvocationHandler.this.fallbackFactory == null) {
return super.getFallback();
} else {
try {
Object fallback = HystrixInvocationHandler.this.fallbackFactory.create(this.getExecutionException());
Object result = ((Method)HystrixInvocationHandler.this.fallbackMethodMap.get(method)).invoke(fallback, args);
if (HystrixInvocationHandler.this.isReturnsHystrixCommand(method)) {
return ((HystrixCommand)result).execute();
} else if (HystrixInvocationHandler.this.isReturnsObservable(method)) {
return ((Observable)result).toBlocking().first();
} else if (HystrixInvocationHandler.this.isReturnsSingle(method)) {
return ((Single)result).toObservable().toBlocking().first();
} else if (HystrixInvocationHandler.this.isReturnsCompletable(method)) {
((Completable)result).await();
return null;
} else {
return result;
}
} catch (IllegalAccessException var3) {
throw new AssertionError(var3);
} catch (InvocationTargetException var4) {
throw new AssertionError(var4.getCause());
}
}
}
};
if (this.isReturnsHystrixCommand(method)) {
return hystrixCommand;
} else if (this.isReturnsObservable(method)) {
return hystrixCommand.toObservable();
} else if (this.isReturnsSingle(method)) {
return hystrixCommand.toObservable().toSingle();
} else {
return this.isReturnsCompletable(method) ? hystrixCommand.toObservable().toCompletable() : hystrixCommand.execute();
}
}
} else {
try {
Object otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
return this.equals(otherHandler);
} catch (IllegalArgumentException var5) {
return false;
}
}
}
}
与ribbon的集成
这时你可能会问Feign是怎样实现负载均衡的呢?这个不难理解,肯定是集成ribbon实现的,其实构建客户端Contract
时就有这样的API:
HystrixFeign.Builder.client(Client client)
,而client就实现了负载均衡。client的创建是通过RibbonClient.builder().delegate(new Client.Default(null, null)).build()
完成的。
public interface Client {
Response execute(Request request, Options options) throws IOException;
//真正执行get/post的Client
public static class Default implements Client {
@Override
public Response execute(Request request, Options options) throws IOException {
HttpURLConnection connection = convertAndSend(request, options);
return convertResponse(connection).toBuilder().request(request).build();
}
HttpURLConnection convertAndSend(Request request, Options options) throws IOException {
final HttpURLConnection
connection =
(HttpURLConnection) new URL(request.url()).openConnection();
if (connection instanceof HttpsURLConnection) {
HttpsURLConnection sslCon = (HttpsURLConnection) connection;
if (sslContextFactory != null) {
sslCon.setSSLSocketFactory(sslContextFactory);
}
if (hostnameVerifier != null) {
sslCon.setHostnameVerifier(hostnameVerifier);
}
}
connection.setConnectTimeout(options.connectTimeoutMillis());
connection.setReadTimeout(options.readTimeoutMillis());
connection.setAllowUserInteraction(false);
connection.setInstanceFollowRedirects(true);
connection.setRequestMethod(request.method());
Collection<String> contentEncodingValues = request.headers().get(CONTENT_ENCODING);
boolean
gzipEncodedRequest =
contentEncodingValues != null && contentEncodingValues.contains(ENCODING_GZIP);
boolean
deflateEncodedRequest =
contentEncodingValues != null && contentEncodingValues.contains(ENCODING_DEFLATE);
boolean hasAcceptHeader = false;
Integer contentLength = null;
for (String field : request.headers().keySet()) {
if (field.equalsIgnoreCase("Accept")) {
hasAcceptHeader = true;
}
for (String value : request.headers().get(field)) {
if (field.equals(CONTENT_LENGTH)) {
if (!gzipEncodedRequest && !deflateEncodedRequest) {
contentLength = Integer.valueOf(value);
connection.addRequestProperty(field, value);
}
} else {
connection.addRequestProperty(field, value);
}
}
}
// Some servers choke on the default accept string.
if (!hasAcceptHeader) {
connection.addRequestProperty("Accept", "*/*");
}
if (request.body() != null) {
if (contentLength != null) {
connection.setFixedLengthStreamingMode(contentLength);
} else {
connection.setChunkedStreamingMode(8196);
}
connection.setDoOutput(true);
OutputStream out = connection.getOutputStream();
if (gzipEncodedRequest) {
out = new GZIPOutputStream(out);
} else if (deflateEncodedRequest) {
out = new DeflaterOutputStream(out);
}
try {
out.write(request.body());
} finally {
try {
out.close();
} catch (IOException suppressed) { // NOPMD
}
}
}
return connection;
}
}
//具备负载均衡的Client
public class RibbonClient implements Client {
//delegate就是Client.Default
private final Client delegate;
private final LBClientFactory lbClientFactory;
//具备负载均衡的get/post
public Response execute(Request request, Options options) throws IOException {
try {
URI asUri = URI.create(request.url());
String clientName = asUri.getHost();
URI uriWithoutHost = cleanUrl(request.url(), clientName);
RibbonRequest ribbonRequest = new RibbonRequest(this.delegate, request, uriWithoutHost);
//执行请求AbstractLoadBalancerAwareClient.executeWithLoadBalancer
return ((RibbonResponse)this.lbClient(clientName).executeWithLoadBalancer(ribbonRequest, new RibbonClient.FeignOptionsClientConfig(options))).toResponse();
} catch (ClientException var7) {
propagateFirstIOException(var7);
throw new RuntimeException(var7);
}
}
//RibbonClient的构造者模式
public static final class Builder {
//指定Client
public RibbonClient.Builder delegate(Client delegate) {
this.delegate = delegate;
return this;
}
public RibbonClient build() {
//指定了负载均衡模式:LBClientFactory.Default
return new RibbonClient((Client)(this.delegate != null ? this.delegate : new Default((SSLSocketFactory)null, (HostnameVerifier)null)), (LBClientFactory)(this.lbClientFactory != null ? this.lbClientFactory : new feign.ribbon.LBClientFactory.Default()));
}
}
}
//负载均衡客户端
public interface LBClientFactory {
public static final class Default implements LBClientFactory {
public Default() {
}
//创建(懒加载设计:基于服务名USER)
public LBClient create(String clientName) {
//加载文件eureka-client.properties中USER的配置
IClientConfig config = ClientFactory.getNamedConfig(clientName, LBClientFactory.DisableAutoRetriesByDefaultClientConfig.class);
//这牵连到与eureka的交互是非常最复杂的,后续讲
ILoadBalancer lb = ClientFactory.getNamedLoadBalancer(clientName);
return LBClient.create(lb, config);
}
}
}
//真正实现负载均衡
public abstract class AbstractLoadBalancerAwareClient<S extends ClientRequest, T extends IResponse> extends LoadBalancerContext implements IClient<S, T>, IClientConfigAware {
//执行请求
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
RequestSpecificRetryHandler handler = this.getRequestSpecificRetryHandler(request, requestConfig);
LoadBalancerCommand command = LoadBalancerCommand.builder().withLoadBalancerContext(this).withRetryHandler(handler).withLoadBalancerURI(request.getUri()).build();
try {
return (IResponse)command.submit(new ServerOperation<T>() {
public Observable<T> call(Server server) {
//使用eureka中的具体server(ip+port)构建真实的url,不再是微服务名
URI finalUri = AbstractLoadBalancerAwareClient.this.reconstructURIWithServer(server, request.getUri());
ClientRequest requestForServer = request.replaceUri(finalUri);
try {
return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
} catch (Exception var5) {
return Observable.error(var5);
}
}
}).toBlocking().single();
} catch (Exception var7) {
Throwable t = var7.getCause();
if (t instanceof ClientException) {
throw (ClientException)t;
} else {
throw new ClientException(var7);
}
}
}
}
与eureka的集成
你也许会问FeignClient.name为服务名(不是url时)它是如何关联到eureka的呢?其实在LBClientFactory.create
中有这样一段: ClientFactory.getNamedLoadBalancer(clientName)
,这个其实就是读取了eureka-client.properties文件信息,并根据服务名加载配置,并连接eureka拉取ServerList
。
public class ClientFactory {
//如果不存在则创建一个实例
public static synchronized ILoadBalancer getNamedLoadBalancer(String name) {
return getNamedLoadBalancer(name, DefaultClientConfigImpl.class);
}
//同上
public static synchronized ILoadBalancer getNamedLoadBalancer(String name, Class<? extends IClientConfig> configClass) {
ILoadBalancer lb = namedLBMap.get(name);
if (lb != null) {
return lb;
} else {
try {
lb = registerNamedLoadBalancerFromProperties(name, configClass);
} catch (ClientException e) {
throw new RuntimeException("Unable to create load balancer", e);
}
return lb;
}
}
//同上
public static ILoadBalancer registerNamedLoadBalancerFromclientConfig(String name, IClientConfig clientConfig) throws ClientException {
if (namedLBMap.get(name) != null) {
throw new ClientException("LoadBalancer for name " + name + " already exists");
}
ILoadBalancer lb = null;
try {
//获取配置项NFLoadBalancerClassName:DynamicServerListLoadBalancer
String loadBalancerClassName = (String) clientConfig.getProperty(CommonClientConfigKey.NFLoadBalancerClassName);
//实例化DynamicServerListLoadBalancer
lb = (ILoadBalancer) ClientFactory.instantiateInstanceWithClientConfig(loadBalancerClassName, clientConfig);
namedLBMap.put(name, lb);
logger.info("Client: {} instantiated a LoadBalancer: {}", name, lb);
return lb;
} catch (Throwable e) {
throw new ClientException("Unable to instantiate/associate LoadBalancer with Client:" + name, e);
}
}
}
public class DynamicServerListLoadBalancer<T extends Server> extends BaseLoadBalancer {
//instantiateInstanceWithClientConfig中执行它
public DynamicServerListLoadBalancer() {
super();
}
//instantiateInstanceWithClientConfig中执行它
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
try {
super.initWithNiwsConfig(clientConfig);
//获取配置项NIWSServerListClassName:DiscoveryEnabledNIWSServerList
String niwsServerListClassName = clientConfig.getPropertyAsString(
CommonClientConfigKey.NIWSServerListClassName,
DefaultClientConfigImpl.DEFAULT_SEVER_LIST_CLASS);
//实例化DiscoveryEnabledNIWSServerList
ServerList<T> niwsServerListImpl = (ServerList<T>) ClientFactory
.instantiateInstanceWithClientConfig(niwsServerListClassName, clientConfig);
this.serverListImpl = niwsServerListImpl;
if (niwsServerListImpl instanceof AbstractServerList) {
AbstractServerListFilter<T> niwsFilter = ((AbstractServerList) niwsServerListImpl)
.getFilterImpl(clientConfig);
niwsFilter.setLoadBalancerStats(getLoadBalancerStats());
this.filter = niwsFilter;
}
String serverListUpdaterClassName = clientConfig.getPropertyAsString(
CommonClientConfigKey.ServerListUpdaterClassName,
DefaultClientConfigImpl.DEFAULT_SERVER_LIST_UPDATER_CLASS
);
this.serverListUpdater = (ServerListUpdater) ClientFactory
.instantiateInstanceWithClientConfig(serverListUpdaterClassName, clientConfig);
restOfInit(clientConfig);
} catch (Exception e) {
throw new RuntimeException(
"Exception while initializing NIWSDiscoveryLoadBalancer:"
+ clientConfig.getClientName()
+ ", niwsClientConfig:" + clientConfig, e);
}
}
}
//eureka服务发现
public class DiscoveryEnabledNIWSServerList extends AbstractServerList<DiscoveryEnabledServer>{
//instantiateInstanceWithClientConfig中调用
public DiscoveryEnabledNIWSServerList() {
//eureka客户端
this.eurekaClientProvider = new LegacyEurekaClientProvider();
}
//instantiateInstanceWithClientConfig中调用
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
clientName = clientConfig.getClientName();
vipAddresses = clientConfig.resolveDeploymentContextbasedVipAddresses();
if (vipAddresses == null &&
ConfigurationManager.getConfigInstance().getBoolean("DiscoveryEnabledNIWSServerList.failFastOnNullVip", true)) {
throw new NullPointerException("VIP address for client " + clientName + " is null");
}
isSecure = Boolean.parseBoolean(""+clientConfig.getProperty(CommonClientConfigKey.IsSecure, "false"));
prioritizeVipAddressBasedServers = Boolean.parseBoolean(""+clientConfig.getProperty(CommonClientConfigKey.PrioritizeVipAddressBasedServers, prioritizeVipAddressBasedServers));
datacenter = ConfigurationManager.getDeploymentContext().getDeploymentDatacenter();
targetRegion = (String) clientConfig.getProperty(CommonClientConfigKey.TargetRegion);
shouldUseIpAddr = clientConfig.getPropertyAsBoolean(CommonClientConfigKey.UseIPAddrForServer, DefaultClientConfigImpl.DEFAULT_USEIPADDRESS_FOR_SERVER);
// override client configuration and use client-defined port
if(clientConfig.getPropertyAsBoolean(CommonClientConfigKey.ForceClientPortConfiguration, false)){
if(isSecure){
if(clientConfig.containsProperty(CommonClientConfigKey.SecurePort)){
overridePort = clientConfig.getPropertyAsInteger(CommonClientConfigKey.SecurePort, DefaultClientConfigImpl.DEFAULT_PORT);
shouldUseOverridePort = true;
}else{
logger.warn(clientName + " set to force client port but no secure port is set, so ignoring");
}
}else{
if(clientConfig.containsProperty(CommonClientConfigKey.Port)){
overridePort = clientConfig.getPropertyAsInteger(CommonClientConfigKey.Port, DefaultClientConfigImpl.DEFAULT_PORT);
shouldUseOverridePort = true;
}else{
logger.warn(clientName + " set to force client port but no port is set, so ignoring");
}
}
}
}
}
5.Q&A
1.Spring Cloud的 Feign和Ribbon重试机制的有何异同点?
完全是两个不相关,如果都配置重试(feign M次、ribbon N次),那将会发生M+N次重试!
2.Feign和hystrix中很多次强调隔离,究竟是怎样去什么隔离的?
Feign在融合了spring framework后,它通过@FeignClient#configuration
便可完成,它内部是通过实例化不同ApplicationContext子容器来完成隔离(在实施模块化方向绝对算一个创新,有点阿里webx的味道)!
hystrix这个隔离非常简单,每个请求都是一个HystrixCommand
,参照前面提到的基本原理再去代码中去验证。
3.Feign-ribbon-hystrix的配置和spring 的配置实现上有什么大的差异点?
spring 主要是通过外部化配置,大概17个级别的优先级向下查找,具体可参阅spring boot-doc[10],而Feign-ribbon-hystrix自成体系,延续了netflix一贯的套路(自研的Archaius)。
4.如何去拓展实现一个具有grpc语义的契约来重塑我们的feign客户端呢?
syntax = "proto3";
// stub选项
option java_multiple_files = true;
option java_package = "com.github.net.grpc";
option java_outer_classname = "OrderApi";
// 定义包名
//package com.github.net.grpc;
service OrderContract {
rpc getOrder (OrderId) returns (Order) {}
rpc addOrder (Order) returns (OrderId) {}
}
message OrderId {
int32 id = 1;
}
message Order {
string name = 1;
string address = 2;
string buyDate = 3;
repeated Product products=4;
}
message Product {
int32 id = 1;
string prodName = 2;
}
public class OrderClient {
private OrderContractGrpc.OrderServiceBlockingStub orderConsumer;
private OrderContractGrpc.OrderServiceFutureStub orderAsyncConsumer;
private OrderContractGrpc.OrderServiceStub orderStreamConsumer;
private Order order = Order.newBuilder().setName("iphone").build();
public void addOrder() {
orderConsumer.addOrder(order);
}
public void asyncAddOrder() {
ListenableFuture<OrderDTO> result = orderAsyncConsumer.addOrder(orderDTO);
}
public void streamGetOrder() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
StreamObserver<Order> responseObserver = new StreamObserver<Order>() {
@Override
public void onNext(Order value) {
System.out.println("get result :" + value);
}
@Override
public void onError(Throwable t) {
Status status = Status.fromThrowable(t);
latch.countDown();
}
@Override
public void onCompleted() {
System.out.println("finished!");
latch.countDown();
}
};
orderStreamConsumer.getOrder(order, responseObserver);
latch.await();
}
}
可部分参阅实现[11]
References
[1]
spring-cloud-openfeign 在 Github 描述了其特性: https://github.com/spring-cloud/spring-cloud-openfeign[2]
ribbon在github中的描述:: https://github.com/Netflix/ribbon[3]
www.microsoft.com:80,www.yahoo.com:80,www.google.com:80: http://www.microsoft.com:80,www.yahoo.com:80,www.google.com:80[4]
Hystrix官方的描述: https://github.com/Netflix/Hystrix/wiki[5]
参阅wiki: https://en.wikipedia.org/wiki/Fail-silent_system[6]
官方 Wiki: https://github.com/Netflix/Hystrix/wiki/How-it-Works[7]
Plugins: https://github.com/Netflix/Hystrix/wiki/Plugins[8]
Metrics and Monitoring: https://github.com/Netflix/Hystrix/wiki/Metrics-and-Monitoring[9]
Circuit Breaker: https://github.com/Netflix/Hystrix/wiki/How-it-Works#CircuitBreaker[10]
spring boot-doc: https://docs.spring.io/spring-boot/docs/2.0.2.RELEASE/reference/htmlsingle/#boot-features-external-config[11]
参阅实现: https://github.com/alex2chen/feign-async
以上是关于feign-ribbon-hystrix解码的主要内容,如果未能解决你的问题,请参考以下文章