一、总结前置:
1、ribbon、feign、openfeign三者的对比:
我们现在工作中现在几乎都是直接使用openfeign,而我们很有必要了解一下,ribbon、feign、openfeign三者之间的关系!
-
ribbon:ribbon是netflix开源的客户端负载均衡组件,可以调用注册中心获取服务列表,并通过负载均衡算法挑选一台Server;
-
feign:feign也是netflix开源的,是对ribbon的一层封装,通过接口的方式使用,更加优雅,不需要每次都去写restTemplate,只需要定义一个接口来使用;但是Feign不支持springmvc的注解;
-
openfeign:是对feign的进一步封装,使其能够支持springmvc注解,如@GetMapping等,使用起来更加方便和优雅!
2、工作中都是如何使用openfeign:
// 想要使用openfeign,必须要在启动类增加@EnableFeignClient注解,否则启动报错
@FeignClient("feign-provider")
public interface OrderService {
@GetMapping("/order/get/{id}")
public String getById(@PathVariable("id") String id);
}
3、openfeign与ribbon如何分工?
-
1. openfeign通过动态代理的方式,对feignclient注解修饰的类进行动态代理,拼接成临时URL:http://feign-provider/order/get/100,交给ribbon
-
2. ribbon通过自己的拦截器,截取出serviceName在服务注册表中找到对应的serverList,并通过负载均衡策略挑选一台Server,拼接成最终的URL:http://10.206.73.156:1111/order/get/100,交还给feign;
-
3. 最后,feign通过自己封装的Client对目标地址发起调用,并获得返回结果;(Client是对原生 java.net 中的URL类的封装,实现远程调用)
二、核心代码——Openfeign生成的动态代理类是啥?(LoadBalancerFeignClient.class)
1、启动类必须增加@EnableFeignClient,那我们就从这里入口:
@EnableFeignClients
|
|
@Import(FeignClientsRegistrar.class)
|
|
// 遇到import(Registrar),我们就去看它的registerBeanDefinitions()方法:
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
// 注册FeignClient,其实FeignClient就是我们需要的动态代理类
registerFeignClients(metadata, registry);
}
|
|
public void registerFeignClients(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
......
// 定义过滤器,把加了@FeignClient注解的接口都过滤出来
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(FeignClient.class);
...过滤逻辑...
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);
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();
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
...目标FactoryBean为FeignClientFactoryBean...
...FactoryBean一般用于定制化一些特殊的Bean,spring会调用它的getObject接口...
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
new String[] { alias });
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
2、见名知意FeignClient的工厂bean:FeignClientFactoryBean.getObject()方法:
class FeignClientFactoryBean{
@Override
public Object getObject() throws Exception {
return getTarget();
}
<T> T getTarget() {
FeignContext context = this.applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
// 当我们不为@FeignClient()注解配置url属性时
// 这里同时会根据注解,拼接出url前缀,如:http://feign-provider
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));
}
...如果我们为@FeignClient()注解配置url属性之后的逻辑...
}
}
|
|
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget<T> target) {
// 从容器中获取一个类型为Client的bean,那么IOC容器中肯定有一个这样的Bean
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);
}
// 如果找不到类型为Client的Bean就是有问题的啦!
throw new IllegalStateException(
"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
}
3、容器中类型为Client的Bean是谁?
此时还是启动阶段,我们去看看有哪些bean会被注入,在 spring.factories 中,我们会找到两个自动配置类:

这两个类,很关键,都会往容器中注入负载均衡的Client Bean,但是只会有一个成功,
FeignRibbonClientAutoCOnfiguration类:
@ConditionalOnClass({ ILoadBalancer.class, Feign.class })
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.ribbon.enabled",
matchIfMissing = true)
@Configuration(proxyBeanMethods = false)
// 重点1:在FeignAutoConfiguration之前装载配置
@AutoConfigureBefore(FeignAutoConfiguration.class)
@EnableConfigurationProperties({ FeignHttpClientProperties.class })
@Import({ HttpClientFeignLoadBalancedConfiguration.class,
OkHttpFeignLoadBalancedConfiguration.class,
DefaultFeignLoadBalancedConfiguration.class }) // 重点2:
public class FeignRibbonClientAutoConfiguration {
...非重点...
}
|
|
@Configuration(proxyBeanMethods = false)
class DefaultFeignLoadBalancedConfiguration {
@Bean
@ConditionalOnMissingBean // 如果Client类型的bean已经存在,则不执行
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
SpringClientFactory clientFactory) {
return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory,
clientFactory);
}
}
FeignLoadBalancerAutoConfiguration类:
@ConditionalOnClass(Feign.class)
@ConditionalOnBean(BlockingLoadBalancerClient.class)
@AutoConfigureBefore(FeignAutoConfiguration.class)
// 重点1:在FeignRibbonClientAutoConfiguration之后装载配置
@AutoConfigureAfter(FeignRibbonClientAutoConfiguration.class)
@EnableConfigurationProperties(FeignHttpClientProperties.class)
@Configuration(proxyBeanMethods = false)
@Import({ HttpClientFeignLoadBalancerConfiguration.class,
OkHttpFeignLoadBalancerConfiguration.class,
DefaultFeignLoadBalancerConfiguration.class }) // 重点2:
public class FeignLoadBalancerAutoConfiguration {
...非重点...
}
|
|
@Configuration(proxyBeanMethods = false)
class DefaultFeignLoadBalancerConfiguration {
@Bean
@ConditionalOnMissingBean // 如果Client类型的bean已经存在,则不执行
public Client feignClient(BlockingLoadBalancerClient loadBalancerClient) {
return new FeignBlockingLoadBalancerClient(new Client.Default(null, null),
loadBalancerClient);
}
}
到这里就非常清晰了:
1. 通过spring.factories注入了两个配置类,并通过 @AutoConfigureBefore 和 @AutoConfigureAfter强制了两个配置类的装载顺序!
2. 这两个配置类各import 了 另一个配置类:DefaultFeignLoadBalancedConfiguration(注入:LoadBalancerFeignClient) 和 DefaultFeignLoadBalancerConfiguration(注入:FeignBlockingLoadBalancerClient),但是由于@ConditionalOnMissingBean注解,只有一个前面那个会注入成功!
所以,结论就是:IOC容器中的“Client”类型的 Bean 为 “LoadBalancerFeignClient”
所以,最终的结论就是:Openfeign通过的动态代理,为每个“@FeignClient”注解修饰的接口生成一个类型为 “LoadBalancerFeignClient”的代理类。
三、FeignClient代理类执行时,是如何使用Ribbon的?
1、当执行LoadBalancerFeignClient.execute()方法时:
// org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient#execute
public Response execute(Request request, Request.Options options) throws IOException {
try {
URI asUri = URI.create(request.url());
String clientName = asUri.getHost();
URI uriWithoutHost = cleanUrl(request.url(), clientName);
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
this.delegate, request, uriWithoutHost);
// 可以获取到nacos的一些信息
IClientConfig requestConfig = getClientConfig(options, clientName);
// lbClient(clientName):可以获得具体的代理类,每个@FeignClient修饰的接口代理类时独立的
return lbClient(clientName)
.executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
}
catch (ClientException e) {
......
}
}
我们可以很清楚的看到,这里肯定会要用到Ribbon;
2、通过负载均衡器,执行调用,并返回结果:
// com.netflix.client.AbstractLoadBalancerAwareClient#executeWithLoadBalancer(...)
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);
try {
return command.submit( // 这步会返回具体的,负载均衡选定好的Server
new ServerOperation<T>() {
@Override
public Observable<T> call(Server server) { // submit选好的Server作为入参
URI finalUri = reconstructURIWithServer(server, request.getUri());
S requestForServer = (S) request.replaceUri(finalUri);
try {
return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
}
catch (Exception e) {
return Observable.error(e);
}
}
})
.toBlocking()
.single();
} catch (Exception e) {
......
}
}
|
|
// com.netflix.loadbalancer.reactive.LoadBalancerCommand#submit
public Observable<T> submit(final ServerOperation<T> operation) {
final ExecutionInfoContext context = new ExecutionInfoContext();
......
final int maxRetrysSame = retryHandler.getMaxRetriesOnSameServer();
final int maxRetrysNext = retryHandler.getMaxRetriesOnNextServer();
// selectServer()方法会通过负载均衡选择一台Server
Observable<T> o = (server == null ? selectServer() : Observable.just(server))
.concatMap(new Func1<Server, Observable<T>>() {
......
});
}
3、selectServer()具体方法:
// com.netflix.loadbalancer.reactive.LoadBalancerCommand#selectServer
private Observable<Server> selectServer() {
return Observable.create(new OnSubscribe<Server>() {
@Override
public void call(Subscriber<? super Server> next) {
try {
Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey);
next.onNext(server);
next.onCompleted();
} catch (Exception e) {
next.onError(e);
}
}
});
}
4、这里将使用到非常重要的一个ZoneAwareLoadBalancer:
// com.netflix.loadbalancer.LoadBalancerContext#getServerFromLoadBalancer
public Server getServerFromLoadBalancer(@Nullable URI original, @Nullable Object loadBalancerKey) throws ClientException {
String host = null;
int port = -1;
if (original != null) {
host = original.getHost();
}
if (original != null) {
Pair<String, Integer> schemeAndPort = deriveSchemeAndPortFromPartialUri(original);
port = schemeAndPort.second();
}
// 关键一步,获得了一个ILoadBalancer
ILoadBalancer lb = getLoadBalancer();
if (host == null) {
// 重要:这里其实获得的是ZoneAwareLoadBalancer类的Bean
if (lb != null){
Server svc = lb.chooseServer(loadBalancerKey);
if (svc == null){
throw new ClientException(ClientException.ErrorType.GENERAL,
"Load balancer does not have available server for client: "
+ clientName);
}
host = svc.getHost();
return svc;
} else {
......
}
} else {
......
}
return new Server(host, port);
}
|
|
// com.netflix.loadbalancer.BaseLoadBalancer#chooseServer
public Server chooseServer(Object key) {
if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
logger.debug("Zone aware logic disabled or there is only one zone");
return super.chooseServer(key);
}
...其它分支在国内一般不会走,没有zone的概念...
}
|
|
// com.netflix.loadbalancer.BaseLoadBalancer#chooseServer
public Server chooseServer(Object key) {
if (counter == null) {
counter = createCounter();
}
counter.increment();
if (rule == null) {
return null;
} else {
try {
return rule.choose(key); // PredicateBasedRule
} catch (Exception e) {
return null;
}
}
}
|
|
// com.netflix.loadbalancer.PredicateBasedRule#choose
public Server choose(Object key) {
ILoadBalancer lb = getLoadBalancer();
Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
if (server.isPresent()) {
return server.get();
} else {
return null;
}
}
5、可以看到 lb.getAllServer(),正是ZoneAwareLoadBalancer.getAllServers()方法:
// com.netflix.loadbalancer.BaseLoadBalancer#getAllServers
public class BaseLoadBalancer extends AbstractLoadBalancer {
protected volatile List<Server> allServerList = Collections.synchronizedList(new ArrayList<Server>());
public List<Server> getAllServers() {
return Collections.unmodifiableList(allServerList);
}
}
所以,我们现在用弄清楚的有两点:
-
为什么重要的ILoadBalancer的实现就是ZoneAwareLoadBalancer;
-
ZoneAwareLoadBalancer中的allServerList属性是何时被赋值的;
四、ZoneAwareLoadBalancer是何时注入的?
1、在spring-cloud-netfliex-ribbon包的spring.factories中有RibbonAutoConfiguration类:

我们看到这个类的构造方法,创建了一个SpringClientFactory工厂类:
@Configuration
@Conditional(RibbonAutoConfiguration.RibbonClassesConditions.class)
@RibbonClients
@AutoConfigureAfter(
name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
@AutoConfigureBefore({ LoadBalancerAutoConfiguration.class,
AsyncLoadBalancerAutoConfiguration.class })
@EnableConfigurationProperties({ RibbonEagerLoadProperties.class,
ServerIntrospectorProperties.class })
public class RibbonAutoConfiguration {
@Autowired(required = false)
private List<RibbonClientSpecification> configurations = new ArrayList<>();
@Autowired
private RibbonEagerLoadProperties ribbonEagerLoadProperties;
@Bean
public HasFeatures ribbonFeature() {
return HasFeatures.namedFeature("Ribbon", Ribbon.class);
}
@Bean
public SpringClientFactory springClientFactory() {
// SpringClientFactory这个Client工厂类很关键
SpringClientFactory factory = new SpringClientFactory();
factory.setConfigurations(this.configurations);
return factory;
}
}
我们再看看这个工厂类的构造方法做了什么事:
public class SpringClientFactory extends NamedContextFactory<RibbonClientSpecification> {
static final String NAMESPACE = "ribbon";
public SpringClientFactory() {
super(RibbonClientConfiguration.class, NAMESPACE, "ribbon.client.name");
}
}
|
|
public abstract class NamedContextFactory<C extends NamedContextFactory.Specification> implements DisposableBean, ApplicationContextAware {
private final String propertySourceName;
private final String propertyName;
private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap();
private Map<String, C> configurations = new ConcurrentHashMap();
private ApplicationContext parent;
private Class<?> defaultConfigType;
public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName, String propertyName) {
this.defaultConfigType = defaultConfigType;
this.propertySourceName = propertySourceName;
this.propertyName = propertyName;
}
}
显然,构造了一个上下工厂“NamedContextFactory”类,这个工厂类的默认配置类是“RibbonClientConfiguraion”,这个在之后Feign的调用过程中非常关键;
2、LoadBalancerFeignClient这个代理类的execute()方法中:
// org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient#execute
public Response execute(Request request, Request.Options options) throws IOException {
try {
URI asUri = URI.create(request.url());
String clientName = asUri.getHost();
URI uriWithoutHost = cleanUrl(request.url(), clientName);
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
this.delegate, request, uriWithoutHost);
// 这个方法不用讲,得到的IclientConfig肯定是RibbonClientConfiguraion
IClientConfig requestConfig = getClientConfig(options, clientName);
return lbClient(clientName)
.executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
}
catch (ClientException e) {
......
}
}
|
|
// org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient#getClientConfig
IClientConfig getClientConfig(Request.Options options, String clientName) {
......
requestConfig = this.clientFactory.getClientConfig(clientName);
......
}
|
|
// org.springframework.cloud.netflix.ribbon.SpringClientFactory#getClientConfig
public IClientConfig getClientConfig(String name) {
return getInstance(name, IClientConfig.class);
}
|
|
// org.springframework.cloud.netflix.ribbon.SpringClientFactory#getInstance
public <C> C getInstance(String name, Class<C> type) {
// SpringClientFactory的super父类就是NamedContextFactory
C instance = super.getInstance(name, type);
if (instance != null) {
return instance;
}
IClientConfig config = getInstance(name, IClientConfig.class);
return instantiateWithConfig(getContext(name), type, config);
}
|
|
// org.springframework.cloud.context.named.NamedContextFactory#getInstance
public <T> T getInstance(String name, Class<T> type) {
AnnotationConfigApplicationContext context = this.getContext(name);
return BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, type).length > 0 ? context.getBean(type) : null;
}
找到了NamedContextFactory,就和第一点串起来了,它的默认配置类正是RibbonClientConfiguration;
3、RibbonClientConfiguration又是如何注入我们需要的ZoneAwareLoadBalancer的:
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
// Order is important here, last should be the default, first should be optional
// see
// https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
@Import({ HttpClientConfiguration.class, OkHttpRibbonConfiguration.class,
RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class })
public class RibbonClientConfiguration {
@Bean
@ConditionalOnMissingBean
public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
return this.propertiesFactory.get(ILoadBalancer.class, config, name);
}
// 默认注入的类正是ZoneAwareLoadBalancer
return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
serverListFilter, serverListUpdater);
}
}
所以,这就解释了,为什么在后面的 ILoadBalancer lb = getLoadBalancer(); 获得到的就是ZoneAwareLoadBalancer!
同时,这个类是在动态代理被执行execute()的时候被调起的,所以Ribbon也是在被使用时才从Nacos获取注册表的,并不是在容器启动时!
五、Ribbon又是何时从Nacos服务端获取allServerList的?
1、我们需要跟踪ZoneAwareLoadBalancer对应的Bean的构造过程:
// com.netflix.loadbalancer.ZoneAwareLoadBalancer#ZoneAwareLoadBalancer
public ZoneAwareLoadBalancer(IClientConfig clientConfig, IRule rule,
IPing ping, ServerList<T> serverList, ServerListFilter<T> filter,
ServerListUpdater serverListUpdater) {
super(clientConfig, rule, ping, serverList, filter, serverListUpdater);
}
|
|
// com.netflix.loadbalancer.DynamicServerListLoadBalancer#DynamicServerListLoadBalancer
public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping,
ServerList<T> serverList, ServerListFilter<T> filter,
ServerListUpdater serverListUpdater) {
super(clientConfig, rule, ping);
this.serverListImpl = serverList;
this.filter = filter;
this.serverListUpdater = serverListUpdater;
if (filter instanceof AbstractServerListFilter) {
((AbstractServerListFilter) filter).setLoadBalancerStats(getLoadBalancerStats());
}
//执行远程调用,并初始化BaseLoadBalancer中的allServerList
restOfInit(clientConfig);
}
2、远程调用Nacos并初始化restOfInit()方法的逻辑:
void restOfInit(IClientConfig clientConfig) {
boolean primeConnection = this.isEnablePrimingConnections();
// turn this off to avoid duplicated asynchronous priming done in BaseLoadBalancer.setServerList()
this.setEnablePrimingConnections(false);
// 获取服务列表功能
enableAndInitLearnNewServersFeature();
updateListOfServers();
if (primeConnection && this.getPrimeConnections() != null) {
this.getPrimeConnections()
.primeConnections(getReachableServers());
}
this.setEnablePrimingConnections(primeConnection);
}
|
|
public void enableAndInitLearnNewServersFeature() {
serverListUpdater.start(updateAction);
}
这个updateAction变量是可执行的:
protected final ServerListUpdater.UpdateAction updateAction = new ServerListUpdater.UpdateAction() {
@Override
public void doUpdate() {
updateListOfServers(); // 更新Servers列表
}
};
3、updateListOfServer()方法如何从Nacos服务端获取服务列表:
// com.netflix.loadbalancer.DynamicServerListLoadBalancer#updateListOfServers
public void updateListOfServers() {
List<T> servers = new ArrayList<T>();
if (serverListImpl != null) {
// 其中的serverListImpl有三个实现,其中之一就是Nacos
servers = serverListImpl.getUpdatedListOfServers();
LOGGER.debug("List of Servers for {} obtained from Discovery client: {}",
getIdentifier(), servers);
if (filter != null) {
servers = filter.getFilteredListOfServers(servers);
LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}",
getIdentifier(), servers);
}
}
// 用获得到的服务列表更新BaseLoadBalancer中的allServerList
updateAllServerList(servers);
}
不用想了,肯定是NacosServerList,这个简单追踪就不赘述了!

NacosServerList.getUpdatedListOfServers()最终会调用nacos客户端的核心类 NacosNamingServer.selectInstances() 方法,该方法只会返回健康实例列表;
4、根据从nacos获得到的Servers,更新本地的allServerList属性:
// com.netflix.loadbalancer.DynamicServerListLoadBalancer#updateAllServerList
protected void updateAllServerList(List<T> ls) {
// other threads might be doing this - in which case, we pass
if (serverListUpdateInProgress.compareAndSet(false, true)) {
try {
for (T s : ls) {
s.setAlive(true); // set so that clients can start using these
// servers right away instead
// of having to wait out the ping cycle.
}
setServersList(ls);
super.forceQuickPing();
} finally {
serverListUpdateInProgress.set(false);
}
}
}
|
|
public void setServersList(List lsrv) {
super.setServersList(lsrv); // 核心
List<T> serverList = (List<T>) lsrv;
Map<String, List<Server>> serversInZones = new HashMap<String, List<Server>>();
for (Server server : serverList) {
// make sure ServerStats is created to avoid creating them on hot
// path
getLoadBalancerStats().getSingleServerStat(server);
String zone = server.getZone();
if (zone != null) {
zone = zone.toLowerCase();
List<Server> servers = serversInZones.get(zone);
if (servers == null) {
servers = new ArrayList<Server>();
serversInZones.put(zone, servers);
}
servers.add(server);
}
}
setServerListForZones(serversInZones);
}
|
|
// com.netflix.loadbalancer.BaseLoadBalancer#setServersList
public void setServersList(List lsrv) {
Lock writeLock = allServerLock.writeLock();
logger.debug("LoadBalancer [{}]: clearing server list (SET op)", name);
ArrayList<Server> newServers = new ArrayList<Server>();
writeLock.lock();
try {
ArrayList<Server> allServers = new ArrayList<Server>();
...对每一个Server进行下包装...
allServerList = allServers; //本地的allServerList赋值
if (canSkipPing()) {
for (Server s : allServerList) {
s.setAlive(true);
}
upServerList = allServerList;
} else if (listChanged) {
forceQuickPing();
}
} finally {
writeLock.unlock();
}
}
到这里,总算和 二.5 章节中的allServerList进行呼应了!
至此,整个从FeignClient注解生成动态代理类LoadBalancerFeignClient;
——> Client执行时会生成ribbon下的ZoneAwareLoadBalancer类,调用nacos服务端获取服务列表;
——> 在通过ZoneAwareLoadBalancer的父类的BaseLoadBalancer.chooseServer()方法根据负载均衡算法挑选一台服务器;
——> 最后交给Feign的的Client通过URL类完成调用。
六、Feign和Ribbon的重试机制
1、首先我们做两个事情:
服务提供者feign-provider:
@GetMapping("/get/{id}")
public String getById(@PathVariable("id") String id){
System.out.println("被调用,时间:" + System.currentTimeMillis());
try {
TimeUnit.SECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "feign-provider:com.jiguiquan.springcloud.controller.OrderController#getById=" + id;
}
服务消费者feign-consumer:
@GetMapping("test")
public String test(){
return orderService.getById("100");
}
2、不配置任何重试机制,使用默认值:
调用接口后,看服务提供者的日志(重试了2次,时间间隔为1秒):

3、我们为系统注入Feign的默认Retryer重试机制:
@Bean
public Retryer retryer(){
return new Retryer.Default();
}
再次调用,再看日志(重试了10次,):

4、Feign的默认重试器的核心代码(默认是不开启的):
public static class Default implements Retryer {
private final int maxAttempts;
private final long period;
private final long maxPeriod;
int attempt;
long sleptForMillis;
public Default() {
// 默认的重试间隔时间100ms,最大间隔时间为1s,最大重试次数为5次
this(100L, TimeUnit.SECONDS.toMillis(1L), 5);
}
// 默认下次重试间隔时间 100 * 1.5^(尝试轮次-1) 次方
// 第一次尝试:100 * 1.5(2 -1) = 150ms; 以此类推
long nextMaxInterval() {
long interval = (long)((double)this.period * Math.pow(1.5D, (double)(this.attempt - 1)));
return interval > this.maxPeriod ? this.maxPeriod : interval;
}
}
5、Ribbon和Feign的重试器的源码就不过度追溯了,直接上结论:
-
Ribbon的重试机制默认是开启的,重试1次,共调用两次;
-
Feign的Retryer重试器默认是关闭的 NEVER_RETRY;
-
Feign如果想开启默认重试器,直接在Spring容器中注入 Retryer.Default 即可,默认重试5次;
6、修改Ribbon的默认重试机制:
# Ribbon 配置 ribbon: # 单节点最大重试次数(不包含默认调用的1次),达到最大值时,切换到下一个示例 MaxAutoRetries: 0 # 0 相当于关闭ribbon重试 # 更换下一个重试节点的最大次数,可以设置为服务提供者副本数(副本数 = 总机器数 - 1),也是就每个副本都查询一次 MaxAutoRetriesNextServer: 0 # 是否对所有请求进行重试,默认fasle,则只会对GET请求进行重试,建议配置为false,不然添加数据接口,会造成多条重复,也就是幂等性问题。 OkToRetryOnAllOperations: false
7、自定义Feign重试机制(直接给 Retryer.Default 构造方法传参即可):
@Bean
public Retryer retryer(){
return new Retryer.Default(100, 1000, 3); // 调用3次(包含原本的一次调用)
}
当然,如果对 Retryer.Dafault 的默认的方法逻辑不认可,可以直接实现一个自己的CustomRetryer注入到spring容器中即可:
@Bean
public Retryer customerRetryer(){
return new Retryer() {
@Override
public void continueOrPropagate(RetryableException e) {
}
@Override
public Retryer clone() {
return null;
}
};
}



